Overhaul frontend organization and pause message polling during repeater operations

This commit is contained in:
Jack Kingsman committed 2026-01-10 16:27:15 -08:00
1 parent e559d6cd47
commit b0ab2bcb32
15 files changed
+1817 -1131

No files matched your search

+16 -1
View File
@@ -12,6 +12,7 @@ don't work reliably.
import asyncio
import logging
import time
from contextlib import asynccontextmanager
from meshcore import EventType
@@ -28,6 +29,20 @@ _message_poll_task: asyncio.Task | None = None
# Message poll interval in seconds
MESSAGE_POLL_INTERVAL = 5
# Flag to pause polling during repeater operations
_polling_paused: bool = False
@asynccontextmanager
async def pause_polling():
"""Context manager to pause message polling during repeater operations."""
global _polling_paused
_polling_paused = True
try:
yield
finally:
_polling_paused = False
# Background task handle
_sync_task: asyncio.Task | None = None
@@ -276,7 +291,7 @@ async def _message_poll_loop():
try:
await asyncio.sleep(MESSAGE_POLL_INTERVAL)
if radio_manager.is_connected:
if radio_manager.is_connected and not _polling_paused:
await poll_for_messages()
except asyncio.CancelledError:
+1 -1
View File
@@ -15,7 +15,7 @@ class ContactRepository:
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(public_key) DO UPDATE SET
name = COALESCE(excluded.name, contacts.name),
type = excluded.type,
type = CASE WHEN excluded.type = 0 THEN contacts.type ELSE excluded.type END,
flags = excluded.flags,
last_path = COALESCE(excluded.last_path, contacts.last_path),
last_path_len = excluded.last_path_len,
+42 -39
View File
@@ -23,6 +23,7 @@ ACL_PERMISSION_NAMES = {
3: "Admin",
}
from app.radio import radio_manager
from app.radio_sync import pause_polling
from app.repository import ContactRepository
logger = logging.getLogger(__name__)
@@ -382,50 +383,52 @@ async def send_repeater_command(public_key: str, request: CommandRequest) -> Com
detail=f"Contact is not a repeater (type={contact.type}, expected {CONTACT_TYPE_REPEATER})"
)
# Send the command
logger.info("Sending command to repeater %s: %s", contact.public_key[:12], request.command)
# Pause message polling to prevent it from stealing our response
async with pause_polling():
# Send the command
logger.info("Sending command to repeater %s: %s", contact.public_key[:12], request.command)
send_result = await mc.commands.send_cmd(contact.public_key, request.command)
send_result = await mc.commands.send_cmd(contact.public_key, request.command)
if send_result.type == EventType.ERROR:
raise HTTPException(
status_code=500,
detail=f"Failed to send command: {send_result.payload}"
)
# Wait for response (MESSAGES_WAITING event, then get_msg)
try:
wait_result = await mc.wait_for_event(EventType.MESSAGES_WAITING, timeout=10.0)
if wait_result is None:
# Timeout - no response received
logger.warning("No response from repeater %s for command: %s", contact.public_key[:12], request.command)
return CommandResponse(
command=request.command,
response="(no response - command may have been processed)"
if send_result.type == EventType.ERROR:
raise HTTPException(
status_code=500,
detail=f"Failed to send command: {send_result.payload}"
)
response_event = await mc.commands.get_msg()
# Wait for response (MESSAGES_WAITING event, then get_msg)
try:
wait_result = await mc.wait_for_event(EventType.MESSAGES_WAITING, timeout=10.0)
if wait_result is None:
# Timeout - no response received
logger.warning("No response from repeater %s for command: %s", contact.public_key[:12], request.command)
return CommandResponse(
command=request.command,
response="(no response - command may have been processed)"
)
response_event = await mc.commands.get_msg()
if response_event.type == EventType.ERROR:
return CommandResponse(
command=request.command,
response=f"(error: {response_event.payload})"
)
# Extract the response text and timestamp from the payload
response_text = response_event.payload.get("text", str(response_event.payload))
sender_timestamp = response_event.payload.get("timestamp")
logger.info("Received response from %s: %s", contact.public_key[:12], response_text)
if response_event.type == EventType.ERROR:
return CommandResponse(
command=request.command,
response=f"(error: {response_event.payload})"
response=response_text,
sender_timestamp=sender_timestamp,
)
except Exception as e:
logger.error("Error waiting for response: %s", e)
return CommandResponse(
command=request.command,
response=f"(error waiting for response: {e})"
)
# Extract the response text and timestamp from the payload
response_text = response_event.payload.get("text", str(response_event.payload))
sender_timestamp = response_event.payload.get("timestamp")
logger.info("Received response from %s: %s", contact.public_key[:12], response_text)
return CommandResponse(
command=request.command,
response=response_text,
sender_timestamp=sender_timestamp,
)
except Exception as e:
logger.error("Error waiting for response: %s", e)
return CommandResponse(
command=request.command,
response=f"(error waiting for response: {e})"
)
+537
View File
@@ -0,0 +1,537 @@
var ek=Object.defineProperty;var ik=(a,r,o)=>r in a?ek(a,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):a[r]=o;var Ln=(a,r,o)=>ik(a,typeof r!="symbol"?r+"":r,o);function ak(a,r){for(var o=0;o<r.length;o++){const s=r[o];if(typeof s!="string"&&!Array.isArray(s)){for(const u in s)if(u!=="default"&&!(u in a)){const l=Object.getOwnPropertyDescriptor(s,u);l&&Object.defineProperty(a,u,l.get?l:{enumerable:!0,get:()=>s[u]})}}}return Object.freeze(Object.defineProperty(a,Symbol.toStringTag,{value:"Module"}))}(function(){const r=document.createElement("link").relList;if(r&&r.supports&&r.supports("modulepreload"))return;for(const u of document.querySelectorAll('link[rel="modulepreload"]'))s(u);new MutationObserver(u=>{for(const l of u)if(l.type==="childList")for(const m of l.addedNodes)m.tagName==="LINK"&&m.rel==="modulepreload"&&s(m)}).observe(document,{childList:!0,subtree:!0});function o(u){const l={};return u.integrity&&(l.integrity=u.integrity),u.referrerPolicy&&(l.referrerPolicy=u.referrerPolicy),u.crossOrigin==="use-credentials"?l.credentials="include":u.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function s(u){if(u.ep)return;u.ep=!0;const l=o(u);fetch(u.href,l)}})();var eu=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function Ro(a){return a&&a.__esModule&&Object.prototype.hasOwnProperty.call(a,"default")?a.default:a}function $g(a){if(Object.prototype.hasOwnProperty.call(a,"__esModule"))return a;var r=a.default;if(typeof r=="function"){var o=function s(){return this instanceof s?Reflect.construct(r,arguments,this.constructor):r.apply(this,arguments)};o.prototype=r.prototype}else o={};return Object.defineProperty(o,"__esModule",{value:!0}),Object.keys(a).forEach(function(s){var u=Object.getOwnPropertyDescriptor(a,s);Object.defineProperty(o,s,u.get?u:{enumerable:!0,get:function(){return a[s]}})}),o}var iu={exports:{}},fr={},au={exports:{}},Te={};/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var Hm;function nk(){if(Hm)return Te;Hm=1;var a=Symbol.for("react.element"),r=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),s=Symbol.for("react.strict_mode"),u=Symbol.for("react.profiler"),l=Symbol.for("react.provider"),m=Symbol.for("react.context"),c=Symbol.for("react.forward_ref"),h=Symbol.for("react.suspense"),d=Symbol.for("react.memo"),g=Symbol.for("react.lazy"),y=Symbol.iterator;function f(j){return j===null||typeof j!="object"?null:(j=y&&j[y]||j["@@iterator"],typeof j=="function"?j:null)}var w={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},k=Object.assign,x={};function v(j,I,L){this.props=j,this.context=I,this.refs=x,this.updater=L||w}v.prototype.isReactComponent={},v.prototype.setState=function(j,I){if(typeof j!="object"&&typeof j!="function"&&j!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,j,I,"setState")},v.prototype.forceUpdate=function(j){this.updater.enqueueForceUpdate(this,j,"forceUpdate")};function q(){}q.prototype=v.prototype;function A(j,I,L){this.props=j,this.context=I,this.refs=x,this.updater=L||w}var z=A.prototype=new q;z.constructor=A,k(z,v.prototype),z.isPureReactComponent=!0;var _=Array.isArray,C=Object.prototype.hasOwnProperty,D={current:null},F={key:!0,ref:!0,__self:!0,__source:!0};function R(j,I,L){var $,le={},me=null,ge=null;if(I!=null)for($ in I.ref!==void 0&&(ge=I.ref),I.key!==void 0&&(me=""+I.key),I)C.call(I,$)&&!F.hasOwnProperty($)&&(le[$]=I[$]);var oe=arguments.length-2;if(oe===1)le.children=L;else if(1<oe){for(var ce=Array(oe),je=0;je<oe;je++)ce[je]=arguments[je+2];le.children=ce}if(j&&j.defaultProps)for($ in oe=j.defaultProps,oe)le[$]===void 0&&(le[$]=oe[$]);return{$$typeof:a,type:j,key:me,ref:ge,props:le,_owner:D.current}}function M(j,I){return{$$typeof:a,type:j.type,key:I,ref:j.ref,props:j.props,_owner:j._owner}}function P(j){return typeof j=="object"&&j!==null&&j.$$typeof===a}function T(j){var I={"=":"=0",":":"=2"};return"$"+j.replace(/[=:]/g,function(L){return I[L]})}var U=/\/+/g;function H(j,I){return typeof j=="object"&&j!==null&&j.key!=null?T(""+j.key):I.toString(36)}function K(j,I,L,$,le){var me=typeof j;(me==="undefined"||me==="boolean")&&(j=null);var ge=!1;if(j===null)ge=!0;else switch(me){case"string":case"number":ge=!0;break;case"object":switch(j.$$typeof){case a:case r:ge=!0}}if(ge)return ge=j,le=le(ge),j=$===""?"."+H(ge,0):$,_(le)?(L="",j!=null&&(L=j.replace(U,"$&/")+"/"),K(le,I,L,"",function(je){return je})):le!=null&&(P(le)&&(le=M(le,L+(!le.key||ge&&ge.key===le.key?"":(""+le.key).replace(U,"$&/")+"/")+j)),I.push(le)),1;if(ge=0,$=$===""?".":$+":",_(j))for(var oe=0;oe<j.length;oe++){me=j[oe];var ce=$+H(me,oe);ge+=K(me,I,L,ce,le)}else if(ce=f(j),typeof ce=="function")for(j=ce.call(j),oe=0;!(me=j.next()).done;)me=me.value,ce=$+H(me,oe++),ge+=K(me,I,L,ce,le);else if(me==="object")throw I=String(j),Error("Objects are not valid as a React child (found: "+(I==="[object Object]"?"object with keys {"+Object.keys(j).join(", ")+"}":I)+"). If you meant to render a collection of children, use an array instead.");return ge}function ie(j,I,L){if(j==null)return j;var $=[],le=0;return K(j,$,"","",function(me){return I.call(L,me,le++)}),$}function Z(j){if(j._status===-1){var I=j._result;I=I(),I.then(function(L){(j._status===0||j._status===-1)&&(j._status=1,j._result=L)},function(L){(j._status===0||j._status===-1)&&(j._status=2,j._result=L)}),j._status===-1&&(j._status=0,j._result=I)}if(j._status===1)return j._result.default;throw j._result}var ae={current:null},W={transition:null},ne={ReactCurrentDispatcher:ae,ReactCurrentBatchConfig:W,ReactCurrentOwner:D};function X(){throw Error("act(...) is not supported in production builds of React.")}return Te.Children={map:ie,forEach:function(j,I,L){ie(j,function(){I.apply(this,arguments)},L)},count:function(j){var I=0;return ie(j,function(){I++}),I},toArray:function(j){return ie(j,function(I){return I})||[]},only:function(j){if(!P(j))throw Error("React.Children.only expected to receive a single React element child.");return j}},Te.Component=v,Te.Fragment=o,Te.Profiler=u,Te.PureComponent=A,Te.StrictMode=s,Te.Suspense=h,Te.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=ne,Te.act=X,Te.cloneElement=function(j,I,L){if(j==null)throw Error("React.cloneElement(...): The argument must be a React element, but you passed "+j+".");var $=k({},j.props),le=j.key,me=j.ref,ge=j._owner;if(I!=null){if(I.ref!==void 0&&(me=I.ref,ge=D.current),I.key!==void 0&&(le=""+I.key),j.type&&j.type.defaultProps)var oe=j.type.defaultProps;for(ce in I)C.call(I,ce)&&!F.hasOwnProperty(ce)&&($[ce]=I[ce]===void 0&&oe!==void 0?oe[ce]:I[ce])}var ce=arguments.length-2;if(ce===1)$.children=L;else if(1<ce){oe=Array(ce);for(var je=0;je<ce;je++)oe[je]=arguments[je+2];$.children=oe}return{$$typeof:a,type:j.type,key:le,ref:me,props:$,_owner:ge}},Te.createContexLine truncated
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var Um;function ok(){if(Um)return fr;Um=1;var a=Iu(),r=Symbol.for("react.element"),o=Symbol.for("react.fragment"),s=Object.prototype.hasOwnProperty,u=a.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l={key:!0,ref:!0,__self:!0,__source:!0};function m(c,h,d){var g,y={},f=null,w=null;d!==void 0&&(f=""+d),h.key!==void 0&&(f=""+h.key),h.ref!==void 0&&(w=h.ref);for(g in h)s.call(h,g)&&!l.hasOwnProperty(g)&&(y[g]=h[g]);if(c&&c.defaultProps)for(g in h=c.defaultProps,h)y[g]===void 0&&(y[g]=h[g]);return{$$typeof:r,type:c,key:f,ref:w,props:y,_owner:u.current}}return fr.Fragment=o,fr.jsx=m,fr.jsxs=m,fr}var Wm;function rk(){return Wm||(Wm=1,iu.exports=ok()),iu.exports}var B=rk(),E=Iu();const pe=Ro(E),Mu=ak({__proto__:null,default:pe},[E]);var Ys={},nu={exports:{}},aa={},ou={exports:{}},ru={};/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var $m;function sk(){return $m||($m=1,(function(a){function r(W,ne){var X=W.length;W.push(ne);e:for(;0<X;){var j=X-1>>>1,I=W[j];if(0<u(I,ne))W[j]=ne,W[X]=I,X=j;else break e}}function o(W){return W.length===0?null:W[0]}function s(W){if(W.length===0)return null;var ne=W[0],X=W.pop();if(X!==ne){W[0]=X;e:for(var j=0,I=W.length,L=I>>>1;j<L;){var $=2*(j+1)-1,le=W[$],me=$+1,ge=W[me];if(0>u(le,X))me<I&&0>u(ge,le)?(W[j]=ge,W[me]=X,j=me):(W[j]=le,W[$]=X,j=$);else if(me<I&&0>u(ge,X))W[j]=ge,W[me]=X,j=me;else break e}}return ne}function u(W,ne){var X=W.sortIndex-ne.sortIndex;return X!==0?X:W.id-ne.id}if(typeof performance=="object"&&typeof performance.now=="function"){var l=performance;a.unstable_now=function(){return l.now()}}else{var m=Date,c=m.now();a.unstable_now=function(){return m.now()-c}}var h=[],d=[],g=1,y=null,f=3,w=!1,k=!1,x=!1,v=typeof setTimeout=="function"?setTimeout:null,q=typeof clearTimeout=="function"?clearTimeout:null,A=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function z(W){for(var ne=o(d);ne!==null;){if(ne.callback===null)s(d);else if(ne.startTime<=W)s(d),ne.sortIndex=ne.expirationTime,r(h,ne);else break;ne=o(d)}}function _(W){if(x=!1,z(W),!k)if(o(h)!==null)k=!0,Z(C);else{var ne=o(d);ne!==null&&ae(_,ne.startTime-W)}}function C(W,ne){k=!1,x&&(x=!1,q(R),R=-1),w=!0;var X=f;try{for(z(ne),y=o(h);y!==null&&(!(y.expirationTime>ne)||W&&!T());){var j=y.callback;if(typeof j=="function"){y.callback=null,f=y.priorityLevel;var I=j(y.expirationTime<=ne);ne=a.unstable_now(),typeof I=="function"?y.callback=I:y===o(h)&&s(h),z(ne)}else s(h);y=o(h)}if(y!==null)var L=!0;else{var $=o(d);$!==null&&ae(_,$.startTime-ne),L=!1}return L}finally{y=null,f=X,w=!1}}var D=!1,F=null,R=-1,M=5,P=-1;function T(){return!(a.unstable_now()-P<M)}function U(){if(F!==null){var W=a.unstable_now();P=W;var ne=!0;try{ne=F(!0,W)}finally{ne?H():(D=!1,F=null)}}else D=!1}var H;if(typeof A=="function")H=function(){A(U)};else if(typeof MessageChannel<"u"){var K=new MessageChannel,ie=K.port2;K.port1.onmessage=U,H=function(){ie.postMessage(null)}}else H=function(){v(U,0)};function Z(W){F=W,D||(D=!0,H())}function ae(W,ne){R=v(function(){W(a.unstable_now())},ne)}a.unstable_IdlePriority=5,a.unstable_ImmediatePriority=1,a.unstable_LowPriority=4,a.unstable_NormalPriority=3,a.unstable_Profiling=null,a.unstable_UserBlockingPriority=2,a.unstable_cancelCallback=function(W){W.callback=null},a.unstable_continueExecution=function(){k||w||(k=!0,Z(C))},a.unstable_forceFrameRate=function(W){0>W||125<W?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):M=0<W?Math.floor(1e3/W):5},a.unstable_getCurrentPriorityLevel=function(){return f},a.unstable_getFirstCallbackNode=function(){return o(h)},a.unstable_next=function(W){switch(f){case 1:case 2:case 3:var ne=3;break;default:ne=f}var X=f;f=ne;try{return W()}finally{f=X}},a.unstable_pauseExecution=function(){},a.unstable_requestPaint=function(){},a.unstable_runWithPriority=function(W,ne){switch(W){case 1:case 2:case 3:case 4:case 5:break;default:W=3}var X=f;f=W;try{return ne()}finally{f=X}},a.unstable_scheduleCallback=function(W,ne,X){var j=a.unstable_now();switch(typeof X=="object"&&X!==null?(X=X.delay,X=typeof X=="number"&&0<X?j+X:j):X=j,W){case 1:var I=-1;break;case 2:I=250;break;case 5:I=1073741823;break;case 4:I=1e4;break;default:I=5e3}return I=X+I,W={id:g++,callback:ne,priorityLevel:W,startTime:X,expirationTime:I,sortIndex:-1},X>j?(W.sortIndex=X,r(d,W),o(h)===null&&W===o(d)&&(x?(q(R),R=-1):x=!0,ae(_,X-j))):(W.sortIndex=I,r(h,W),k||w||(k=!0,Z(C))),W},a.unstable_shouldYield=T,a.unstable_wrapCallback=function(W){var ne=f;return function(){var X=f;f=ne;try{return W.apply(this,arguments)}finally{f=X}}}})(ru)),ru}var Gm;function tk(){return Gm||(Gm=1,ou.exports=sk()),ou.exports}/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var Qm;function lk(){if(Qm)return aa;Qm=1;var a=Iu(),r=tk();function o(e){for(var i="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n<arguments.length;n++)i+="&args[]="+encodeURIComponent(arguments[n]);return"Minified React error #"+e+"; visit "+i+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}var s=new Set,u={};function l(e,i){m(e,i),m(e+"Capture",i)}function m(e,i){for(u[e]=i,e=0;e<i.length;e++)s.add(i[e])}var c=!(typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),h=Object.prototype.hasOwnProperty,d=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,g={},y={};function f(e){return h.call(y,e)?!0:h.call(g,e)?!1:d.test(e)?y[e]=!0:(g[e]=!0,!1)}function w(e,i,n,t){if(n!==null&&n.type===0)return!1;switch(typeof i){case"function":case"symbol":return!0;case"boolean":return t?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function k(e,i,n,t){if(i===null||typeof i>"u"||w(e,i,n,t))return!0;if(t)return!1;if(n!==null)switch(n.type){case 3:return!i;case 4:return i===!1;case 5:return isNaN(i);case 6:return isNaN(i)||1>i}return!1}function x(e,i,n,t,p,b,S){this.acceptsBooleans=i===2||i===3||i===4,this.attributeName=t,this.attributeNamespace=p,this.mustUseProperty=n,this.propertyName=e,this.type=i,this.sanitizeURL=b,this.removeEmptyString=S}var v={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){v[e]=new x(e,0,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var i=e[0];v[i]=new x(i,1,!1,e[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){v[e]=new x(e,2,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){v[e]=new x(e,2,!1,e,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){v[e]=new x(e,3,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){v[e]=new x(e,3,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){v[e]=new x(e,4,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){v[e]=new x(e,6,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){v[e]=new x(e,5,!1,e.toLowerCase(),null,!1,!1)});var q=/[\-:]([a-z])/g;function A(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var i=e.replace(q,A);v[i]=new x(i,1,!1,e,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var i=e.replace(q,A);v[i]=new x(i,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var i=e.replace(q,A);v[i]=new x(i,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){v[e]=new x(e,1,!1,e.toLowerCase(),null,!1,!1)}),v.xlinkHref=new x("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){v[e]=new x(e,1,!1,e.toLowerCase(),null,!0,!0)});function z(e,i,n,t){var p=v.hasOwnProperty(i)?v[i]:null;(p!==null?p.type!==0:t||!(2<i.length)||i[0]!=="o"&&i[0]!=="O"||i[1]!=="n"&&i[1]!= Line truncated
`+j+e}var L=!1;function $(e,i){if(!e||L)return"";L=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(i)if(i=function(){throw Error()},Object.defineProperty(i.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(i,[])}catch(Y){var t=Y}Reflect.construct(e,[],i)}else{try{i.call()}catch(Y){t=Y}e.call(i.prototype)}else{try{throw Error()}catch(Y){t=Y}e()}}catch(Y){if(Y&&t&&typeof Y.stack=="string"){for(var p=Y.stack.split(`
`),b=t.stack.split(`
`),S=p.length-1,N=b.length-1;1<=S&&0<=N&&p[S]!==b[N];)N--;for(;1<=S&&0<=N;S--,N--)if(p[S]!==b[N]){if(S!==1||N!==1)do if(S--,N--,0>N||p[S]!==b[N]){var O=`
`+p[S].replace(" at new "," at ");return e.displayName&&O.includes("<anonymous>")&&(O=O.replace("<anonymous>",e.displayName)),O}while(1<=S&&0<=N);break}}}finally{L=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?I(e):""}function le(e){switch(e.tag){case 5:return I(e.type);case 16:return I("Lazy");case 13:return I("Suspense");case 19:return I("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 me(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 F:return"Fragment";case D:return"Portal";case M:return"Profiler";case R:return"StrictMode";case H:return"Suspense";case K:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case T:return(e.displayName||"Context")+".Consumer";case P:return(e._context.displayName||"Context")+".Provider";case U:var i=e.render;return e=e.displayName,e||(e=i.displayName||i.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case ie:return i=e.displayName||null,i!==null?i:me(e.type)||"Memo";case Z:i=e._payload,e=e._init;try{return me(e(i))}catch{}}return null}function ge(e){var i=e.type;switch(e.tag){case 24:return"Cache";case 9:return(i.displayName||"Context")+".Consumer";case 10:return(i._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=i.render,e=e.displayName||e.name||"",i.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return i;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return me(i);case 8:return i===R?"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 i=="function")return i.displayName||i.name||null;if(typeof i=="string")return i}return null}function oe(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function ce(e){var i=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(i==="checkbox"||i==="radio")}function je(e){var i=ce(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,i),t=""+e[i];if(!e.hasOwnProperty(i)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var p=n.get,b=n.set;return Object.defineProperty(e,i,{configurable:!0,get:function(){return p.call(this)},set:function(S){t=""+S,b.call(this,S)}}),Object.defineProperty(e,i,{enumerable:n.enumerable}),{getValue:function(){return t},setValue:function(S){t=""+S},stopTracking:function(){e._valueTracker=null,delete e[i]}}}}function De(e){e._valueTracker||(e._valueTracker=je(e))}function Pe(e){if(!e)return!1;var i=e._valueTracker;if(!i)return!0;var n=i.getValue(),t="";return e&&(t=ce(e)?e.checked?"true":"false":e.value),e=t,e!==n?(i.setValue(e),!0):!1}function ai(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 Re(e,i){var n=i.checked;return X({},i,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function He(e,i){var n=i.defaultValue==null?"":i.defaultValue,t=i.checked!=null?i.checked:i.defaultChecked;n=oe(i.value!=null?i.value:n),e._wrapperState={initialChecked:t,initialValue:n,controlled:i.type==="checkbox"||i.type==="radio"?i.checked!=null:i.value!=null}}function Oe(e,i){i=i.checked,i!=null&&z(e,"checked",i,!1)}function Je(e,i){Oe(e,i);var n=oe(i.value),t=i.type;if(n!=null)t==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(t==="submit"||t==="reset"){e.removeAttribute("value");return}i.hasOwnProperty("value")?oi(e,i.type,n):i.hasOwnProperty("defaultValue")&&oi(e,i.type,oe(i.defaultValue)),i.checked==null&&i.defaultChecked!=null&&(e.defaultChecked=!!i.defaultChecked)}function Ge(e,i,n){if(i.hasOwnProperty("value")||i.hasOwnProperty("defaultValue")){var t=i.type;if(!(t!=="submit"&&t!=="reset"||i.value!==void 0&&i.value!==null))return;i=""+e._wrapperState.initialValue,n||i===e.value||(e.value=i),e.defaultValue=i}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function oi(e,i,n){(i!=="number"||ai(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var fi=Array.isArray;function ti(e,i,n,t){if(e=e.options,i){i={};for(var p=0;p<n.length;p++)i["$"+n[p]]=!0;for(n=0;n<e.length;n++)p=i.hasOwnProperty("$"+e[n].value),e[n].selected!==p&&(e[n].selected=p),p&&t&&(e[n].defaultSelected=!0)}else{for(n=""+oe(n),i=null,p=0;p<e.length;p++){if(e[p].value===n){e[p].selected=!0,t&&(e[p].defaultSelected=!0);return}i!==null||e[p].disabled||(i=e[p])}i!==null&&(i.selected=!0)}}function Ve(e,i){if(i.dangerouslySetInnerHTML!= Line truncated
`).replace(mv,"")}function cs(e,i,n){if(i=tp(i),tp(e)!==i&&n)throw Error(o(425))}function us(){}var Wl=null,$l=null;function Gl(e,i){return e==="textarea"||e==="noscript"||typeof i.children=="string"||typeof i.children=="number"||typeof i.dangerouslySetInnerHTML=="object"&&i.dangerouslySetInnerHTML!==null&&i.dangerouslySetInnerHTML.__html!=null}var Ql=typeof setTimeout=="function"?setTimeout:void 0,hv=typeof clearTimeout=="function"?clearTimeout:void 0,lp=typeof Promise=="function"?Promise:void 0,gv=typeof queueMicrotask=="function"?queueMicrotask:typeof lp<"u"?function(e){return lp.resolve(null).then(e).catch(yv)}:Ql;function yv(e){setTimeout(function(){throw e})}function Kl(e,i){var n=i,t=0;do{var p=n.nextSibling;if(e.removeChild(n),p&&p.nodeType===8)if(n=p.data,n==="/$"){if(t===0){e.removeChild(p),Wo(i);return}t--}else n!=="$"&&n!=="$?"&&n!=="$!"||t++;n=p}while(n);Wo(i)}function un(e){for(;e!=null;e=e.nextSibling){var i=e.nodeType;if(i===1||i===3)break;if(i===8){if(i=e.data,i==="$"||i==="$!"||i==="$?")break;if(i==="/$")return null}}return e}function cp(e){e=e.previousSibling;for(var i=0;e;){if(e.nodeType===8){var n=e.data;if(n==="$"||n==="$!"||n==="$?"){if(i===0)return e;i--}else n==="/$"&&i++}e=e.previousSibling}return null}var to=Math.random().toString(36).slice(2),Ra="__reactFiber$"+to,ar="__reactProps$"+to,Ua="__reactContainer$"+to,Yl="__reactEvents$"+to,bv="__reactListeners$"+to,fv="__reactHandles$"+to;function Sn(e){var i=e[Ra];if(i)return i;for(var n=e.parentNode;n;){if(i=n[Ua]||n[Ra]){if(n=i.alternate,i.child!==null||n!==null&&n.child!==null)for(e=cp(e);e!==null;){if(n=e[Ra])return n;e=cp(e)}return i}e=n,n=e.parentNode}return null}function nr(e){return e=e[Ra]||e[Ua],!e||e.tag!==5&&e.tag!==6&&e.tag!==13&&e.tag!==3?null:e}function lo(e){if(e.tag===5||e.tag===6)return e.stateNode;throw Error(o(33))}function ds(e){return e[ar]||null}var Zl=[],co=-1;function dn(e){return{current:e}}function ui(e){0>co||(e.current=Zl[co],Zl[co]=null,co--)}function si(e,i){co++,Zl[co]=e.current,e.current=i}var pn={},Oi=dn(pn),Zi=dn(!1),Bn=pn;function uo(e,i){var n=e.type.contextTypes;if(!n)return pn;var t=e.stateNode;if(t&&t.__reactInternalMemoizedUnmaskedChildContext===i)return t.__reactInternalMemoizedMaskedChildContext;var p={},b;for(b in n)p[b]=i[b];return t&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=i,e.__reactInternalMemoizedMaskedChildContext=p),p}function Xi(e){return e=e.childContextTypes,e!=null}function ps(){ui(Zi),ui(Oi)}function up(e,i,n){if(Oi.current!==pn)throw Error(o(168));si(Oi,i),si(Zi,n)}function dp(e,i,n){var t=e.stateNode;if(i=i.childContextTypes,typeof t.getChildContext!="function")return n;t=t.getChildContext();for(var p in t)if(!(p in i))throw Error(o(108,ge(e)||"Unknown",p));return X({},n,t)}function ms(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||pn,Bn=Oi.current,si(Oi,e),si(Zi,Zi.current),!0}function pp(e,i,n){var t=e.stateNode;if(!t)throw Error(o(169));n?(e=dp(e,i,Bn),t.__reactInternalMemoizedMergedChildContext=e,ui(Zi),ui(Oi),si(Oi,e)):ui(Zi),si(Zi,n)}var Wa=null,hs=!1,Xl=!1;function mp(e){Wa===null?Wa=[e]:Wa.push(e)}function vv(e){hs=!0,mp(e)}function mn(){if(!Xl&&Wa!==null){Xl=!0;var e=0,i=ii;try{var n=Wa;for(ii=1;e<n.length;e++){var t=n[e];do t=t(!0);while(t!==null)}Wa=null,hs=!1}catch(p){throw Wa!==null&&(Wa=Wa.slice(e+1)),gd(kl,mn),p}finally{ii=i,Xl=!1}}return null}var po=[],mo=0,gs=null,ys=0,pa=[],ma=0,_n=null,$a=1,Ga="";function Dn(e,i){po[mo++]=ys,po[mo++]=gs,gs=e,ys=i}function hp(e,i,n){pa[ma++]=$a,pa[ma++]=Ga,pa[ma++]=_n,_n=e;var t=$a;e=Ga;var p=32-za(t)-1;t&=~(1<<p),n+=1;var b=32-za(i)+p;if(30<b){var S=p-p%5;b=(t&(1<<S)-1).toString(32),t>>=S,p-=S,$a=1<<32-za(i)+p|n<<p|t,Ga=b+e}else $a=1<<b|n<<p|t,Ga=e}function Jl(e){e.return!==null&&(Dn(e,1),hp(e,1,0))}function ec(e){for(;e===gs;)gs=po[--mo],po[mo]=null,ys=po[--mo],po[mo]=null;for(;e===_n;)_n=pa[--ma],pa[ma]=null,Ga=pa[--ma],pa[ma]=null,$a=pa[--ma],pa[ma]=null}var sa=null,ta=null,di=!1,ja=null;function gp(e,i){var n=ba(5,null,null,0);n.elementType="DELETED",n.stateNode=i,n.return=e,i=e.deletions,i===null?(e.deletions=[n],e.flags|=16):i.push(n)}function yp(e,i){switch(e.tag){case 5:var n=e.type;return i=i.nodeType!==1||n.toLowerCase()!==i.nodeName.toLowerCase()?null:i,i!==null?(e.stateNode=i,sa=e,ta=un(i.firstChild),!0):!1;case 6:return i=e.pendingProps===""||i.nodeType!==3?null:i,i!==null?(e.stateNode=i,sa=e,ta=null,!0):!1;case 13:return i=i.nodeType!==8?null:i,i!==null?(n=_n!==null?{id:$a,overflow:Ga}:null,e.memoizedState={dehydrated:i,treeContext:n,retryLane:1073741824},n=ba(18,null,null,0),n.stateNode=i,n.return=e,e.child=n,sa=e,ta=null,!0):!1;default:return!1}}function ic(e){return(e.mode&1)!==0&&(e.flags&128)===0}function ac(e){if(di){var i=ta;if(i){var n=i;if(!yp(e,i)){if(ic(e))throw Error(o(418));i=un(n.nextSibling);var t=sa;i&&yp(e,i)?gp(t,n):(e.flags=e.flags&-4097|2,di=!1,sa=e)}}else{if(ic(e))throw Error(o(418));e.flags=e.flags&-4097|2,di=!1,sa=e}}}function bp(e){for(e=e. Line truncated
Error generating stack: `+b.message+`
`+b.stack}return{value:e,source:i,stack:p,digest:null}}function qc(e,i,n){return{value:e,source:null,stack:n??null,digest:i??null}}function jc(e,i){try{console.error(i.value)}catch(n){setTimeout(function(){throw n})}}var Ev=typeof WeakMap=="function"?WeakMap:Map;function Zp(e,i,n){n=Ka(-1,n),n.tag=3,n.payload={element:null};var t=i.value;return n.callback=function(){Ps||(Ps=!0,Oc=t),jc(e,i)},n}function Xp(e,i,n){n=Ka(-1,n),n.tag=3;var t=e.type.getDerivedStateFromError;if(typeof t=="function"){var p=i.value;n.payload=function(){return t(p)},n.callback=function(){jc(e,i)}}var b=e.stateNode;return b!==null&&typeof b.componentDidCatch=="function"&&(n.callback=function(){jc(e,i),typeof t!="function"&&(bn===null?bn=new Set([this]):bn.add(this));var S=i.stack;this.componentDidCatch(i.value,{componentStack:S!==null?S:""})}),n}function Jp(e,i,n){var t=e.pingCache;if(t===null){t=e.pingCache=new Ev;var p=new Set;t.set(i,p)}else p=t.get(i),p===void 0&&(p=new Set,t.set(i,p));p.has(n)||(p.add(n),e=Hv.bind(null,e,i,n),i.then(e,e))}function em(e){do{var i;if((i=e.tag===13)&&(i=e.memoizedState,i=i!==null?i.dehydrated!==null:!0),i)return e;e=e.return}while(e!==null);return null}function im(e,i,n,t,p){return(e.mode&1)===0?(e===i?e.flags|=65536:(e.flags|=128,n.flags|=131072,n.flags&=-52805,n.tag===1&&(n.alternate===null?n.tag=17:(i=Ka(-1,1),i.tag=2,gn(n,i,1))),n.lanes|=1),e):(e.flags|=65536,e.lanes=p,e)}var Sv=_.ReactCurrentOwner,Ji=!1;function $i(e,i,n,t){i.child=e===null?wp(i,null,n,t):go(i,e.child,n,t)}function am(e,i,n,t,p){n=n.render;var b=i.ref;return bo(i,p),t=yc(e,i,n,t,b,p),n=bc(),e!==null&&!Ji?(i.updateQueue=e.updateQueue,i.flags&=-2053,e.lanes&=~p,Ya(e,i,p)):(di&&n&&Jl(i),i.flags|=1,$i(e,i,t,p),i.child)}function nm(e,i,n,t,p){if(e===null){var b=n.type;return typeof b=="function"&&!Gc(b)&&b.defaultProps===void 0&&n.compare===null&&n.defaultProps===void 0?(i.tag=15,i.type=b,om(e,i,b,t,p)):(e=Hs(n.type,null,t,i,i.mode,p),e.ref=i.ref,e.return=i,i.child=e)}if(b=e.child,(e.lanes&p)===0){var S=b.memoizedProps;if(n=n.compare,n=n!==null?n:Zo,n(S,t)&&e.ref===i.ref)return Ya(e,i,p)}return i.flags|=1,e=wn(b,t),e.ref=i.ref,e.return=i,i.child=e}function om(e,i,n,t,p){if(e!==null){var b=e.memoizedProps;if(Zo(b,t)&&e.ref===i.ref)if(Ji=!1,i.pendingProps=t=b,(e.lanes&p)!==0)(e.flags&131072)!==0&&(Ji=!0);else return i.lanes=e.lanes,Ya(e,i,p)}return Ac(e,i,n,t,p)}function rm(e,i,n){var t=i.pendingProps,p=t.children,b=e!==null?e.memoizedState:null;if(t.mode==="hidden")if((i.mode&1)===0)i.memoizedState={baseLanes:0,cachePool:null,transitions:null},si(wo,la),la|=n;else{if((n&1073741824)===0)return e=b!==null?b.baseLanes|n:n,i.lanes=i.childLanes=1073741824,i.memoizedState={baseLanes:e,cachePool:null,transitions:null},i.updateQueue=null,si(wo,la),la|=e,null;i.memoizedState={baseLanes:0,cachePool:null,transitions:null},t=b!==null?b.baseLanes:n,si(wo,la),la|=t}else b!==null?(t=b.baseLanes|n,i.memoizedState=null):t=n,si(wo,la),la|=t;return $i(e,i,p,n),i.child}function sm(e,i){var n=i.ref;(e===null&&n!==null||e!==null&&e.ref!==n)&&(i.flags|=512,i.flags|=2097152)}function Ac(e,i,n,t,p){var b=Xi(n)?Bn:Oi.current;return b=uo(i,b),bo(i,p),n=yc(e,i,n,t,b,p),t=bc(),e!==null&&!Ji?(i.updateQueue=e.updateQueue,i.flags&=-2053,e.lanes&=~p,Ya(e,i,p)):(di&&t&&Jl(i),i.flags|=1,$i(e,i,n,p),i.child)}function tm(e,i,n,t,p){if(Xi(n)){var b=!0;ms(i)}else b=!1;if(bo(i,p),i.stateNode===null)_s(e,i),Kp(i,n,t),zc(i,n,t,p),t=!0;else if(e===null){var S=i.stateNode,N=i.memoizedProps;S.props=N;var O=S.context,Y=n.contextType;typeof Y=="object"&&Y!==null?Y=ha(Y):(Y=Xi(n)?Bn:Oi.current,Y=uo(i,Y));var se=n.getDerivedStateFromProps,te=typeof se=="function"||typeof S.getSnapshotBeforeUpdate=="function";te||typeof S.UNSAFE_componentWillReceiveProps!="function"&&typeof S.componentWillReceiveProps!="function"||(N!==t||O!==Y)&&Yp(i,S,t,Y),hn=!1;var re=i.memoizedState;S.state=re,xs(i,t,S,p),O=i.memoizedState,N!==t||re!==O||Zi.current||hn?(typeof se=="function"&&(xc(i,n,se,t),O=i.memoizedState),(N=hn||Qp(i,n,N,t,re,O,Y))?(te||typeof S.UNSAFE_componentWillMount!="function"&&typeof S.componentWillMount!="function"||(typeof S.componentWillMount=="function"&&S.componentWillMount(),typeof S.UNSAFE_componentWillMount=="function"&&S.UNSAFE_componentWillMount()),typeof S.componentDidMount=="function"&&(i.flags|=4194308)):(typeof S.componentDidMount=="function"&&(i.flags|=4194308),i.memoizedProps=t,i.memoizedState=O),S.props=t,S.state=O,S.context=Y,t=N):(typeof S.componentDidMount=="function"&&(i.flags|=4194308),t=!1)}else{S=i.stateNode,zp(e,i),N=i.memoizedProps,Y=i.type===i.elementType?N:Aa(i.type,N),S.props=Y,te=i.pendingProps,re=S.context,O=n.contextType,typeof O=="object"&&O!==null?O=ha(O):(O=Xi(n)?Bn:Oi.current,O=uo(i,O));var ye=n.getDerivedStateFromProps;(se=typeof ye=="function"||typeof S.getSnapshotBeforeUpdate=="function")||typeof S.UNSAFE_componentWillReceiveProps!="function"&&typeof S.componentWillReceiveProps!="function"||(N!==te||re!==O)&&Yp(i,S,t,O),hn=!1,re=i.memoizedState,S Line truncated
`)}function hk(a){if(a.length===0)return`Neighbors
No neighbors reported`;const r=[...a].sort((s,u)=>u.snr-s.snr),o=[`Neighbors (${r.length})`];for(const s of r){const u=s.name||s.pubkey_prefix,l=s.snr>=0?`+${s.snr.toFixed(1)}`:s.snr.toFixed(1);o.push(`${u}, ${l} dB [${tt(s.last_heard_seconds)} ago]`)}return o.join(`
`)}function gk(a){if(a.length===0)return`ACL
No ACL entries`;const r=[`ACL (${a.length})`];for(const o of a){const s=o.name||o.pubkey_prefix;r.push(`${s}: ${o.permission_name}`)}return r.join(`
`)}function Hn(a,r,o,s=0){const u=Math.floor(Date.now()/1e3);return{id:-Date.now()-s,type:"PRIV",conversation_key:a,text:r,sender_timestamp:u,received_at:u,path_len:null,txt_type:0,signature:null,outgoing:o,acked:1}}function yk(a,r,o){const[s,u]=E.useState(!1);E.useEffect(()=>{u(!1)},[a==null?void 0:a.id]);const l=E.useMemo(()=>{if(!a||a.type!=="contact")return!1;const h=r.find(d=>d.public_key===a.id);return(h==null?void 0:h.type)===Qg},[a,r]),m=E.useCallback(async h=>{if(!(!a||a.type!=="contact")&&l)try{const d=await ei.requestTelemetry(a.id,h),g=Hn(a.id,mk(d),!1,0),y=Hn(a.id,hk(d.neighbors),!1,1),f=Hn(a.id,gk(d.acl),!1,2);o(w=>[...w,g,y,f]),u(!0)}catch(d){const g=Hn(a.id,`Telemetry request failed: ${d instanceof Error?d.message:"Unknown error"}`,!1,0);o(y=>[...y,g])}},[a,l,o]),c=E.useCallback(async h=>{if(!a||a.type!=="contact"||!l||!s)return;const d=Hn(a.id,`> ${h}`,!0,0);o(g=>[...g,d]);try{const g=await ei.sendRepeaterCommand(a.id,h),y=Hn(a.id,g.response,!1,1);g.sender_timestamp&&(y.sender_timestamp=g.sender_timestamp),o(f=>[...f,y])}catch(g){const y=Hn(a.id,`Command failed: ${g instanceof Error?g.message:"Unknown error"}`,!1,1);o(f=>[...f,y])}},[a,l,s,o]);return{repeaterLoggedIn:s,activeContactIsRepeater:l,handleTelemetryRequest:m,handleRepeaterCommand:c}}const bk=12;function _o(a){return a.slice(0,bk)}function Kg(a,r){return!a||!r?!1:_o(a)===_o(r)}function Yn(a,r){return a||_o(r)}const zu="remoteterm-lastMessageTime",qu="remoteterm-lastReadTime";function ol(a){try{const r=localStorage.getItem(a);return r?JSON.parse(r):{}}catch{return{}}}function Yg(a,r){try{localStorage.setItem(a,JSON.stringify(r))}catch{}}function Zm(){return ol(zu)}function fk(){return ol(qu)}function su(a,r){const o=ol(zu);return(!o[a]||r>o[a])&&(o[a]=r,Yg(zu,o)),o}function Zs(a,r){const o=ol(qu);return o[a]=r,Yg(qu,o),o}function fa(a,r){return a==="channel"?`channel-${r}`:`contact-${_o(r)}`}function vk(a,r,o){const[s,u]=E.useState({}),[l,m]=E.useState(Zm),c=E.useRef(new Set),h=E.useRef(new Set);E.useEffect(()=>{const w=a.filter(v=>!c.current.has(v.key)),k=r.filter(v=>v.public_key&&!h.current.has(v.public_key));if(w.length===0&&k.length===0)return;w.forEach(v=>c.current.add(v.key)),k.forEach(v=>h.current.add(v.public_key)),(async()=>{const v=[...w.map(q=>({type:"CHAN",conversation_key:q.key})),...k.map(q=>({type:"PRIV",conversation_key:q.public_key}))];if(v.length!==0)try{const q=await ei.getMessagesBulk(v,100),A=fk(),z={},_={};for(const C of w){const D=q[`CHAN:${C.key}`]||[];if(D.length>0){const F=fa("channel",C.key),R=A[F]||0,M=D.filter(T=>!T.outgoing&&T.received_at>R).length;M>0&&(z[F]=M);const P=Math.max(...D.map(T=>T.received_at));_[F]=P,su(F,P)}}for(const C of k){const D=q[`PRIV:${C.public_key}`]||[];if(D.length>0){const F=fa("contact",C.public_key),R=A[F]||0,M=D.filter(T=>!T.outgoing&&T.received_at>R).length;M>0&&(z[F]=M);const P=Math.max(...D.map(T=>T.received_at));_[F]=P,su(F,P)}}Object.keys(z).length>0&&u(C=>({...C,...z})),m(Zm())}catch(q){console.error("Failed to fetch messages bulk:",q)}})()},[a,r]),E.useEffect(()=>{if(o&&o.type!=="raw"){const w=fa(o.type,o.id),k=Math.floor(Date.now()/1e3);Zs(w,k),u(x=>{if(x[w]){const v={...x};return delete v[w],v}return x})}},[o]);const d=E.useCallback(w=>{u(k=>({...k,[w]:(k[w]||0)+1}))},[]),g=E.useCallback(()=>{const w=Math.floor(Date.now()/1e3);for(const k of a){const x=fa("channel",k.key);Zs(x,w)}for(const k of r)if(k.public_key){const x=fa("contact",k.public_key);Zs(x,w)}u({})},[a,r]),y=E.useCallback(w=>{if(w.type==="raw")return;const k=fa(w.type,w.id),x=Math.floor(Date.now()/1e3);Zs(k,x),u(v=>{if(v[k]){const q={...v};return delete q[k],q}return v})},[]),f=E.useCallback(w=>{let k=null;if(w.type==="CHAN"&&w.conversation_key?k=fa("channel",w.conversation_key):w.type==="PRIV"&&w.conversation_key&&(k=fa("contact",w.conversation_key)),k){const x=w.received_at||Math.floor(Date.now()/1e3),v=su(k,x);m(v)}},[]);return{unreadCounts:s,lastMessageTimes:l,incrementUnread:d,markAllRead:g,markConversationRead:y,trackNewMessage:f}}const Xs=200;function tu(a){return`${a.type}-${a.conversation_key}-${a.text}-${a.sender_timestamp}`}function kk(a){const[r,o]=E.useState([]),[s,u]=E.useState(!1),[l,m]=E.useState(!1),[c,h]=E.useState(!1),d=E.useRef(new Set),g=E.useCallback(async(k=!1)=>{if(!a||a.type==="raw"){o([]),h(!1);return}k&&u(!0);try{const x=await ei.getMessages({type:a.type==="channel"?"CHAN":"PRIV",conversation_key:a.id,limit:Xs});o(x),d.current.clear();for(const v of x)d.current.add(tu(v));h(x.length>=Xs)}catch(x){console.error("Failed to fetch messages:",x)}finally{k&&u(!1)}},[a]),y=E.useCallback(async()=>{if(!(!a||a.type==="raw"||l||!c)){m(!0);try{const k=await ei.getMessages({type:a.type==="channel"?"CHAN":"PRIV",conversation_key:a.id,limit:Xs,offset:r.length});if(k.length>0){o(x=>[...x,...k]);for(const x of k)d.current.add(tu(x))}h(k.length>=Xs)}catch(k){console.error("Failed to fetch older messages:",k)}finally{m(!1)}}},[a,l,c,r.length]);E.useEffect(()=>{g(!0)},[g]);const f=E.useCallback( Line truncated
* @license lucide-react v0.562.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const wk=a=>a.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),xk=a=>a.replace(/^([A-Z])|[\s-_]+(\w)/g,(r,o,s)=>s?s.toUpperCase():o.toLowerCase()),Xm=a=>{const r=xk(a);return r.charAt(0).toUpperCase()+r.slice(1)},Zg=(...a)=>a.filter((r,o,s)=>!!r&&r.trim()!==""&&s.indexOf(r)===o).join(" ").trim(),zk=a=>{for(const r in a)if(r.startsWith("aria-")||r==="role"||r==="title")return!0};/**
* @license lucide-react v0.562.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/var qk={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/**
* @license lucide-react v0.562.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const jk=E.forwardRef(({color:a="currentColor",size:r=24,strokeWidth:o=2,absoluteStrokeWidth:s,className:u="",children:l,iconNode:m,...c},h)=>E.createElement("svg",{ref:h,...qk,width:r,height:r,stroke:a,strokeWidth:s?Number(o)*24/Number(r):o,className:Zg("lucide",u),...!l&&!zk(c)&&{"aria-hidden":"true"},...c},[...m.map(([d,g])=>E.createElement(d,g)),...Array.isArray(l)?l:[l]]));/**
* @license lucide-react v0.562.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const Ou=(a,r)=>{const o=E.forwardRef(({className:s,...u},l)=>E.createElement(jk,{ref:l,iconNode:r,className:Zg(`lucide-${wk(Xm(a))}`,`lucide-${a}`,s),...u}));return o.displayName=Xm(a),o};/**
* @license lucide-react v0.562.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const Ak=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],Ck=Ou("check",Ak);/**
* @license lucide-react v0.562.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const Ek=[["path",{d:"M4 5h16",key:"1tepv9"}],["path",{d:"M4 12h16",key:"1lakjw"}],["path",{d:"M4 19h16",key:"1djgab"}]],Sk=Ou("menu",Ek);/**
* @license lucide-react v0.562.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const Bk=[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]],Xg=Ou("x",Bk);var Jg=Gg();const ey=Ro(Jg);function _k(a){if(typeof document>"u")return;let r=document.head||document.getElementsByTagName("head")[0],o=document.createElement("style");o.type="text/css",r.appendChild(o),o.styleSheet?o.styleSheet.cssText=a:o.appendChild(document.createTextNode(a))}const Dk=a=>{switch(a){case"success":return Tk;case"info":return Nk;case"warning":return Pk;case"error":return Ik;default:return null}},Fk=Array(12).fill(0),Rk=({visible:a,className:r})=>pe.createElement("div",{className:["sonner-loading-wrapper",r].filter(Boolean).join(" "),"data-visible":a},pe.createElement("div",{className:"sonner-spinner"},Fk.map((o,s)=>pe.createElement("div",{className:"sonner-loading-bar",key:`spinner-bar-${s}`})))),Tk=pe.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",fill:"currentColor",height:"20",width:"20"},pe.createElement("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z",clipRule:"evenodd"})),Pk=pe.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor",height:"20",width:"20"},pe.createElement("path",{fillRule:"evenodd",d:"M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z",clipRule:"evenodd"})),Nk=pe.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",fill:"currentColor",height:"20",width:"20"},pe.createElement("path",{fillRule:"evenodd",d:"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z",clipRule:"evenodd"})),Ik=pe.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",fill:"currentColor",height:"20",width:"20"},pe.createElement("path",{fillRule:"evenodd",d:"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z",clipRule:"evenodd"})),Mk=pe.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"},pe.createElement("line",{x1:"18",y1:"6",x2:"6",y2:"18"}),pe.createElement("line",{x1:"6",y1:"6",x2:"18",y2:"18"})),Ok=()=>{const[a,r]=pe.useState(document.hidden);return pe.useEffect(()=>{const o=()=>{r(document.hidden)};return document.addEventListener("visibilitychange",o),()=>window.removeEventListener("visibilitychange",o)},[]),a};let ju=1;class Lk{constructor(){this.subscribe=r=>(this.subscribers.push(r),()=>{const o=this.subscribers.indexOf(r);this.subscribers.splice(o,1)}),this.publish=r=>{this.subscribers.forEach(o=>o(r))},this.addToast=r=>{this.publish(r),this.toasts=[...this.toasts,r]},this.create=r=>{var o;const{message:s,...u}=r,l=typeof(r==null?void 0:r.id)=="number"||((o=r.id)==null?void 0:o.length)>0?r.id:ju++,m=this.toasts.find(h=>h.id===l),c=r.dismissible===void 0?!0:r.dismissible;return this.dismissedToasts.has(l)&&this.dismissedToasts.delete(l),m?this.toasts=this.toasts.map(h=>h.id===l?(this.publish({...h,...r,id:l,title:s}),{...h,...r,id:l,dismissible:c,title:s}):h):this.addToast({title:s,...u,dismissible:c,id:l}),l},this.dismiss=r=>(r?(this.dismissedToasts.add(r),requestAnimationFrame(()=>this.subscribers.forEach(o=>o({id:r,dismiss:!0})))):this.toasts.forEach(o=>{this.subscribers.forEach(s=>s({id:o.id,dismiss:!0}))}),r),this.message=(r,o)=>this.create({...o,message:r}),this.error=(r,o)=>this.create({...o,message:r,type:"error"}),this.success=(r,o)=>this.create({...o,type:"success",message:r}),this.info=(r,o)=>this.create({...o,type:"info",message:r}),this.warning=(r,o)=>this.create({...o,type:"warning",message:r}),this.loading=(r,o)=>this.create({...o,type:"loading",message:r}),this.promise=(r,o)=>{if(!o)return;let s;o.loading!==void 0&&(s=this.create({...o,promise:r,type:"loading",message:o.loading,description:typeof o.description!="function"?o.description:void 0}));const u=Promise.resolve(r instanceof Function?r():r);let l=s!==void 0,m;const c=u.then(async d=>{if(m=["resolve",d],pe.isValidElement(d))l=!1,this.create({id:s,type:"default",message:d});else if(Vk(d)&&!d.ok){l=!1;const y=typeof o.error=="function"?await o.error(`HTTP error! status: ${d.status}`):o.error,f=typeof o.description=="function"?await o.description(`HTTP error! status: ${d.status}`):o.description,k=typeof y=="object"&&!pe.isValidElement(y)?y:{message:y};this.create({id:s,type:"error",description:f,...k})}else if(d instanceof Error){l=!1;const y=typeof o.error=="function"?await o.error(d):o.error,Line truncated
`).map((j,I,L)=>B.jsxs("span",{children:[px(j,c),I<L.length-1&&B.jsx("br",{})]},I)),!Z&&B.jsx("span",{className:"text-[10px] text-muted-foreground/50 ml-2",children:uh(C.sender_timestamp||C.received_at)}),C.outgoing&&(C.acked>0?` ✓${C.acked>1?C.acked:""}`:" ?")]})]})]},C.id)})]}),y&&B.jsx("button",{onClick:x,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"})})})]})}const hx=E.forwardRef(function({onSend:r,disabled:o,placeholder:s,isRepeaterMode:u},l){const[m,c]=E.useState(""),[h,d]=E.useState(!1),g=E.useRef(null);E.useImperativeHandle(l,()=>({appendText:k=>{var x;c(v=>v+k),(x=g.current)==null||x.focus()}}));const y=E.useCallback(async k=>{k.preventDefault();const x=m.trim();if(u){if(h||o)return;const v=x==="."?"":x;d(!0);try{await r(v),c("")}catch(q){console.error("Failed to request telemetry:",q);return}finally{d(!1)}setTimeout(()=>{var q;return(q=g.current)==null?void 0:q.focus()},0)}else{if(!x||h||o)return;d(!0);try{await r(x),c("")}catch(v){console.error("Failed to send message:",v);return}finally{d(!1)}setTimeout(()=>{var v;return(v=g.current)==null?void 0:v.focus()},0)}},[m,h,o,r,u]),f=E.useCallback(k=>{k.key==="Enter"&&!k.shiftKey&&(k.preventDefault(),y(k))},[y]),w=u?m.trim().length>0||m===".":m.trim().length>0;return B.jsxs("form",{className:"px-4 py-3 border-t border-border flex gap-2",onSubmit:y,children:[B.jsx(qi,{ref:g,type:u?"password":"text",value:m,onChange:k=>c(k.target.value),onKeyDown:f,placeholder:s||(u?"Enter password (or . for none)...":"Type a message..."),disabled:o||h,className:"flex-1"}),B.jsx(Ha,{type:"submit",disabled:o||h||!w,children:h?u?"Fetching...":"Sending...":u?"Fetch":"Send"})]})});function Si(a,r,{checkForDefaultPrevented:o=!0}={}){return function(u){if(a==null||a(u),o===!1||!u.defaultPrevented)return r==null?void 0:r(u)}}function gx(a,r){const o=E.createContext(r),s=l=>{const{children:m,...c}=l,h=E.useMemo(()=>c,Object.values(c));return B.jsx(o.Provider,{value:h,children:m})};s.displayName=a+"Provider";function u(l){const m=E.useContext(o);if(m)return m;if(r!==void 0)return r;throw new Error(`\`${l}\` must be used within \`${a}\``)}return[s,u]}function Hr(a,r=[]){let o=[];function s(l,m){const c=E.createContext(m),h=o.length;o=[...o,m];const d=y=>{var q;const{scope:f,children:w,...k}=y,x=((q=f==null?void 0:f[a])==null?void 0:q[h])||c,v=E.useMemo(()=>k,Object.values(k));return B.jsx(x.Provider,{value:v,children:w})};d.displayName=l+"Provider";function g(y,f){var x;const w=((x=f==null?void 0:f[a])==null?void 0:x[h])||c,k=E.useContext(w);if(k)return k;if(m!==void 0)return m;throw new Error(`\`${y}\` must be used within \`${l}\``)}return[d,g]}const u=()=>{const l=o.map(m=>E.createContext(m));return function(c){const h=(c==null?void 0:c[a])||l;return E.useMemo(()=>({[`__scope${a}`]:{...c,[a]:h}}),[c,h])}};return u.scopeName=a,[s,yx(u,...r)]}function yx(...a){const r=a[0];if(a.length===1)return r;const o=()=>{const s=a.map(u=>({useScope:u(),scopeName:u.scopeName}));return function(l){const m=s.reduce((c,{useScope:h,scopeName:d})=>{const y=h(l)[`__scope${d}`];return{...c,...y}},{});return E.useMemo(()=>({[`__scope${r.scopeName}`]:m}),[m])}};return o.scopeName=r.scopeName,o}var Do=globalThis!=null&&globalThis.document?E.useLayoutEffect:()=>{},bx=Mu[" useId ".trim().toString()]||(()=>{}),fx=0;function Nr(a){const[r,o]=E.useState(bx());return Do(()=>{o(s=>s??String(fx++))},[a]),r?`radix-${r}`:""}var vx=Mu[" useInsertionEffect ".trim().toString()]||Do;function sl({prop:a,defaultProp:r,onChange:o=()=>{},caller:s}){const[u,l,m]=kx({defaultProp:r,onChange:o}),c=a!==void 0,h=c?a:u;{const g=E.useRef(a!==void 0);E.useEffect(()=>{const y=g.current;y!==c&&console.warn(`${s} is changing from ${y?"controlled":"uncontrolled"} to ${c?"controlled":"uncontrolled"}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`),g.current=c},[c,s])}const d=E.useCallback(g=>{var y;if(c){const f=wx(g)?g(a):g;f!==a&&((y=m.current)==null||y.call(m,f))}else l(g)},[c,a,l,m]);return[h,d]}function kx({defaultProp:a,onChange:r}){const[o,s]=E.useState(a),u=E.useRef(o),l=E.useRef(r);return vx(()=>{l.current=r},[r]),E.useEffect(()=>{var m;u.current!==o&&((m=l.current)==null||m.call(l,o),u.current=o)},[o,u]),[o,s,l]}function wx(a){return typeof a=="function"}function xx(a){const r=zx(a),o=E.forwardRef((s,u)=>{const{children:l,...m}=s,c=E.Children.toArray(l),h=c.find(jx);if(h){const d=h.props.children,g=c.map(y=>y===h?E.Children.count(d)>1?E.Children.only(null):E.isValidElement(d)?d.prLine truncated
.`.concat(Zx,` {
overflow: hidden `).concat(s,`;
padding-right: `).concat(c,"px ").concat(s,`;
}
body[`).concat(So,`] {
overflow: hidden `).concat(s,`;
overscroll-behavior: contain;
`).concat([r&&"position: relative ".concat(s,";"),o==="margin"&&`
padding-left: `.concat(u,`px;
padding-top: `).concat(l,`px;
padding-right: `).concat(m,`px;
margin-left:0;
margin-top:0;
margin-right: `).concat(c,"px ").concat(s,`;
`),o==="padding"&&"padding-right: ".concat(c,"px ").concat(s,";")].filter(Boolean).join(""),`
}
.`).concat(lt,` {
right: `).concat(c,"px ").concat(s,`;
}
.`).concat(ct,` {
margin-right: `).concat(c,"px ").concat(s,`;
}
.`).concat(lt," .").concat(lt,` {
right: 0 `).concat(s,`;
}
.`).concat(ct," .").concat(ct,` {
margin-right: 0 `).concat(s,`;
}
body[`).concat(So,`] {
`).concat(Xx,": ").concat(c,`px;
}
`)},vh=function(){var a=parseInt(document.body.getAttribute(So)||"0",10);return isFinite(a)?a:0},bz=function(){E.useEffect(function(){return document.body.setAttribute(So,(vh()+1).toString()),function(){var a=vh()-1;a<=0?document.body.removeAttribute(So):document.body.setAttribute(So,a.toString())}},[])},fz=function(a){var r=a.noRelative,o=a.noImportant,s=a.gapMode,u=s===void 0?"margin":s;bz();var l=E.useMemo(function(){return hz(u)},[u]);return E.createElement(gz,{styles:yz(l,!r,u,o?"":"!important")})},Su=!1;if(typeof window<"u")try{var nt=Object.defineProperty({},"passive",{get:function(){return Su=!0,!0}});window.addEventListener("test",nt,nt),window.removeEventListener("test",nt,nt)}catch{Su=!1}var jo=Su?{passive:!1}:!1,vz=function(a){return a.tagName==="TEXTAREA"},Cy=function(a,r){if(!(a instanceof Element))return!1;var o=window.getComputedStyle(a);return o[r]!=="hidden"&&!(o.overflowY===o.overflowX&&!vz(a)&&o[r]==="visible")},kz=function(a){return Cy(a,"overflowY")},wz=function(a){return Cy(a,"overflowX")},kh=function(a,r){var o=r.ownerDocument,s=r;do{typeof ShadowRoot<"u"&&s instanceof ShadowRoot&&(s=s.host);var u=Ey(a,s);if(u){var l=Sy(a,s),m=l[1],c=l[2];if(m>c)return!0}s=s.parentNode}while(s&&s!==o.body);return!1},xz=function(a){var r=a.scrollTop,o=a.scrollHeight,s=a.clientHeight;return[r,o,s]},zz=function(a){var r=a.scrollLeft,o=a.scrollWidth,s=a.clientWidth;return[r,o,s]},Ey=function(a,r){return a==="v"?kz(r):wz(r)},Sy=function(a,r){return a==="v"?xz(r):zz(r)},qz=function(a,r){return a==="h"&&r==="rtl"?-1:1},jz=function(a,r,o,s,u){var l=qz(a,window.getComputedStyle(r).direction),m=l*s,c=o.target,h=r.contains(c),d=!1,g=m>0,y=0,f=0;do{if(!c)break;var w=Sy(a,c),k=w[0],x=w[1],v=w[2],q=x-v-l*k;(k||q)&&Ey(a,c)&&(y+=q,f+=k);var A=c.parentNode;c=A&&A.nodeType===Node.DOCUMENT_FRAGMENT_NODE?A.host:A}while(!h&&c!==document.body||h&&(r.contains(c)||r===c));return(g&&Math.abs(y)<1||!g&&Math.abs(f)<1)&&(d=!0),d},ot=function(a){return"changedTouches"in a?[a.changedTouches[0].clientX,a.changedTouches[0].clientY]:[0,0]},wh=function(a){return[a.deltaX,a.deltaY]},xh=function(a){return a&&"current"in a?a.current:a},Az=function(a,r){return a[0]===r[0]&&a[1]===r[1]},Cz=function(a){return`
.block-interactivity-`.concat(a,` {pointer-events: none;}
.allow-interactivity-`).concat(a,` {pointer-events: all;}
`)},Ez=0,Ao=[];function Sz(a){var r=E.useRef([]),o=E.useRef([0,0]),s=E.useRef(),u=E.useState(Ez++)[0],l=E.useState(Ay)[0],m=E.useRef(a);E.useEffect(function(){m.current=a},[a]),E.useEffect(function(){if(a.inert){document.body.classList.add("block-interactivity-".concat(u));var x=Yx([a.lockRef.current],(a.shards||[]).map(xh),!0).filter(Boolean);return x.forEach(function(v){return v.classList.add("allow-interactivity-".concat(u))}),function(){document.body.classList.remove("block-interactivity-".concat(u)),x.forEach(function(v){return v.classList.remove("allow-interactivity-".concat(u))})}}},[a.inert,a.lockRef.current,a.shards]);var c=E.useCallback(function(x,v){if("touches"in x&&x.touches.length===2||x.type==="wheel"&&x.ctrlKey)return!m.current.allowPinchZoom;var q=ot(x),A=o.current,z="deltaX"in x?x.deltaX:A[0]-q[0],_="deltaY"in x?x.deltaY:A[1]-q[1],C,D=x.target,F=Math.abs(z)>Math.abs(_)?"h":"v";if("touches"in x&&F==="h"&&D.type==="range")return!1;var R=window.getSelection(),M=R&&R.anchorNode,P=M?M===D||M.contains(D):!1;if(P)return!1;var T=kh(F,D);if(!T)return!0;if(T?C=F:(C=F==="v"?"h":"v",T=kh(F,D)),!T)return!1;if(!s.current&&"changedTouches"in x&&(z||_)&&(s.current=C),!C)return!0;var U=s.current||C;return jz(U,v,x,U==="h"?z:_)},[]),h=E.useCallback(function(x){var v=x;if(!(!Ao.length||Ao[Ao.length-1]!==l)){var q="deltaY"in v?wh(v):ot(v),A=r.current.filter(function(C){return C.name===v.type&&(C.target===v.target||v.target===C.shadowParent)&&Az(C.delta,q)})[0];if(A&&A.should){v.cancelable&&v.preventDefault();return}if(!A){var z=(m.current.shards||[]).map(xh).filter(Boolean).filter(function(C){return C.contains(v.target)}),_=z.length>0?c(v,z[0]):!m.current.noIsolation;_&&v.cancelable&&v.preventDefault()}}},[]),d=E.useCallback(function(x,v,q,A){var z={name:x,delta:v,target:q,should:A,shadowParent:Bz(q)};r.current.push(z),setTimeout(function(){r.current=r.current.filter(function(_){return _!==z})},1)},[]),g=E.useCallback(function(x){o.current=ot(x),s.current=void 0},[]),y=E.useCallback(function(x){d(x.type,wh(x),x.target,c(x,a.lockRef.current))},[]),f=E.useCallback(function(x){d(x.type,ot(x),x.target,c(x,a.lockRef.current))},[]);E.useEffect(function(){return Ao.push(l),a.setCallbacks({onScrollCapture:y,onWheelCapture:y,onTouchMoveCapture:f}),document.addEventListener("wheel",h,jo),document.addEventListener("touchmove",h,jo),document.addEventListener("touchstart",g,jo),function(){Ao=Ao.filter(function(x){return x!==l}),document.removeEventListener("wheel",h,jo),document.removeEventListener("touchmove",h,jo),document.removeEventListener("touchstart",g,jo)}},[]);var w=a.removeScrollBar,k=a.inert;return E.createElement(E.Fragment,null,k?E.createElement(l,{styles:Cz(u)}):null,w?E.createElement(fz,{noRelative:a.noRelative,gapMode:a.gapMode}):null)}function Bz(a){for(var r=null;a!==null;)a instanceof ShadowRoot&&(r=a.host,a=a.host),a=a.parentNode;return r}const _z=rz(jy,Sz);var By=E.forwardRef(function(a,r){return E.createElement(tl,La({},a,{ref:r,sideCar:_z}))});By.classNames=tl.classNames;var Dz=function(a){if(typeof document>"u")return null;var r=Array.isArray(a)?a[0]:a;return r.ownerDocument.body},Co=new WeakMap,rt=new WeakMap,st={},yu=0,_y=function(a){return a&&(a.host||_y(a.parentNode))},Fz=function(a,r){return r.map(function(o){if(a.contains(o))return o;var s=_y(o);return s&&a.contains(s)?s:(console.error("aria-hidden",o,"in not contained inside",a,". Doing nothing"),null)}).filter(function(o){return!!o})},Rz=function(a,r,o,s){var u=Fz(r,Array.isArray(a)?a:[a]);st[o]||(st[o]=new WeakMap);var l=st[o],m=[],c=new Set,h=new Set(u),d=function(y){!y||c.has(y)||(c.add(y),d(y.parentNode))};u.forEach(d);var g=function(y){!y||h.has(y)||Array.prototype.forEach.call(y.children,function(f){if(c.has(f))g(f);else try{var w=f.getAttribute(s),k=w!==null&&w!=="false",x=(Co.get(f)||0)+1,v=(l.get(f)||0)+1;Co.set(f,x),l.set(f,v),m.push(f),x===1&&k&&rt.set(f,!0),v===1&&f.setAttribute(o,"true"),k||f.setAttribute(s,"true")}catch(q){console.error("aria-hidden: cannot operate on ",f,q)}})};return g(r),c.clear(),yu++,function(){m.forEach(function(y){var f=Co.get(y)-1,w=l.get(y)-1;Co.set(y,f),l.set(y,w),f||(rt.has(y)||y.removeAttribute(s),rt.delete(y)),w||y.removeAttribute(o)}),yu--,yu||(Co=new WeakMap,Co=new WeakMap,rt=new WeakMap,st={})}},Tz=function(a,r,o){o===void 0&&(o="data-aria-hidden");var s=Array.from(Array.isArray(a)?a:[a]),u=Dz(a);return u?(s.push.apply(s,Array.from(u.querySelectorAll("[aria-live], script"))),Rz(s,u,o,"aria-hidden")):function(){return null}};function Pz(a){const r=Nz(a),o=E.forwardRef((s,u)=>{const{children:l,...m}=s,c=E.Children.toArray(l),h=c.find(Mz);if(h){const d=h.props.children,g=c.map(y=>y===h?E.Children.count(d)>1?E.Children.only(null):E.isValidElement(d)?d.props.children:null:y);return B.jsx(r,{...m,ref:u,children:E.isValidElement(d)?E.cloneElement(d,void 0,g):null})}return B.jsx(r,{...m,ref:u,children:l})});return o.displayName=`${a}.Slot`,o}function Nz(a){const r=E.forwardRef((o,s)=>{const{children:u Line truncated
If you want to hide the \`${r.titleName}\`, you can wrap it with our VisuallyHidden component.
For more information, see https://radix-ui.com/primitives/docs/components/${r.docsSlug}`;return E.useEffect(()=>{a&&(document.getElementById(a)||console.error(o))},[o,a]),null},Yz="DialogDescriptionWarning",Zz=({contentRef:a,descriptionId:r})=>{const s=`Warning: Missing \`Description\` or \`aria-describedby={undefined}\` for {${$y(Yz).contentName}}.`;return E.useEffect(()=>{var l;const u=(l=a.current)==null?void 0:l.getAttribute("aria-describedby");r&&u&&(document.getElementById(r)||console.warn(s))},[s,a,r]),null},Gy=Fy,Qy=Py,cl=Ny,ul=Iy,dl=Oy,pl=Hy,Ky=Uy;const Yy=Gy,Xz=Qy,Zy=E.forwardRef(({className:a,...r},o)=>B.jsx(cl,{ref:o,className:$e("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",a),...r}));Zy.displayName=cl.displayName;const $u=E.forwardRef(({className:a,children:r,...o},s)=>B.jsxs(Xz,{children:[B.jsx(Zy,{}),B.jsxs(ul,{ref:s,className:$e("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",a),...o,children:[r,B.jsxs(Ky,{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(Xg,{className:"h-4 w-4"}),B.jsx("span",{className:"sr-only",children:"Close"})]})]})]}));$u.displayName=ul.displayName;const Gu=({className:a,...r})=>B.jsx("div",{className:$e("flex flex-col space-y-1.5 text-center sm:text-left",a),...r});Gu.displayName="DialogHeader";const Qu=({className:a,...r})=>B.jsx("div",{className:$e("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",a),...r});Qu.displayName="DialogFooter";const Ku=E.forwardRef(({className:a,...r},o)=>B.jsx(dl,{ref:o,className:$e("text-lg font-semibold leading-none tracking-tight",a),...r}));Ku.displayName=dl.displayName;const Jz=E.forwardRef(({className:a,...r},o)=>B.jsx(pl,{ref:o,className:$e("text-sm text-muted-foreground",a),...r}));Jz.displayName=pl.displayName;function zh(a){const r=e0(a),o=E.forwardRef((s,u)=>{const{children:l,...m}=s,c=E.Children.toArray(l),h=c.find(a0);if(h){const d=h.props.children,g=c.map(y=>y===h?E.Children.count(d)>1?E.Children.only(null):E.isValidElement(d)?d.props.children:null:y);return B.jsx(r,{...m,ref:u,children:E.isValidElement(d)?E.cloneElement(d,void 0,g):null})}return B.jsx(r,{...m,ref:u,children:l})});return o.displayName=`${a}.Slot`,o}function e0(a){const r=E.forwardRef((o,s)=>{const{children:u,...l}=o;if(E.isValidElement(u)){const m=o0(u),c=n0(l,u.props);return u.type!==E.Fragment&&(c.ref=s?Lr(s,m):m),E.cloneElement(u,c)}return E.Children.count(u)>1?E.Children.only(null):null});return r.displayName=`${a}.SlotClone`,r}var i0=Symbol("radix.slottable");function a0(a){return E.isValidElement(a)&&typeof a.type=="function"&&"__radixId"in a.type&&a.type.__radixId===i0}function n0(a,r){const o={...r};for(const s in r){const u=a[s],l=r[s];/^on[A-Z]/.test(s)?u&&l?o[s]=(...c)=>{const h=l(...c);return u(...c),h}:u&&(o[s]=u):s==="style"?o[s]={...u,...l}:s==="className"&&(o[s]=[u,l].filter(Boolean).join(" "))}return{...a,...o}}function o0(a){var s,u;let r=(s=Object.getOwnPropertyDescriptor(a.props,"ref"))==null?void 0:s.get,o=r&&"isReactWarning"in r&&r.isReactWarning;return o?a.ref:(r=(u=Object.getOwnPropertyDescriptor(a,"ref"))==null?void 0:u.get,o=r&&"isReactWarning"in r&&r.isReactWarning,o?a.props.ref:a.props.ref||a.ref)}function r0(a){const r=a+"CollectionProvider",[o,s]=Hr(r),[u,l]=o(r,{collectionRef:{current:null},itemMap:new Map}),m=x=>{const{scope:v,children:q}=x,A=pe.useRef(null),z=pe.useRef(new Map).current;return B.jsx(u,{scope:v,itemMap:z,collectionRef:A,children:q})};m.displayName=r;const c=a+"CollectionSlot",h=zh(c),d=pe.forwardRef((x,v)=>{const{scope:q,children:A}=x,z=l(c,q),_=va(v,z.collectionRef);return B.jsx(h,{ref:_,children:A})});d.displayName=c;const g=a+"CollectionItemSlot",y="data-radix-collection-item",f=zh(g),w=pe.forwardRef((x,v)=>{const{scope:q,children:A,...z}=x,_=pe.useRef(null),C=va(v,_),D=l(g,q);return pe.useEffect(()=>(D.itemMap.set(_,{ref:_,...z}),()=>void D.itemMap.delete(_))),B.jsx(f,{[y]:"",ref:C,children:A})});w.displayName=g;function k(x){const v=l(a+"CollectionConsumer",x);return pe.useCallback(()=>{const A=v.collectionRef.current;if(!A)return[];const z=Array.from(A.querySelectorAll(`[${y}]`));return Array.from(v.itemMap.values()).sort((D,F)=>z.indexOf(D.ref.current)-z.indexOf(F.ref.current))},[v.coll Line 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:
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/return(function(s){var u=o,l=u.lib,m=l.WordArray,c=l.Hasher,h=u.algo,d=m.create([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13]),g=m.create([5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11]),y=m.create([11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6]),f=m.create([8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11]),w=m.create([0,1518500249,1859775393,2400959708,2840853838]),k=m.create([1352829926,1548603684,1836072691,2053994217,0]),x=h.RIPEMD160=c.extend({_doReset:function(){this._hash=m.create([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(D,F){for(var R=0;R<16;R++){var M=F+R,P=D[M];D[M]=(P<<8|P>>>24)&16711935|(P<<24|P>>>8)&4278255360}var T=this._hash.words,U=w.words,H=k.words,K=d.words,ie=g.words,Z=y.words,ae=f.words,W,ne,X,j,I,L,$,le,me,ge;L=W=T[0],$=ne=T[1],le=X=T[2],me=j=T[3],ge=I=T[4];for(var oe,R=0;R<80;R+=1)oe=W+D[F+K[R]]|0,R<16?oe+=v(ne,X,j)+U[0]:R<32?oe+=q(ne,X,j)+U[1]:R<48?oe+=A(ne,X,j)+U[2]:R<64?oe+=z(ne,X,j)+U[3]:oe+=_(ne,X,j)+U[4],oe=oe|0,oe=C(oe,Z[R]),oe=oe+I|0,W=I,I=j,j=C(X,10),X=ne,ne=oe,oe=L+D[F+ie[R]]|0,R<16?oe+=_($,le,me)+H[0]:R<32?oe+=z($,le,me)+H[1]:R<48?oe+=A($,le,me)+H[2]:R<64?oe+=q($,le,me)+H[3]:oe+=v($,le,me)+H[4],oe=oe|0,oe=C(oe,ae[R]),oe=oe+ge|0,L=ge,ge=me,me=C(le,10),le=$,$=oe;oe=T[1]+X+me|0,T[1]=T[2]+j+ge|0,T[2]=T[3]+I+L|0,T[3]=T[4]+W+$|0,T[4]=T[0]+ne+le|0,T[0]=oe},_doFinalize:function(){var D=this._data,F=D.words,R=this._nDataBytes*8,M=D.sigBytes*8;F[M>>>5]|=128<<24-M%32,F[(M+64>>>9<<4)+14]=(R<<8|R>>>24)&16711935|(R<<24|R>>>8)&4278255360,D.sigBytes=(F.length+1)*4,this._process();for(var P=this._hash,T=P.words,U=0;U<5;U++){var H=T[U];T[U]=(H<<8|H>>>24)&16711935|(H<<24|H>>>8)&4278255360}return P},clone:function(){var D=c.clone.call(this);return D._hash=this._hash.clone(),D}});function v(D,F,R){return D^F^R}function q(D,F,R){return D&F|~D&R}function A(D,F,R){return(D|~F)^R}function z(D,F,R){return D&R|F&~R}function _(D,F,R){return D^(F|~R)}function C(D,F){return D<<F|D>>>32-F}u.RIPEMD160=c._createHelper(x),u.HmacRIPEMD160=c._createHmacHelper(x)})(),o.RIPEMD160})})(jt)),jt.exports}var At={exports:{}},gq=At.exports,Vh;function yl(){return Vh||(Vh=1,(function(a,r){(function(o,s){a.exports=s(Me())})(gq,function(o){(function(){var s=o,u=s.lib,l=u.Base,m=s.enc,c=m.Utf8,h=s.algo;h.HMAC=l.extend({init:function(d,g){d=this._hasher=new d.init,typeof g=="string"&&(g=c.parse(g));var y=d.blockSize,f=y*4;g.sigBytes>f&&(g=d.finalize(g)),g.clamp();for(var w=this._oKey=g.clone(),k=this._iKey=g.clone(),x=w.words,v=k.words,q=0;q<y;q++)x[q]^=1549556828,v[q]^=909522486;w.sigBytes=k.sigBytes=f,this.reset()},reset:function(){var d=this._hasher;d.reset(),d.update(this._iKey)},update:function(d){return this._hasher.update(d),this},finalize:function(d){var g=this._hasher,y=g.finalize(d);g.reset();var f=g.finalize(this._oKey.clone().concat(y));return f}})})()})})(At)),At.exports}var Ct={exports:{}},yq=Ct.exports,Uh;function bq(){return Uh||(Uh=1,(function(a,r){(function(o,s,u){a.exports=s(Me(),Ur(),yl())})(yq,function(o){return(function(){var s=o,u=s.lib,l=u.Base,m=u.WordArray,c=s.algo,h=c.SHA256,d=c.HMAC,g=c.PBKDF2=l.extend({cfg:l.extend({keySize:128/32,hasher:h,iterations:25e4}),init:function(y){this.cfg=this.cfg.extend(y)},compute:function(y,f){for(var w=this.cfg,k=d.create(w.hasher,y),x=m.create(),v=m.create([1]),q=x.words,A=v.words,z=w.keySize,_=w.iterations;q.length<z;){var C=k.update(f).finalize(v);k.reset();for(var D=C.words,F=D.length,R=C,M=1;M<_;M++){R=k.finalize(R),k.reset();for(var P=R.words,T=0;T<F;T++)D[T]^=P[T]}x.concat(C),A[0]++}return x.sigBytes=z*4,x}});s.PBKDF2=function(y,f,w){return g.create(w).compute(y,f)}})(),o.PBKDF2})})(Ct)),Ct.exports}var Et={exports:{}},fq=Et.exports,Wh;function Cn(){return Wh||(Wh=1,(function(a,r){(function(o,s,u){a.exports=s(Me(),Fb(),yl())})(fq,function(o){return(function(){var s=o,u=s.lib,l=u.Base,m=u.WordArray,c=s.algo,h=c.MD5,d=c.EvpKDF=l.extend({cfg:l.extend({keySize:128/32,hasher:h,iterations:1}),init:function(g){this.cfg=this.cfg.extend(g)},compute:function(g,y){for(var f,w=this.cfg,k=w.hasher.create(),x=m.create(),v=x.words,q=w.keySize,A=w.iterations;v.length<q;){f&&k.update(f),f=k.update(g).finalize(y),k.reset();for(var z=1;z<A;z++)f=k.finalize(f),k.reset();x.concat(f)}return x.sigBytes=q*4,x}});s.EvpKDF=function(g,y,f){return d.create(f).compute(g,y)}})(),o.EvpKDF})})(Et)),Et.exports}var St={exports:{}},vq=St.exports,$h;function Ri(Line truncated
* 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 s=o.lib.BlockCipherMode.extend();function u(c){if((c>>24&255)===255){var h=c>>16&255,d=c>>8&255,g=c&255;h===255?(h=0,d===255?(d=0,g===255?g=0:++g):++d):++h,c=0,c+=h<<16,c+=d<<8,c+=g}else c+=1<<24;return c}function l(c){return(c[0]=u(c[0]))===0&&(c[1]=u(c[1])),c}var m=s.Encryptor=s.extend({processBlock:function(c,h){var d=this._cipher,g=d.blockSize,y=this._iv,f=this._counter;y&&(f=this._counter=y.slice(0),this._iv=void 0),l(f);var w=f.slice(0);d.encryptBlock(w,0);for(var k=0;k<g;k++)c[h+k]^=w[k]}});return s.Decryptor=m,s})(),o.mode.CTRGladman})})(Dt)),Dt.exports}var Ft={exports:{}},Aq=Ft.exports,Yh;function Cq(){return Yh||(Yh=1,(function(a,r){(function(o,s,u){a.exports=s(Me(),Ri())})(Aq,function(o){return o.mode.OFB=(function(){var s=o.lib.BlockCipherMode.extend(),u=s.Encryptor=s.extend({processBlock:function(l,m){var c=this._cipher,h=c.blockSize,d=this._iv,g=this._keystream;d&&(g=this._keystream=d.slice(0),this._iv=void 0),c.encryptBlock(g,0);for(var y=0;y<h;y++)l[m+y]^=g[y]}});return s.Decryptor=u,s})(),o.mode.OFB})})(Ft)),Ft.exports}var Rt={exports:{}},Eq=Rt.exports,Zh;function Sq(){return Zh||(Zh=1,(function(a,r){(function(o,s,u){a.exports=s(Me(),Ri())})(Eq,function(o){return o.mode.ECB=(function(){var s=o.lib.BlockCipherMode.extend();return s.Encryptor=s.extend({processBlock:function(u,l){this._cipher.encryptBlock(u,l)}}),s.Decryptor=s.extend({processBlock:function(u,l){this._cipher.decryptBlock(u,l)}}),s})(),o.mode.ECB})})(Rt)),Rt.exports}var Tt={exports:{}},Bq=Tt.exports,Xh;function _q(){return Xh||(Xh=1,(function(a,r){(function(o,s,u){a.exports=s(Me(),Ri())})(Bq,function(o){return o.pad.AnsiX923={pad:function(s,u){var l=s.sigBytes,m=u*4,c=m-l%m,h=l+c-1;s.clamp(),s.words[h>>>2]|=c<<24-h%4*8,s.sigBytes+=c},unpad:function(s){var u=s.words[s.sigBytes-1>>>2]&255;s.sigBytes-=u}},o.pad.Ansix923})})(Tt)),Tt.exports}var Pt={exports:{}},Dq=Pt.exports,Jh;function Fq(){return Jh||(Jh=1,(function(a,r){(function(o,s,u){a.exports=s(Me(),Ri())})(Dq,function(o){return o.pad.Iso10126={pad:function(s,u){var l=u*4,m=l-s.sigBytes%l;s.concat(o.lib.WordArray.random(m-1)).concat(o.lib.WordArray.create([m<<24],1))},unpad:function(s){var u=s.words[s.sigBytes-1>>>2]&255;s.sigBytes-=u}},o.pad.Iso10126})})(Pt)),Pt.exports}var Nt={exports:{}},Rq=Nt.exports,eg;function Tq(){return eg||(eg=1,(function(a,r){(function(o,s,u){a.exports=s(Me(),Ri())})(Rq,function(o){return o.pad.Iso97971={pad:function(s,u){s.concat(o.lib.WordArray.create([2147483648],1)),o.pad.ZeroPadding.pad(s,u)},unpad:function(s){o.pad.ZeroPadding.unpad(s),s.sigBytes--}},o.pad.Iso97971})})(Nt)),Nt.exports}var It={exports:{}},Pq=It.exports,ig;function Nq(){return ig||(ig=1,(function(a,r){(function(o,s,u){a.exports=s(Me(),Ri())})(Pq,function(o){return o.pad.ZeroPadding={pad:function(s,u){var l=u*4;s.clamp(),s.sigBytes+=l-(s.sigBytes%l||l)},unpad:function(s){for(var u=s.words,l=s.sigBytes-1,l=s.sigBytes-1;l>=0;l--)if(u[l>>>2]>>>24-l%4*8&255){s.sigBytes=l+1;break}}},o.pad.ZeroPadding})})(It)),It.exports}var Mt={exports:{}},Iq=Mt.exports,ag;function Mq(){return ag||(ag=1,(function(a,r){(function(o,s,u){a.exports=s(Me(),Ri())})(Iq,function(o){return o.pad.NoPadding={pad:function(){},unpad:function(){}},o.pad.NoPadding})})(Mt)),Mt.exports}var Ot={exports:{}},Oq=Ot.exports,ng;function Lq(){return ng||(ng=1,(function(a,r){(function(o,s,u){a.exports=s(Me(),Ri())})(Oq,function(o){return(function(s){var u=o,l=u.lib,m=l.CipherParams,c=u.enc,h=c.Hex,d=u.format;d.Hex={stringify:function(g){return g.ciphertext.toString(h)},parse:function(g){var y=h.parse(g);return m.create({ciphertext:y})}}})(),o.format.Hex})})(Ot)),Ot.exports}var Lt={exports:{}},Hq=Lt.exports,og;function Vq(){return og||(og=1,(function(a,r){(function(o,s,u){a.exports=s(Me(),Jn(),eo(),Cn(),Ri())})(Hq,function(o){return(function(){var s=o,u=s.lib,l=u.BlockCipher,m=s.algo,c=[],h=[],d=[],g=[],y=[],f=[],w=[],k=[],x=[],v=[];(function(){for(var z=[],_=0;_<256;_++)_<128?z[_]=_<<1:z[_]=_<<1^283;for(var C=0,D=0,_=0;_<256;_++){var F=D^D<<1^D<<2^D<<3^D<<4;F=F>>>8^F&255^99,c[C]=F,h[F]=C;var R=z[C],M=z[R],P=z[M],T=z[F]*257^F*16843008;d[C]=T<<24|T>>>8,g[C]=T<<16|T>>>16,y[C]=T<<8|T>>>24,f[C]=T;var T=P*16843009^M*65537^R*257^C*16843008;w[F]=T<<24|T>>>8,k[F]=T<<16|T>>>16,x[F]=T<<8|T>>>24,v[F]=T,C?(C=R^z[z[z[P^R]]],D^=z[z[D]]):C=D=1}})();var q=[0,1,2,4,8,16,32,64,128,27,54],A=m.AES=l.extend({_doReset:function(){var z;if(!(this._nRounds&&this._keyPriorReset===this._key)){for(var _=this._keyPriorReset=this._key,C=_.words,D=_.sigBytes/4,F=this._nRounds=D+6,R=(F+1)*4,M=this._keySchedule=[],P=0;P<R;P++)P<D?M[P]=C[P]:(z=M[P-1],P%D?D>6&&P%D==4&&(z=c[z>>>24]<<24|c[z>>>16&255]<<16|c[z>>>8&255]<<8|c[z&255]):(z=z<<8|z>>>24,z=c[z>>>24]<<24|c[z>>>16&255]<<16|c[z>>>8&255]<<8|c[z&255],z^=q[P/D|0]<<24),M[P]=M[P-D]^z);for(var T=this._invKeySchedule=[],U=0;U<R;U++){var P=R-U;if(U%4)var z=M[P];else var z=M[P-4];U<4||P<=4?T[U]=z:T[U]=w[c[z>>>24]]^k[c[z>>>16&255]]^x[c[z>>>8&255]]^v[c[z&255]]}}},encryptBlock: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:c+h,endByte:c+u.length-1,value:f});const w={type:a.PayloadType.Request,version:a.PayloadVersion.Version1,isValid:!0,timestamp:0,requestType:a.RequestType.GetStats,requestData:"",destinationHash:d,sourceHash:g,cipherMac:y,ciphertext:f};return l!=null&&l.includeSegments&&(w.segments=m),w}catch(m){return{type:a.PayloadType.Request,version:a.PayloadVersion.Version1,isValid:!1,errors:[m instanceof Error?m.message:"Failed to decode request payload"],timestamp:0,requestType:a.RequestType.GetStats,requestData:"",destinationHash:"",sourceHash:"",cipherMac:"",ciphertext:""}}}}return Ar.RequestPayloadDecoder=o,Ar}var Cr={},Cg;function jj(){if(Cg)return Cr;Cg=1,Object.defineProperty(Cr,"__esModule",{value:!0}),Cr.ResponsePayloadDecoder=void 0;const a=ua(),r=Ii();class o{static decode(u,l){try{if(u.length<4){const k={type:a.PayloadType.Response,version:a.PayloadVersion.Version1,isValid:!1,errors:["Response payload too short (minimum 4 bytes: dest + source + MAC)"],destinationHash:"",sourceHash:"",cipherMac:"",ciphertext:"",ciphertextLength:0};return l!=null&&l.includeSegments&&(k.segments=[{name:"Invalid Response Data",description:"Response payload too short (minimum 4 bytes required)",startByte:l.segmentOffset||0,endByte:(l.segmentOffset||0)+u.length-1,value:(0,r.bytesToHex)(u)}]),k}const m=[],c=(l==null?void 0:l.segmentOffset)||0;let h=0;const d=(0,r.byteToHex)(u[h]);l!=null&&l.includeSegments&&m.push({name:"Destination Hash",description:"First byte of destination node public key",startByte:c+h,endByte:c+h,value:d}),h+=1;const g=(0,r.byteToHex)(u[h]);l!=null&&l.includeSegments&&m.push({name:"Source Hash",description:"First byte of source node public key",startByte:c+h,endByte:c+h,value:g}),h+=1;const y=(0,r.bytesToHex)(u.subarray(h,h+2));l!=null&&l.includeSegments&&m.push({name:"Cipher MAC",description:"MAC for encrypted data in next field",startByte:c+h,endByte:c+h+1,value:y}),h+=2;const f=(0,r.bytesToHex)(u.subarray(h));l!=null&&l.includeSegments&&u.length>h&&m.push({name:"Ciphertext",description:"Encrypted response data (tag + content)",startByte:c+h,endByte:c+u.length-1,value:f});const w={type:a.PayloadType.Response,version:a.PayloadVersion.Version1,isValid:!0,destinationHash:d,sourceHash:g,cipherMac:y,ciphertext:f,ciphertextLength:u.length-4};return l!=null&&l.includeSegments&&(w.segments=m),w}catch(m){return{type:a.PayloadType.Response,version:a.PayloadVersion.Version1,isValid:!1,errors:[m instanceof Error?m.message:"Failed to decode response payload"],destinationHash:"",sourceHash:"",cipherMac:"",ciphertext:"",ciphertextLength:0}}}}return Cr.ResponsePayloadDecoder=o,Cr}var Er={},Eg;function Aj(){if(Eg)return Er;Eg=1,Object.defineProperty(Er,"__esModule",{value:!0}),Er.AnonRequestPayloadDecoder=void 0;const a=ua(),r=Ii();class o{static decode(u,l){try{if(u.length<35){const k={type:a.PayloadType.AnonRequest,version:a.PayloadVersion.Version1,isValid:!1,errors:["AnonRequest payload too short (minimum 35 bytes: dest + public key + MAC)"],destinationHash:"",senderPublicKey:"",cipherMac:"",ciphertext:"",ciphertextLength:0};return l!=null&&l.includeSegments&&(k.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:l.segmentOffset||0,endByte:(l.segmentOffset||0)+u.length-1,value:(0,r.bytesToHex)(u)}]),k}const m=[],c=(l==null?void 0:l.segmentOffset)||0;let h=0;const d=(0,r.byteToHex)(u[0]);l!=null&&l.includeSegments&&m.push({name:"Destination Hash",description:`First byte of destination node public key: 0x${d}`,startByte:c+h,endByte:c+h,value:d}),h+=1;const g=(0,r.bytesToHex)(u.subarray(1,33));l!=null&&l.includeSegments&&m.push({name:"Sender Public Key",description:"Ed25519 public key of the sender (32 bytes)",startByte:c+h,endByte:c+h+31,value:g}),h+=32;const y=(0,r.bytesToHex)(u.subarray(33,35));l!=null&&l.includeSegments&&m.push({name:"Cipher MAC",description:"MAC for encrypted data verification (2 bytes)",startByte:c+h,endByte:c+h+1,value:y}),h+=2;const f=(0,r.bytesToHex)(u.subarray(35));l!=null&&l.includeSegments&&u.length>35&&m.push({name:"Ciphertext",description:`Encrypted message data (${u.length-35} bytes). Contains encrypted plaintext with this structure:
• Timestamp (4 bytes) - send time as unix timestamp
• Sync Timestamp (4 bytes) - room server only, sender's "sync messages SINCE x" timestamp
• Password (remaining bytes) - password for repeater/room`,startByte:c+h,endByte:c+u.length-1,value:f});const w={type:a.PayloadType.AnonRequest,version:a.PayloadVersion.Version1,isValid:!0,destinationHash:d,senderPublicKey:g,cipherMac:y,ciphertext:f,ciphertextLength:u.length-35};return l!=null&&l.includeSegments&&(w.segments=m),w}catch(m){return{type:a.PayloadType.AnonRequest,version:a.PayloadVersion.Version1,isValid:!1,errors:[m instanceof Error?m.message:"Failed to decode AnonRequest payload"],destinationHash:"",senderPublicKey:"",cipherMac:"",ciphertext:"",ciphertextLength:0}}}}return Er.AnonRequestPayloadDecoder=o,Er}var Sr={},Sg;function Cj(){if(Sg)return Sr;Sg=1,Object.defineProperty(Sr,"__esModule",{value:!0}),Sr.AckPayloadDecoder=void 0;const a=ua(),r=Ii();class o{static decode(u,l){try{if(u.length<4){const g={type:a.PayloadType.Ack,version:a.PayloadVersion.Version1,isValid:!1,errors:["Ack payload too short (minimum 4 bytes for checksum)"],checksum:""};return l!=null&&l.includeSegments&&(g.segments=[{name:"Invalid Ack Data",description:"Ack payload too short (minimum 4 bytes required for checksum)",startByte:l.segmentOffset||0,endByte:(l.segmentOffset||0)+u.length-1,value:(0,r.bytesToHex)(u)}]),g}const m=[],c=(l==null?void 0:l.segmentOffset)||0,h=(0,r.bytesToHex)(u.subarray(0,4));l!=null&&l.includeSegments&&m.push({name:"Checksum",description:`CRC checksum of message timestamp, text, and sender pubkey: 0x${h}`,startByte:c,endByte:c+3,value:h}),l!=null&&l.includeSegments&&u.length>4&&m.push({name:"Additional Data",description:"Extra data in Ack payload",startByte:c+4,endByte:c+u.length-1,value:(0,r.bytesToHex)(u.subarray(4))});const d={type:a.PayloadType.Ack,version:a.PayloadVersion.Version1,isValid:!0,checksum:h};return l!=null&&l.includeSegments&&(d.segments=m),d}catch(m){return{type:a.PayloadType.Ack,version:a.PayloadVersion.Version1,isValid:!1,errors:[m instanceof Error?m.message:"Failed to decode Ack payload"],checksum:""}}}}return Sr.AckPayloadDecoder=o,Sr}var Br={},Bg;function Ej(){if(Bg)return Br;Bg=1,Object.defineProperty(Br,"__esModule",{value:!0}),Br.PathPayloadDecoder=void 0;const a=ua(),r=Ii();class o{static decode(u){try{if(u.length<2)return{type:a.PayloadType.Path,version:a.PayloadVersion.Version1,isValid:!1,errors:["Path payload too short (minimum 2 bytes: path length + extra type)"],pathLength:0,pathHashes:[],extraType:0,extraData:""};const l=u[0];if(u.length<1+l+1)return{type:a.PayloadType.Path,version:a.PayloadVersion.Version1,isValid:!1,errors:[`Path payload too short (need ${1+l+1} bytes for path length + path + extra type)`],pathLength:l,pathHashes:[],extraType:0,extraData:""};const m=[];for(let d=0;d<l;d++)m.push((0,r.byteToHex)(u[1+d]));const c=u[1+l];let h="";return u.length>1+l+1&&(h=(0,r.bytesToHex)(u.subarray(1+l+1))),{type:a.PayloadType.Path,version:a.PayloadVersion.Version1,isValid:!0,pathLength:l,pathHashes:m,extraType:c,extraData:h}}catch(l){return{type:a.PayloadType.Path,version:a.PayloadVersion.Version1,isValid:!1,errors:[l instanceof Error?l.message:"Failed to decode Path payload"],pathLength:0,pathHashes:[],extraType:0,extraData:""}}}}return Br.PathPayloadDecoder=o,Br}var _r={},_g;function Sj(){if(_g)return _r;_g=1,Object.defineProperty(_r,"__esModule",{value:!0}),_r.TextMessagePayloadDecoder=void 0;const a=ua(),r=Ii();class o{static decode(u,l){try{if(u.length<4){const k={type:a.PayloadType.TextMessage,version:a.PayloadVersion.Version1,isValid:!1,errors:["TextMessage payload too short (minimum 4 bytes: dest + source + MAC)"],destinationHash:"",sourceHash:"",cipherMac:"",ciphertext:"",ciphertextLength:0};return l!=null&&l.includeSegments&&(k.segments=[{name:"Invalid TextMessage Data",description:"TextMessage payload too short (minimum 4 bytes required)",startByte:l.segmentOffset||0,endByte:(l.segmentOffset||0)+u.length-1,value:(0,r.bytesToHex)(u)}]),k}const m=[],c=(l==null?void 0:l.segmentOffset)||0;let h=0;const d=(0,r.byteToHex)(u[h]);l!=null&&l.includeSegments&&m.push({name:"Destination Hash",description:"First byte of destination node public key",startByte:c+h,endByte:c+h,value:d}),h+=1;const g=(0,r.byteToHex)(u[h]);l!=null&&l.includeSegments&&m.push({name:"Source Hash",description:"First byte of source node public key",startByte:c+h,endByte:c+h,value:g}),h+=1;const y=(0,r.bytesToHex)(u.subarray(h,h+2));l!=null&&l.includeSegments&&m.push({name:"Cipher MAC",description:"MAC for encrypted data in next field",startByte:c+h,endByte:c+h+1,value:y}),h+=2;const f=(0,r.bytesToHex)(u.subarray(h));l!=null&&l.includeSegments&&u.length>h&&m.push({name:"Ciphertext",description:"Encrypted message data (timestamp + message text)",startByte:c+h,endByte:c+u.length-1,value:f});const w={type:a.PayloadType.TextMessage,version:a.PayloadVersion.Version1,isValid:!0,destinationHash:d,sourceHash:g,cipherMac:y,ciphertext:f,ciphertextLength:u.length-4};return l!=null&&l.includeSegments&&(w.segments=m),w}catch(m){return{type:a.PayloadType.TextMessage,version:a.PayloadVersion.Version1,i Line truncated
// SHA256 round constants
const K: array<u32, 64> = array<u32, 64>(
0x428a2f98u, 0x71374491u, 0xb5c0fbcfu, 0xe9b5dba5u, 0x3956c25bu, 0x59f111f1u, 0x923f82a4u, 0xab1c5ed5u,
0xd807aa98u, 0x12835b01u, 0x243185beu, 0x550c7dc3u, 0x72be5d74u, 0x80deb1feu, 0x9bdc06a7u, 0xc19bf174u,
0xe49b69c1u, 0xefbe4786u, 0x0fc19dc6u, 0x240ca1ccu, 0x2de92c6fu, 0x4a7484aau, 0x5cb0a9dcu, 0x76f988dau,
0x983e5152u, 0xa831c66du, 0xb00327c8u, 0xbf597fc7u, 0xc6e00bf3u, 0xd5a79147u, 0x06ca6351u, 0x14292967u,
0x27b70a85u, 0x2e1b2138u, 0x4d2c6dfcu, 0x53380d13u, 0x650a7354u, 0x766a0abbu, 0x81c2c92eu, 0x92722c85u,
0xa2bfe8a1u, 0xa81a664bu, 0xc24b8b70u, 0xc76c51a3u, 0xd192e819u, 0xd6990624u, 0xf40e3585u, 0x106aa070u,
0x19a4c116u, 0x1e376c08u, 0x2748774cu, 0x34b0bcb5u, 0x391c0cb3u, 0x4ed8aa4au, 0x5b9cca4fu, 0x682e6ff3u,
0x748f82eeu, 0x78a5636fu, 0x84c87814u, 0x8cc70208u, 0x90befffau, 0xa4506cebu, 0xbef9a3f7u, 0xc67178f2u
);
// Character lookup table (a-z = 0-25, 0-9 = 26-35, dash = 36)
const CHARS: array<u32, 37> = array<u32, 37>(
0x61u, 0x62u, 0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u, 0x69u, 0x6au, // a-j
0x6bu, 0x6cu, 0x6du, 0x6eu, 0x6fu, 0x70u, 0x71u, 0x72u, 0x73u, 0x74u, // k-t
0x75u, 0x76u, 0x77u, 0x78u, 0x79u, 0x7au, // u-z
0x30u, 0x31u, 0x32u, 0x33u, 0x34u, 0x35u, 0x36u, 0x37u, 0x38u, 0x39u, // 0-9
0x2du // dash
);
struct Params {
target_channel_hash: u32,
batch_offset: u32,
name_length: u32,
batch_size: u32,
target_mac: u32, // First 2 bytes of target MAC (in high 16 bits)
ciphertext_words: u32, // Number of 32-bit words in ciphertext
ciphertext_len_bits: u32, // Length of ciphertext in bits
verify_mac: u32, // 1 to verify MAC, 0 to skip
}
@group(0) @binding(0) var<uniform> params: Params;
@group(0) @binding(1) var<storage, read_write> match_count: atomic<u32>;
@group(0) @binding(2) var<storage, read_write> match_indices: array<u32>;
@group(0) @binding(3) var<storage, read> ciphertext: array<u32>; // Ciphertext data
fn rotr(x: u32, n: u32) -> u32 {
return (x >> n) | (x << (32u - n));
}
fn ch(x: u32, y: u32, z: u32) -> u32 {
return (x & y) ^ (~x & z);
}
fn maj(x: u32, y: u32, z: u32) -> u32 {
return (x & y) ^ (x & z) ^ (y & z);
}
fn sigma0(x: u32) -> u32 {
return rotr(x, 2u) ^ rotr(x, 13u) ^ rotr(x, 22u);
}
fn sigma1(x: u32) -> u32 {
return rotr(x, 6u) ^ rotr(x, 11u) ^ rotr(x, 25u);
}
fn gamma0(x: u32) -> u32 {
return rotr(x, 7u) ^ rotr(x, 18u) ^ (x >> 3u);
}
fn gamma1(x: u32) -> u32 {
return rotr(x, 17u) ^ rotr(x, 19u) ^ (x >> 10u);
}
// Convert index to room name bytes, returns the hash as a u32 for the first byte check
fn index_to_room_name(idx: u32, length: u32, msg: ptr<function, array<u32, 16>>) -> bool {
// Message starts with '#' (0x23)
var byte_pos = 0u;
var word_idx = 0u;
var current_word = 0x23000000u; // '#' in big-endian position 0
byte_pos = 1u;
var remaining = idx;
var prev_was_dash = false;
// Generate room name from index
for (var i = 0u; i < length; i++) {
let char_count = select(37u, 36u, i == 0u || i == length - 1u); // no dash at start/end
var char_idx = remaining % char_count;
remaining = remaining / char_count;
// Check for consecutive dashes (invalid)
let is_dash = char_idx == 36u && i > 0u && i < length - 1u;
if (is_dash && prev_was_dash) {
return false; // Invalid: consecutive dashes
}
prev_was_dash = is_dash;
// Map char index to actual character
let c = CHARS[char_idx];
// Pack byte into current word (big-endian)
let shift = (3u - byte_pos % 4u) * 8u;
if (byte_pos % 4u == 0u && byte_pos > 0u) {
(*msg)[word_idx] = current_word;
word_idx = word_idx + 1u;
current_word = 0u;
}
current_word = current_word | (c << shift);
byte_pos = byte_pos + 1u;
}
// Add padding: 0x80 followed by zeros, then length in bits
let msg_len_bits = (length + 1u) * 8u; // +1 for '#'
// Add 0x80 padding byte
let shift = (3u - byte_pos % 4u) * 8u;
if (byte_pos % 4u == 0u) {
(*msg)[word_idx] = current_word;
word_idx = word_idx + 1u;
current_word = 0x80000000u;
} else {
current_word = current_word | (0x80u << shift);
}
byte_pos = byte_pos + 1u;
// Store current word
if (byte_pos % 4u == 0u || word_idx < 14u) {
(*msg)[word_idx] = current_word;
word_idx = word_idx + 1u;
}
// Zero-fill until word 14
for (var i = word_idx; i < 14u; i++) {
(*msg)[i] = 0u;
}
// Length in bits (64-bit, but we only use lower 32 bits for short messages)
(*msg)[14u] = 0u;
(*msg)[15u] = msg_len_bits;
return true;
}
fn sha256_block(msg: ptr<function, array<u32, 16>>) -> array<u32, 8> {
// Initialize hash values
var h: array<u32, 8> = array<u32, 8>(
0x6a09e667u, 0xbb67ae85u, 0x3c6ef372u, 0xa54ff53au,
0x510e527fu, 0x9b05688cu, 0x1f83d9abu, 0x5be0cd19u
);
// Message schedule
var w: array<u32, 64>;
for (var i = 0u; i < 16u; i++) {
w[i] = (*msg)[i];
}
for (var i = 16u; i < 64u; i++) {
w[i] = gamma1(w[i-2u]) + w[i-7u] + gamma0(w[i-15u]) + w[i-16u];
}
// Compression
var a = h[0]; var b = h[1]; var c = h[2]; var d = h[3];
var e = h[4]; var f = h[5]; var g = h[6]; var hh = h[7];
for (var i = 0u; i < 64u; i++) {
let t1 = hh + sigma1(e) + ch(e, f, g) + K[i] + w[i];
let t2 = sigma0(a) + maj(a, b, c);
hh = g; g = f; f = e; e = d + t1;
d = c; c = b; b = a; a = t1 + t2;
}
h[0] = h[0] + a; h[1] = h[1] + b; h[2] = h[2] + c; h[3] = h[3] + d;
h[4] = h[4] + e; h[5] = h[5] + f; h[6] = h[6] + g; h[7] = h[7] + hh;
return h;
}
// Compute SHA256 of the key (16 bytes) to get channel hash
fn sha256_key(key: array<u32, 4>) -> u32 {
var msg: array<u32, 16>;
// Key bytes (16 bytes = 4 words)
msg[0] = key[0];
msg[1] = key[1];
msg[2] = key[2];
msg[3] = key[3];
// Padding: 0x80 followed by zeros
msg[4] = 0x80000000u;
for (var i = 5u; i < 14u; i++) {
msg[i] = 0u;
}
// Length: 128 bits
msg[14] = 0u;
msg[15] = 128u;
let hash = sha256_block(&msg);
// Return first byte of hash (big-endian)
return hash[0] >> 24u;
}
// HMAC-SHA256 for MAC verification
// Key is 16 bytes (4 words), padded to 32 bytes with zeros for MeshCore
// Returns first 2 bytes of HMAC (as u32 in high 16 bits)
fn hmac_sha256_mac(key: array<u32, 4>, ciphertext_len: u32) -> u32 {
// HMAC: H((K' ^ opad) || H((K' ^ ipad) || message))
// K' is 64 bytes (32 bytes key + 32 bytes zero padding for MeshCore, then padded to 64)
// ipad = 0x36 repeated, opad = 0x5c repeated
// Build padded key (64 bytes = 16 words)
// MeshCore uses 32-byte secret: 16-byte key + 16 zero bytes
var k_pad: array<u32, 16>;
k_pad[0] = key[0];
k_pad[1] = key[1];
k_pad[2] = key[2];
k_pad[3] = key[3];
for (var i = 4u; i < 16u; i++) {
k_pad[i] = 0u;
}
// Inner hash: SHA256((K' ^ ipad) || message)
// First block: K' ^ ipad (64 bytes)
var inner_block: array<u32, 16>;
for (var i = 0u; i < 16u; i++) {
inner_block[i] = k_pad[i] ^ 0x36363636u;
}
// Initialize hash state with first block
var h: array<u32, 8> = sha256_block(&inner_block);
// Process ciphertext blocks (continuing from h state)
let ciphertext_words = params.ciphertext_words;
var word_idx = 0u;
// Process full 64-byte blocks of ciphertext
while (word_idx + 16u <= ciphertext_words) {
var block: array<u32, 16>;
for (var i = 0u; i < 16u; i++) {
block[i] = ciphertext[word_idx + i];
}
h = sha256_block_continue(&block, h);
word_idx = word_idx + 16u;
}
// Final block with remaining ciphertext + padding
var final_block: array<u32, 16>;
var remaining = ciphertext_words - word_idx;
for (var i = 0u; i < 16u; i++) {
if (i < remaining) {
final_block[i] = ciphertext[word_idx + i];
} else if (i == remaining) {
// Add 0x80 padding
final_block[i] = 0x80000000u;
} else {
final_block[i] = 0u;
}
}
// Add length (64 bytes of ipad + ciphertext length)
let total_bits = 512u + params.ciphertext_len_bits;
if (remaining < 14u) {
final_block[14] = 0u;
final_block[15] = total_bits;
h = sha256_block_continue(&final_block, h);
} else {
// Need extra block for length
h = sha256_block_continue(&final_block, h);
var len_block: array<u32, 16>;
for (var i = 0u; i < 14u; i++) {
len_block[i] = 0u;
}
len_block[14] = 0u;
len_block[15] = total_bits;
h = sha256_block_continue(&len_block, h);
}
let inner_hash = h;
// Outer hash: SHA256((K' ^ opad) || inner_hash)
var outer_block: array<u32, 16>;
for (var i = 0u; i < 16u; i++) {
outer_block[i] = k_pad[i] ^ 0x5c5c5c5cu;
}
h = sha256_block(&outer_block);
// Second block: inner_hash (32 bytes) + padding
var hash_block: array<u32, 16>;
for (var i = 0u; i < 8u; i++) {
hash_block[i] = inner_hash[i];
}
hash_block[8] = 0x80000000u;
for (var i = 9u; i < 14u; i++) {
hash_block[i] = 0u;
}
hash_block[14] = 0u;
hash_block[15] = 512u + 256u; // 64 bytes opad + 32 bytes inner hash
h = sha256_block_continue(&hash_block, h);
// Return first 2 bytes (high 16 bits of first word)
return h[0] & 0xFFFF0000u;
}
// SHA256 block computation continuing from existing state
fn sha256_block_continue(msg: ptr<function, array<u32, 16>>, h_in: array<u32, 8>) -> array<u32, 8> {
var h = h_in;
// Message schedule
var w: array<u32, 64>;
for (var i = 0u; i < 16u; i++) {
w[i] = (*msg)[i];
}
for (var i = 16u; i < 64u; i++) {
w[i] = gamma1(w[i-2u]) + w[i-7u] + gamma0(w[i-15u]) + w[i-16u];
}
// Compression
var a = h[0]; var b = h[1]; var c = h[2]; var d = h[3];
var e = h[4]; var f = h[5]; var g = h[6]; var hh = h[7];
for (var i = 0u; i < 64u; i++) {
let t1 = hh + sigma1(e) + ch(e, f, g) + K[i] + w[i];
let t2 = sigma0(a) + maj(a, b, c);
hh = g; g = f; f = e; e = d + t1;
d = c; c = b; b = a; a = t1 + t2;
}
h[0] = h[0] + a; h[1] = h[1] + b; h[2] = h[2] + c; h[3] = h[3] + d;
h[4] = h[4] + e; h[5] = h[5] + f; h[6] = h[6] + g; h[7] = h[7] + hh;
return h;
}
// Process a single candidate and record match if found
fn process_candidate(name_idx: u32) {
// Generate message for this room name
var msg: array<u32, 16>;
let valid = index_to_room_name(name_idx, params.name_length, &msg);
if (!valid) {
return;
}
// Compute SHA256("#roomname") - this gives us the key
let key_hash = sha256_block(&msg);
// Take first 16 bytes (4 words) as the key
var key: array<u32, 4>;
key[0] = key_hash[0];
key[1] = key_hash[1];
key[2] = key_hash[2];
key[3] = key_hash[3];
// Compute SHA256(key) to get channel hash
let channel_hash = sha256_key(key);
// Check if channel hash matches target
if (channel_hash != params.target_channel_hash) {
return;
}
// Channel hash matches - verify MAC if enabled
if (params.verify_mac == 1u) {
let computed_mac = hmac_sha256_mac(key, params.ciphertext_len_bits);
if (computed_mac != params.target_mac) {
return;
}
}
// Found a match - record the index
let match_idx = atomicAdd(&match_count, 1u);
if (match_idx < 1024u) { // Limit stored matches
match_indices[match_idx] = name_idx;
}
}
// Each thread processes 16 candidates to amortize thread overhead
const CANDIDATES_PER_THREAD: u32 = 16u;
@compute @workgroup_size(256)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
let base_idx = global_id.x * CANDIDATES_PER_THREAD;
for (var i = 0u; i < CANDIDATES_PER_THREAD; i++) {
let idx = base_idx + i;
if (idx >= params.batch_size) {
return;
}
let name_idx = params.batch_offset + idx;
process_candidate(name_idx);
}
}
`}async init(){if(!navigator.gpu)return console.warn("WebGPU not supported"),!1;try{const r=await navigator.gpu.requestAdapter();if(!r)return console.warn("No GPU adapter found"),!1;this.device=await r.requestDevice(),this.bindGroupLayout=this.device.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.COMPUTE,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.COMPUTE,buffer:{type:"storage"}},{binding:2,visibility:GPUShaderStage.COMPUTE,buffer:{type:"storage"}},{binding:3,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}}]}),this.paramsBuffer=this.device.createBuffer({size:32,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),this.matchCountBuffer=this.device.createBuffer({size:4,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_SRC|GPUBufferUsage.COPY_DST}),this.matchIndicesBuffer=this.device.createBuffer({size:1024*4,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_SRC});for(let u=0;u<2;u++)this.matchCountReadBuffers[u]=this.device.createBuffer({size:4,usage:GPUBufferUsage.MAP_READ|GPUBufferUsage.COPY_DST}),this.matchIndicesReadBuffers[u]=this.device.createBuffer({size:1024*4,usage:GPUBufferUsage.MAP_READ|GPUBufferUsage.COPY_DST});const o=this.device.createShaderModule({code:this.shaderCode}),s=this.device.createPipelineLayout({bindGroupLayouts:[this.bindGroupLayout]});return this.pipeline=this.device.createComputePipeline({layout:s,compute:{module:o,entryPoint:"main"}}),!0}catch(r){return console.error("WebGPU initialization failed:",r),!1}}isAvailable(){return this.device!==null&&this.pipeline!==null}indexToRoomName(r,o){return Kn(o,r)}countNamesForLength(r){return Tr(r)}async runBatch(r,o,s,u,l,m){if(!this.device||!this.pipeline||!this.bindGroupLayout||!this.paramsBuffer||!this.matchCountBuffer||!this.matchIndicesBuffer||!this.matchCountReadBuffers[0]||!this.matchCountReadBuffers[1]||!this.matchIndicesReadBuffers[0]||!this.matchIndicesReadBuffers[1])throw new Error("GPU not initialized");const c=this.currentReadBufferIndex;this.currentReadBufferIndex=1-this.currentReadBufferIndex;const h=this.matchCountReadBuffers[c],d=this.matchIndicesReadBuffers[c],g=l&&m?1:0;let y,f=0,w=0;if(g){const C=new Uint8Array(l.length/2);for(let P=0;P<C.length;P++)C[P]=parseInt(l.substr(P*2,2),16);f=C.length*8;const D=Math.ceil(C.length/4)*4,F=new Uint8Array(D);F.set(C),y=new Uint32Array(D/4);for(let P=0;P<y.length;P++)y[P]=F[P*4]<<24|F[P*4+1]<<16|F[P*4+2]<<8|F[P*4+3];const R=parseInt(m.substr(0,2),16),M=parseInt(m.substr(2,2),16);w=R<<24|M<<16}else y=new Uint32Array([0]);const k=Math.max(y.length*4,4);(!this.ciphertextBuffer||this.ciphertextBufferSize<k)&&(this.ciphertextBuffer&&this.ciphertextBuffer.destroy(),this.ciphertextBuffer=this.device.createBuffer({size:k,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST}),this.ciphertextBufferSize=k,this.bindGroupDirty=!0);const x=new Uint32Array([r,s,o,u,w,y.length,f,g]);this.device.queue.writeBuffer(this.paramsBuffer,0,x),this.device.queue.writeBuffer(this.ciphertextBuffer,0,y),this.device.queue.writeBuffer(this.matchCountBuffer,0,fl.ZERO_DATA),(this.bindGroupDirty||!this.bindGroup)&&(this.bindGroup=this.device.createBindGroup({layout:this.bindGroupLayout,entries:[{binding:0,resource:{buffer:this.paramsBuffer}},{binding:1,resource:{buffer:this.matchCountBuffer}},{binding:2,resource:{buffer:this.matchIndicesBuffer}},{binding:3,resource:{buffer:this.ciphertextBuffer}}]}),this.bindGroupDirty=!1);const v=this.device.createCommandEncoder(),q=v.beginComputePass();q.setPipeline(this.pipeline),q.setBindGroup(0,this.bindGroup),q.dispatchWorkgroups(Math.ceil(u/(256*16))),q.end(),v.copyBufferToBuffer(this.matchCountBuffer,0,h,0,4),v.copyBufferToBuffer(this.matchIndicesBuffer,0,d,0,1024*4),this.device.queue.submit([v.finish()]),await h.mapAsync(GPUMapMode.READ);const z=new Uint32Array(h.getMappedRange())[0];h.unmap();const _=[];if(z>0){await d.mapAsync(GPUMapMode.READ);const C=new Uint32Array(d.getMappedRange());for(let D=0;D<Math.min(z,1024);D++)_.push(C[D]);d.unmap()}return _}destroy(){var r,o,s,u,l,m,c,h;(r=this.paramsBuffer)==null||r.destroy(),(o=this.matchCountBuffer)==null||o.destroy(),(s=this.matchIndicesBuffer)==null||s.destroy(),(u=this.ciphertextBuffer)==null||u.destroy(),(l=this.matchCountReadBuffers[0])==null||l.destroy(),(m=this.matchCountReadBuffers[1])==null||m.destroy(),(c=this.matchIndicesReadBuffers[0])==null||c.destroy(),(h=this.matchIndicesReadBuffers[1])==null||h.destroy(),this.paramsBuffer=null,this.matchCountBuffer=null,this.matchIndicesBuffer=null,this.ciphertextBuffer=null,this.ciphertextBufferSize=0,this.matchCountReadBuffers=[null,null],this.matchIndicesReadBuffers=[null,null],this.currentReadBufferIndex=0,this.bindGroup=null,this.bindGroupDirty=!0,this.device&&(this.device.destroy(),this.device=null),this.pipeline=null,this.bindGroupLayout=null}}fl.ZERO_DATA=new Uint32Array([0]);function Vj(){return typeof navigator<"u"&&"gpu"in navigator}class Lg{runBatch(r,o,s,u,l,m){const c=[],h=r.toString(16).padStart(2,"0") Line truncated
`).map(l=>l.trim().toLowerCase()).filter(l=>l.length>0);this.wordlist=u.filter(Hg)}setWordlist(r){this.wordlist=r.map(o=>o.trim().toLowerCase()).filter(Hg)}abort(){this.abortFlag=!0}isGpuAvailable(){return Vj()}async decodePacket(r){var s;const o=r.trim().replace(/\s+/g,"").replace(/^0x/i,"");if(!o||!/^[0-9a-fA-F]+$/.test(o))return null;try{const l=(s=(await Ig.MeshCorePacketDecoder.decodeWithVerification(o,{})).payload)==null?void 0:s.decoded;return!(l!=null&&l.channelHash)||!(l!=null&&l.ciphertext)||!(l!=null&&l.cipherMac)?null:{channelHash:l.channelHash,ciphertext:l.ciphertext,cipherMac:l.cipherMac,isGroupText:!0}}catch{return null}}async crack(r,o,s){this.abortFlag=!1,this.useTimestampFilter=(o==null?void 0:o.useTimestampFilter)??!0,this.useUtf8Filter=(o==null?void 0:o.useUtf8Filter)??!0,this.validSeconds=(o==null?void 0:o.validSeconds)??Tu,this.useCpu=(o==null?void 0:o.forceCpu)??!1;const u=(o==null?void 0:o.maxLength)??8,l=(o==null?void 0:o.startingLength)??1,m=(o==null?void 0:o.useDictionary)??!0,c=(o==null?void 0:o.startFromType)??"bruteforce",h=r.toLowerCase(),d=await this.decodePacket(h);if(!d)return{found:!1,error:"Invalid packet or not a GroupText packet"};const{channelHash:g,ciphertext:y,cipherMac:f}=d,w=parseInt(g,16);this.useCpu?this.cpuInstance||(this.cpuInstance=new Lg):this.gpuInstance||(this.gpuInstance=new fl,await this.gpuInstance.init()||(this.useCpu=!0,this.cpuInstance=new Lg));const k=performance.now();let x=0,v=performance.now(),q=l,A=0,z=0,_=!1;if(o!=null&&o.startFrom){const H=o.startFrom.toLowerCase();if(c==="dictionary"){const K=this.wordlist.indexOf(H);K>=0&&(z=K+1)}else{_=!0;const K=Oj(H);K&&(q=Math.max(l,K.length),A=K.index+1,A>=Tr(q)&&(q++,A=0))}}let C=0;m&&!_&&this.wordlist.length>0&&(C+=this.wordlist.length-z);for(let H=q;H<=u;H++)C+=Tr(H);C-=A;const D=(H,K,ie)=>{if(!s)return;const ae=(performance.now()-k)/1e3,W=ae>0?Math.round(x/ae):0,ne=C-x,X=W>0?ne/W:0;s({checked:x,total:C,percent:C>0?Math.min(100,x/C*100):0,rateKeysPerSec:W,etaSeconds:X,elapsedSeconds:ae,currentLength:K,currentPosition:ie,phase:H})},F=H=>{if(!af(y,f,H))return{valid:!1};const K=Ig.ChannelCrypto.decryptGroupTextMessage(y,f,H);return!K.success||!K.data?{valid:!1}:this.useTimestampFilter&&!Lj(K.data.timestamp,this.validSeconds)?{valid:!1}:this.useUtf8Filter&&!Hj(K.data.message)?{valid:!1}:{valid:!0,message:K.data.message}};if(!_&&z===0&&q===l&&A===0){D("public-key",0,Ru);const H=Nu(Yt);if(g===H){const K=F(Yt);if(K.valid)return{found:!0,roomName:Ru,key:Yt,decryptedMessage:K.message}}}if(m&&!_&&this.wordlist.length>0)for(let H=z;H<this.wordlist.length;H++){if(this.abortFlag)return{found:!1,aborted:!0,resumeFrom:this.wordlist[H],resumeType:"dictionary"};const K=this.wordlist[H],ie=Pu("#"+K),Z=Nu(ie);if(parseInt(Z,16)===w){const W=F(ie);if(W.valid)return{found:!0,roomName:K,key:ie,decryptedMessage:W.message,resumeFrom:K,resumeType:"dictionary"}}x++;const ae=performance.now();ae-v>=200&&(D("wordlist",K.length,K),v=ae,await new Promise(W=>setTimeout(W,0)))}const R=this.useCpu?1024:32768,M=(o==null?void 0:o.gpuDispatchMs)??1e3;let P=R,T=!1;for(let H=q;H<=u;H++){if(this.abortFlag)return{found:!1,aborted:!0,resumeFrom:Kn(H,0)||void 0,resumeType:"bruteforce"};const K=Tr(H);let ie=H===q?A:0;for(;ie<K;){if(this.abortFlag)return{found:!1,aborted:!0,resumeFrom:Kn(H,ie)||void 0,resumeType:"bruteforce"};const Z=Math.min(P,K-ie),ae=performance.now();let W;this.useCpu?W=this.cpuInstance.runBatch(w,H,ie,Z,y,f):W=await this.gpuInstance.runBatch(w,H,ie,Z,y,f);const ne=performance.now()-ae;if(x+=Z,!this.useCpu&&!T&&Z>=R&&ne>0){const j=M/ne,I=Math.round(Z*j),L=Math.pow(2,Math.round(Math.log2(Math.max(R,I))));P=Math.max(R,L),T=!0}for(const j of W){const I=Kn(H,j);if(!I)continue;const L=Pu("#"+I),$=F(L);if($.valid)return{found:!0,roomName:I,key:L,decryptedMessage:$.message,resumeFrom:I,resumeType:"bruteforce"}}ie+=Z;const X=performance.now();if(X-v>=200){const j=Kn(H,Math.min(ie,K-1))||"";D("bruteforce",H,j),v=X,await new Promise(I=>setTimeout(I,0))}}}return{found:!1,resumeFrom:Kn(u,Tr(u)-1)||void 0,resumeType:"bruteforce"}}destroy(){this.gpuInstance&&(this.gpuInstance.destroy(),this.gpuInstance=null),this.cpuInstance&&(this.cpuInstance.destroy(),this.cpuInstance=null)}}const Qj=["a","aa","aaa","aah","aahed","aahing","aahs","aal","aalii","aaliis","aals","aam","aani","aardvark","aardvarks","aardwolf","aardwolves","aargh","aaron","aaronic","aaronical","aaronite","aaronitic","aarrgh","aarrghh","aaru","aas","aasvogel","aasvogels","ab","aba","ababdeh","ababua","abac","abaca","abacay","abacas","abacate","abacaxi","abaci","abacinate","abacination","abacisci","abaciscus","abacist","aback","abacli","abacot","abacterial","abactinal","abactinally","abaction","abactor","abaculi","abaculus","abacus","abacuses","abada","abaddon","abadejo","abadengo","abadia","abadite","abaff","abaft","abay","abayah","abaisance","abaised","abaiser","abaisse","abaissed","abaka","abakas","abalation","abalienate","abalienated","abalienating","abalienation","abalone Line truncated
NoSleep enabled for older iOS devices. This can interrupt
active or long-running network requests from completing successfully.
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(c=>(this.enabled=!0,c)).catch(c=>{throw this.enabled=!1,c})}disable(){s()?(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 xu=u,xu}var Zj=Yj();const Xj=Ro(Zj);function Jj({packets:a,channels:r,onChannelCreate:o,onRunningChange:s}){const[u,l]=E.useState(!1),[m,c]=E.useState(6),[h,d]=E.useState(!1),[g,y]=E.useState(!0),[f,w]=E.useState(!1),[k,x]=E.useState(null),[v,q]=E.useState(new Map),[A,z]=E.useState([]),[_,C]=E.useState(!1),[D,F]=E.useState(null),[R,M]=E.useState(null),P=E.useRef(null),T=E.useRef(null),U=E.useRef(!1),H=E.useRef(!1),K=E.useRef(!1),ie=E.useRef(new Map),Z=E.useRef(!1),ae=E.useRef(6),W=E.useRef(!0),ne=E.useRef(!1),X=E.useRef(new Set);E.useEffect(()=>{const ce=new Gj;P.current=ce,F(ce.isGpuAvailable());const je=new Xj;return T.current=je,ce.setWordlist(Qj),C(!0),()=>{ce.destroy(),P.current=null,je.disable(),T.current=null}},[]),E.useEffect(()=>{const ce=()=>{ei.getUndecryptedPacketCount().then(({count:De})=>M(De)).catch(()=>M(null))};ce();const je=setInterval(ce,3e4);return()=>clearInterval(je)},[]);const j=new Set(r.map(ce=>ce.key.toUpperCase())),I=a.filter(ce=>ce.payload_type==="GROUP_TEXT"&&!ce.decrypted);E.useEffect(()=>{q(ce=>{const je=new Map(ce);let De=!1;for(const Pe of I)je.has(Pe.id)||(je.set(Pe.id,{packet:Pe,attempts:0,lastAttemptLength:0,status:"pending"}),De=!0);return De?(ie.current=je,je):ce})},[I.length]),E.useEffect(()=>{ie.current=v},[v]),E.useEffect(()=>{Z.current=h},[h]),E.useEffect(()=>{ae.current=m},[m]),E.useEffect(()=>{W.current=g},[g]),E.useEffect(()=>{ne.current=f},[f]),E.useEffect(()=>{X.current=new Set(I.map(ce=>ce.id))},[I]),E.useEffect(()=>{s==null||s(u)},[u,s]);const L=Array.from(v.values()).filter(ce=>ce.status==="pending").length,$=Array.from(v.values()).filter(ce=>ce.status==="cracked").length,le=Array.from(v.values()).filter(ce=>ce.status==="failed").length,me=E.useCallback(async()=>{if(K.current||!P.current||!U.current)return;const ce=ie.current;let je=null,De=null;for(const[He,Oe]of ce.entries())if(Oe.status==="pending"){je=Oe,De=He;break}if(!je&&Z.current){const He=Array.from(ce.entries()).filter(([,Oe])=>Oe.status==="failed"&&Oe.lastAttemptLength<10);He.length>0&&(He.sort((Oe,Je)=>Oe[1].lastAttemptLength-Je[1].lastAttemptLength),[De,je]=He[0])}if(!je||De===null){U.current&&setTimeout(()=>me(),1e3);return}if(!X.current.has(De)){q(He=>{const Oe=new Map(He);return Oe.delete(De),Oe}),U.current&&setTimeout(()=>me(),10);return}K.current=!0;const Pe=ae.current,ai=je.lastAttemptLength>0,Re=ai?je.lastAttemptLength+1:Pe;try{const He=await P.current.crack(je.packet.data,{maxLength:Re,useTimestampFilter:!0,useUtf8Filter:!0,...ne.current&&{gpuDispatchMs:1e4},...ai&&{useDictionary:!1,startingLength:Re}},Oe=>{x(Oe)});if(H.current){H.current=!1,K.current=!1,x(null);return}if(He.found&&He.roomName&&He.key){q(Ge=>{const oi=new Map(Ge),fi=oi.get(De);return fi&&oi.set(De,{...fi,status:"cracked",attempts:fi.attempts+1,lastAttemptLength:Re}),oi});const Oe={roomName:He.roomName,key:He.key,packetId:De,message:He.decryptedMessage||"",crackedAt:Date.now()};z(Ge=>[...Ge,Oe]);const Je=He.key.toUpperCase();if(!j.has(Je))try{const Ge="#"+He.roomName;await o(Ge,He.key),W.current&&await ei.decryptHistoricalPackets({key_type:"channel",channel_name:Ge})}catch(Ge){console.error("Failed to create channel or decrypt historical:",Ge)}}else q(Oe=>{const Je=new Map(Oe),Ge=Je.get(De);return Ge&&Je.set(De,{...Ge,status:"failed",attempts:Ge.attempts+1,lastAttemptLength:Re}),Je})}catch(He){console.error("Cracking error:",He),q(Oe=>{const Je=new Map(Oe),Ge=Je.get(De);return Ge&&Je.set(De,{...Ge,status:"failed",attempts:Ge.attempts+1,lastAttemptLength:Re}),Je})}K.current=!1,x(null),U.current&&setTimeout(()=>me(),100)},[j,o]),ge=()=>{var ce;if(!D){alert("WebGPU is not available in your browser. Please use Chrome 113+ or Edge 113+.");return}l(!0),U.current=!0,H.current=!1,(ce=T.current)==null||ce.enable(),me()},oe=()=>{var ce,je;l(!1),U.current=!1,H.current=!0,(ce=P.current)==null||ce.abort(),(je=T.current)==null||je.disable()};return B.jsxs("div",{className:"flex flex-col h-full p-3 gap-3 bg-background border-t border-border",children:[B.jsxs("p",{className:"text-xs text-muted-foreground leading-relaxed",children:["This will attempt to dictionary attack, then brute force GroupText packets as they arrive, testing room names up to the specified length.",B.jsx("strong",{children:" Retry failed at n+1"})," will let the cracker return to the failed queue and pick up messages it couldn't crack, attempting them at one longer length.",B.jsx("strong",{children:" Decrypt historical"})," will run an async job on any room name it finds to see if any historically captured packets will decrypt with that key.",B.jsx("strong",{children:" Turbo mode"})," will push your GPU to the max (target dispatch time of 10s) and may allow accelerated cracking and/or system instability."]}),B.jsxs("div",{className:"flex items-center gap-3 flex-wrap",children:[B.jsx("button",{onClick:u?oe:ge,disabled:!_||D===!1,className:$e("px Line truncated
-537
View File
@@ -1,537 +0,0 @@
var Zv=Object.defineProperty;var Xv=(a,r,o)=>r in a?Zv(a,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):a[r]=o;var Ln=(a,r,o)=>Xv(a,typeof r!="symbol"?r+"":r,o);function Jv(a,r){for(var o=0;o<r.length;o++){const s=r[o];if(typeof s!="string"&&!Array.isArray(s)){for(const u in s)if(u!=="default"&&!(u in a)){const l=Object.getOwnPropertyDescriptor(s,u);l&&Object.defineProperty(a,u,l.get?l:{enumerable:!0,get:()=>s[u]})}}}return Object.freeze(Object.defineProperty(a,Symbol.toStringTag,{value:"Module"}))}(function(){const r=document.createElement("link").relList;if(r&&r.supports&&r.supports("modulepreload"))return;for(const u of document.querySelectorAll('link[rel="modulepreload"]'))s(u);new MutationObserver(u=>{for(const l of u)if(l.type==="childList")for(const m of l.addedNodes)m.tagName==="LINK"&&m.rel==="modulepreload"&&s(m)}).observe(document,{childList:!0,subtree:!0});function o(u){const l={};return u.integrity&&(l.integrity=u.integrity),u.referrerPolicy&&(l.referrerPolicy=u.referrerPolicy),u.crossOrigin==="use-credentials"?l.credentials="include":u.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function s(u){if(u.ep)return;u.ep=!0;const l=o(u);fetch(u.href,l)}})();var Zc=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function Fo(a){return a&&a.__esModule&&Object.prototype.hasOwnProperty.call(a,"default")?a.default:a}function Vg(a){if(Object.prototype.hasOwnProperty.call(a,"__esModule"))return a;var r=a.default;if(typeof r=="function"){var o=function s(){return this instanceof s?Reflect.construct(r,arguments,this.constructor):r.apply(this,arguments)};o.prototype=r.prototype}else o={};return Object.defineProperty(o,"__esModule",{value:!0}),Object.keys(a).forEach(function(s){var u=Object.getOwnPropertyDescriptor(a,s);Object.defineProperty(o,s,u.get?u:{enumerable:!0,get:function(){return a[s]}})}),o}var Xc={exports:{}},br={},Jc={exports:{}},Ne={};/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var Mm;function ek(){if(Mm)return Ne;Mm=1;var a=Symbol.for("react.element"),r=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),s=Symbol.for("react.strict_mode"),u=Symbol.for("react.profiler"),l=Symbol.for("react.provider"),m=Symbol.for("react.context"),c=Symbol.for("react.forward_ref"),h=Symbol.for("react.suspense"),d=Symbol.for("react.memo"),y=Symbol.for("react.lazy"),g=Symbol.iterator;function f(q){return q===null||typeof q!="object"?null:(q=g&&q[g]||q["@@iterator"],typeof q=="function"?q:null)}var z={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},k=Object.assign,j={};function v(q,I,L){this.props=q,this.context=I,this.refs=j,this.updater=L||z}v.prototype.isReactComponent={},v.prototype.setState=function(q,I){if(typeof q!="object"&&typeof q!="function"&&q!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,q,I,"setState")},v.prototype.forceUpdate=function(q){this.updater.enqueueForceUpdate(this,q,"forceUpdate")};function A(){}A.prototype=v.prototype;function C(q,I,L){this.props=q,this.context=I,this.refs=j,this.updater=L||z}var w=C.prototype=new A;w.constructor=C,k(w,v.prototype),w.isPureReactComponent=!0;var _=Array.isArray,x=Object.prototype.hasOwnProperty,D={current:null},R={key:!0,ref:!0,__self:!0,__source:!0};function F(q,I,L){var G,de={},ge=null,be=null;if(I!=null)for(G in I.ref!==void 0&&(be=I.ref),I.key!==void 0&&(ge=""+I.key),I)x.call(I,G)&&!R.hasOwnProperty(G)&&(de[G]=I[G]);var se=arguments.length-2;if(se===1)de.children=L;else if(1<se){for(var ce=Array(se),Ae=0;Ae<se;Ae++)ce[Ae]=arguments[Ae+2];de.children=ce}if(q&&q.defaultProps)for(G in se=q.defaultProps,se)de[G]===void 0&&(de[G]=se[G]);return{$$typeof:a,type:q,key:ge,ref:be,props:de,_owner:D.current}}function O(q,I){return{$$typeof:a,type:q.type,key:I,ref:q.ref,props:q.props,_owner:q._owner}}function N(q){return typeof q=="object"&&q!==null&&q.$$typeof===a}function T(q){var I={"=":"=0",":":"=2"};return"$"+q.replace(/[=:]/g,function(L){return I[L]})}var W=/\/+/g;function H(q,I){return typeof q=="object"&&q!==null&&q.key!=null?T(""+q.key):I.toString(36)}function K(q,I,L,G,de){var ge=typeof q;(ge==="undefined"||ge==="boolean")&&(q=null);var be=!1;if(q===null)be=!0;else switch(ge){case"string":case"number":be=!0;break;case"object":switch(q.$$typeof){case a:case r:be=!0}}if(be)return be=q,de=de(be),q=G===""?"."+H(be,0):G,_(de)?(L="",q!=null&&(L=q.replace(W,"$&/")+"/"),K(de,I,L,"",function(Ae){return Ae})):de!=null&&(N(de)&&(de=O(de,L+(!de.key||be&&be.key===de.key?"":(""+de.key).replace(W,"$&/")+"/")+q)),I.push(de)),1;if(be=0,G=G===""?".":G+":",_(q))for(var se=0;se<q.length;se++){ge=q[se];var ce=G+H(ge,se);be+=K(ge,I,L,ce,de)}else if(ce=f(q),typeof ce=="function")for(q=ce.call(q),se=0;!(ge=q.next()).done;)ge=ge.value,ce=G+H(ge,se++),be+=K(ge,I,L,ce,de);else if(ge==="object")throw I=String(q),Error("Objects are not valid as a React child (found: "+(I==="[object Object]"?"object with keys {"+Object.keys(q).join(", ")+"}":I)+"). If you meant to render a collection of children, use an array instead.");return be}function ae(q,I,L){if(q==null)return q;var G=[],de=0;return K(q,G,"","",function(ge){return I.call(L,ge,de++)}),G}function X(q){if(q._status===-1){var I=q._result;I=I(),I.then(function(L){(q._status===0||q._status===-1)&&(q._status=1,q._result=L)},function(L){(q._status===0||q._status===-1)&&(q._status=2,q._result=L)}),q._status===-1&&(q._status=0,q._result=I)}if(q._status===1)return q._result.default;throw q._result}var ne={current:null},$={transition:null},oe={ReactCurrentDispatcher:ne,ReactCurrentBatchConfig:$,ReactCurrentOwner:D};function ee(){throw Error("act(...) is not supported in production builds of React.")}return Ne.Children={map:ae,forEach:function(q,I,L){ae(q,function(){I.apply(this,arguments)},L)},count:function(q){var I=0;return ae(q,function(){I++}),I},toArray:function(q){return ae(q,function(I){return I})||[]},only:function(q){if(!N(q))throw Error("React.Children.only expected to receive a single React element child.");return q}},Ne.Component=v,Ne.Fragment=o,Ne.Profiler=u,Ne.PureComponent=C,Ne.StrictMode=s,Ne.Suspense=h,Ne.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=oe,Ne.act=ee,Ne.cloneElement=function(q,I,L){if(q==null)throw Error("React.cloneElement(...): The argument must be a React element, but you passed "+q+".");var G=k({},q.props),de=q.key,ge=q.ref,be=q._owner;if(I!=null){if(I.ref!==void 0&&(ge=I.ref,be=D.current),I.key!==void 0&&(de=""+I.key),q.type&&q.type.defaultProps)var se=q.type.defaultProps;for(ce in I)x.call(I,ce)&&!R.hasOwnProperty(ce)&&(G[ce]=I[ce]===void 0&&se!==void 0?se[ce]:I[ce])}var ce=arguments.length-2;if(ce===1)G.children=L;else if(1<ce){se=Array(ce);for(var Ae=0;Ae<ce;Ae++)se[Ae]=arguments[Ae+2];G.children=se}return{$$typeof:a,type:q.type,key:de,ref:ge,props:G,_owner:be}},Ne.createContLine truncated
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var Lm;function ik(){if(Lm)return br;Lm=1;var a=Tu(),r=Symbol.for("react.element"),o=Symbol.for("react.fragment"),s=Object.prototype.hasOwnProperty,u=a.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l={key:!0,ref:!0,__self:!0,__source:!0};function m(c,h,d){var y,g={},f=null,z=null;d!==void 0&&(f=""+d),h.key!==void 0&&(f=""+h.key),h.ref!==void 0&&(z=h.ref);for(y in h)s.call(h,y)&&!l.hasOwnProperty(y)&&(g[y]=h[y]);if(c&&c.defaultProps)for(y in h=c.defaultProps,h)g[y]===void 0&&(g[y]=h[y]);return{$$typeof:r,type:c,key:f,ref:z,props:g,_owner:u.current}}return br.Fragment=o,br.jsx=m,br.jsxs=m,br}var Hm;function ak(){return Hm||(Hm=1,Xc.exports=ik()),Xc.exports}var B=ak(),S=Tu();const he=Fo(S),Pu=Jv({__proto__:null,default:he},[S]);var Ks={},eu={exports:{}},aa={},iu={exports:{}},au={};/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var Vm;function nk(){return Vm||(Vm=1,(function(a){function r($,oe){var ee=$.length;$.push(oe);e:for(;0<ee;){var q=ee-1>>>1,I=$[q];if(0<u(I,oe))$[q]=oe,$[ee]=I,ee=q;else break e}}function o($){return $.length===0?null:$[0]}function s($){if($.length===0)return null;var oe=$[0],ee=$.pop();if(ee!==oe){$[0]=ee;e:for(var q=0,I=$.length,L=I>>>1;q<L;){var G=2*(q+1)-1,de=$[G],ge=G+1,be=$[ge];if(0>u(de,ee))ge<I&&0>u(be,de)?($[q]=be,$[ge]=ee,q=ge):($[q]=de,$[G]=ee,q=G);else if(ge<I&&0>u(be,ee))$[q]=be,$[ge]=ee,q=ge;else break e}}return oe}function u($,oe){var ee=$.sortIndex-oe.sortIndex;return ee!==0?ee:$.id-oe.id}if(typeof performance=="object"&&typeof performance.now=="function"){var l=performance;a.unstable_now=function(){return l.now()}}else{var m=Date,c=m.now();a.unstable_now=function(){return m.now()-c}}var h=[],d=[],y=1,g=null,f=3,z=!1,k=!1,j=!1,v=typeof setTimeout=="function"?setTimeout:null,A=typeof clearTimeout=="function"?clearTimeout:null,C=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function w($){for(var oe=o(d);oe!==null;){if(oe.callback===null)s(d);else if(oe.startTime<=$)s(d),oe.sortIndex=oe.expirationTime,r(h,oe);else break;oe=o(d)}}function _($){if(j=!1,w($),!k)if(o(h)!==null)k=!0,X(x);else{var oe=o(d);oe!==null&&ne(_,oe.startTime-$)}}function x($,oe){k=!1,j&&(j=!1,A(F),F=-1),z=!0;var ee=f;try{for(w(oe),g=o(h);g!==null&&(!(g.expirationTime>oe)||$&&!T());){var q=g.callback;if(typeof q=="function"){g.callback=null,f=g.priorityLevel;var I=q(g.expirationTime<=oe);oe=a.unstable_now(),typeof I=="function"?g.callback=I:g===o(h)&&s(h),w(oe)}else s(h);g=o(h)}if(g!==null)var L=!0;else{var G=o(d);G!==null&&ne(_,G.startTime-oe),L=!1}return L}finally{g=null,f=ee,z=!1}}var D=!1,R=null,F=-1,O=5,N=-1;function T(){return!(a.unstable_now()-N<O)}function W(){if(R!==null){var $=a.unstable_now();N=$;var oe=!0;try{oe=R(!0,$)}finally{oe?H():(D=!1,R=null)}}else D=!1}var H;if(typeof C=="function")H=function(){C(W)};else if(typeof MessageChannel<"u"){var K=new MessageChannel,ae=K.port2;K.port1.onmessage=W,H=function(){ae.postMessage(null)}}else H=function(){v(W,0)};function X($){R=$,D||(D=!0,H())}function ne($,oe){F=v(function(){$(a.unstable_now())},oe)}a.unstable_IdlePriority=5,a.unstable_ImmediatePriority=1,a.unstable_LowPriority=4,a.unstable_NormalPriority=3,a.unstable_Profiling=null,a.unstable_UserBlockingPriority=2,a.unstable_cancelCallback=function($){$.callback=null},a.unstable_continueExecution=function(){k||z||(k=!0,X(x))},a.unstable_forceFrameRate=function($){0>$||125<$?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):O=0<$?Math.floor(1e3/$):5},a.unstable_getCurrentPriorityLevel=function(){return f},a.unstable_getFirstCallbackNode=function(){return o(h)},a.unstable_next=function($){switch(f){case 1:case 2:case 3:var oe=3;break;default:oe=f}var ee=f;f=oe;try{return $()}finally{f=ee}},a.unstable_pauseExecution=function(){},a.unstable_requestPaint=function(){},a.unstable_runWithPriority=function($,oe){switch($){case 1:case 2:case 3:case 4:case 5:break;default:$=3}var ee=f;f=$;try{return oe()}finally{f=ee}},a.unstable_scheduleCallback=function($,oe,ee){var q=a.unstable_now();switch(typeof ee=="object"&&ee!==null?(ee=ee.delay,ee=typeof ee=="number"&&0<ee?q+ee:q):ee=q,$){case 1:var I=-1;break;case 2:I=250;break;case 5:I=1073741823;break;case 4:I=1e4;break;default:I=5e3}return I=ee+I,$={id:y++,callback:oe,priorityLevel:$,startTime:ee,expirationTime:I,sortIndex:-1},ee>q?($.sortIndex=ee,r(d,$),o(h)===null&&$===o(d)&&(j?(A(F),F=-1):j=!0,ne(_,ee-q))):($.sortIndex=I,r(h,$),k||z||(k=!0,X(x))),$},a.unstable_shouldYield=T,a.unstable_wrapCallback=function($){var oe=f;return function(){var ee=f;f=oe;try{return $.apply(this,arguments)}finally{f=ee}}}})(au)),au}var Um;function ok(){return Um||(Um=1,iu.exports=nk()),iu.exports}/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var Wm;function rk(){if(Wm)return aa;Wm=1;var a=Tu(),r=ok();function o(e){for(var i="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n<arguments.length;n++)i+="&args[]="+encodeURIComponent(arguments[n]);return"Minified React error #"+e+"; visit "+i+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}var s=new Set,u={};function l(e,i){m(e,i),m(e+"Capture",i)}function m(e,i){for(u[e]=i,e=0;e<i.length;e++)s.add(i[e])}var c=!(typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),h=Object.prototype.hasOwnProperty,d=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,y={},g={};function f(e){return h.call(g,e)?!0:h.call(y,e)?!1:d.test(e)?g[e]=!0:(y[e]=!0,!1)}function z(e,i,n,t){if(n!==null&&n.type===0)return!1;switch(typeof i){case"function":case"symbol":return!0;case"boolean":return t?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function k(e,i,n,t){if(i===null||typeof i>"u"||z(e,i,n,t))return!0;if(t)return!1;if(n!==null)switch(n.type){case 3:return!i;case 4:return i===!1;case 5:return isNaN(i);case 6:return isNaN(i)||1>i}return!1}function j(e,i,n,t,p,b,E){this.acceptsBooleans=i===2||i===3||i===4,this.attributeName=t,this.attributeNamespace=p,this.mustUseProperty=n,this.propertyName=e,this.type=i,this.sanitizeURL=b,this.removeEmptyString=E}var v={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){v[e]=new j(e,0,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var i=e[0];v[i]=new j(i,1,!1,e[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){v[e]=new j(e,2,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){v[e]=new j(e,2,!1,e,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){v[e]=new j(e,3,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){v[e]=new j(e,3,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){v[e]=new j(e,4,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){v[e]=new j(e,6,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){v[e]=new j(e,5,!1,e.toLowerCase(),null,!1,!1)});var A=/[\-:]([a-z])/g;function C(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var i=e.replace(A,C);v[i]=new j(i,1,!1,e,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var i=e.replace(A,C);v[i]=new j(i,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var i=e.replace(A,C);v[i]=new j(i,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){v[e]=new j(e,1,!1,e.toLowerCase(),null,!1,!1)}),v.xlinkHref=new j("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){v[e]=new j(e,1,!1,e.toLowerCase(),null,!0,!0)});function w(e,i,n,t){var p=v.hasOwnProperty(i)?v[i]:null;(p!==null?p.type!==0:t||!(2<i.length)||i[0]!=="o"&&i[0]!=="O"||i[1]!=="n"&&i[1]!= Line truncated
`+q+e}var L=!1;function G(e,i){if(!e||L)return"";L=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(i)if(i=function(){throw Error()},Object.defineProperty(i.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(i,[])}catch(Z){var t=Z}Reflect.construct(e,[],i)}else{try{i.call()}catch(Z){t=Z}e.call(i.prototype)}else{try{throw Error()}catch(Z){t=Z}e()}}catch(Z){if(Z&&t&&typeof Z.stack=="string"){for(var p=Z.stack.split(`
`),b=t.stack.split(`
`),E=p.length-1,P=b.length-1;1<=E&&0<=P&&p[E]!==b[P];)P--;for(;1<=E&&0<=P;E--,P--)if(p[E]!==b[P]){if(E!==1||P!==1)do if(E--,P--,0>P||p[E]!==b[P]){var M=`
`+p[E].replace(" at new "," at ");return e.displayName&&M.includes("<anonymous>")&&(M=M.replace("<anonymous>",e.displayName)),M}while(1<=E&&0<=P);break}}}finally{L=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?I(e):""}function de(e){switch(e.tag){case 5:return I(e.type);case 16:return I("Lazy");case 13:return I("Suspense");case 19:return I("SuspenseList");case 0:case 2:case 15:return e=G(e.type,!1),e;case 11:return e=G(e.type.render,!1),e;case 1:return e=G(e.type,!0),e;default:return""}}function ge(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 R:return"Fragment";case D:return"Portal";case O:return"Profiler";case F:return"StrictMode";case H:return"Suspense";case K:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case T:return(e.displayName||"Context")+".Consumer";case N:return(e._context.displayName||"Context")+".Provider";case W:var i=e.render;return e=e.displayName,e||(e=i.displayName||i.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case ae:return i=e.displayName||null,i!==null?i:ge(e.type)||"Memo";case X:i=e._payload,e=e._init;try{return ge(e(i))}catch{}}return null}function be(e){var i=e.type;switch(e.tag){case 24:return"Cache";case 9:return(i.displayName||"Context")+".Consumer";case 10:return(i._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=i.render,e=e.displayName||e.name||"",i.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return i;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return ge(i);case 8:return i===F?"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 i=="function")return i.displayName||i.name||null;if(typeof i=="string")return i}return null}function se(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function ce(e){var i=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(i==="checkbox"||i==="radio")}function Ae(e){var i=ce(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,i),t=""+e[i];if(!e.hasOwnProperty(i)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var p=n.get,b=n.set;return Object.defineProperty(e,i,{configurable:!0,get:function(){return p.call(this)},set:function(E){t=""+E,b.call(this,E)}}),Object.defineProperty(e,i,{enumerable:n.enumerable}),{getValue:function(){return t},setValue:function(E){t=""+E},stopTracking:function(){e._valueTracker=null,delete e[i]}}}}function Fe(e){e._valueTracker||(e._valueTracker=Ae(e))}function He(e){if(!e)return!1;var i=e._valueTracker;if(!i)return!0;var n=i.getValue(),t="";return e&&(t=ce(e)?e.checked?"true":"false":e.value),e=t,e!==n?(i.setValue(e),!0):!1}function ai(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 Ge(e,i){var n=i.checked;return ee({},i,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Oe(e,i){var n=i.defaultValue==null?"":i.defaultValue,t=i.checked!=null?i.checked:i.defaultChecked;n=se(i.value!=null?i.value:n),e._wrapperState={initialChecked:t,initialValue:n,controlled:i.type==="checkbox"||i.type==="radio"?i.checked!=null:i.value!=null}}function Me(e,i){i=i.checked,i!=null&&w(e,"checked",i,!1)}function Xe(e,i){Me(e,i);var n=se(i.value),t=i.type;if(n!=null)t==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(t==="submit"||t==="reset"){e.removeAttribute("value");return}i.hasOwnProperty("value")?Qe(e,i.type,n):i.hasOwnProperty("defaultValue")&&Qe(e,i.type,se(i.defaultValue)),i.checked==null&&i.defaultChecked!=null&&(e.defaultChecked=!!i.defaultChecked)}function Ve(e,i,n){if(i.hasOwnProperty("value")||i.hasOwnProperty("defaultValue")){var t=i.type;if(!(t!=="submit"&&t!=="reset"||i.value!==void 0&&i.value!==null))return;i=""+e._wrapperState.initialValue,n||i===e.value||(e.value=i),e.defaultValue=i}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function Qe(e,i,n){(i!=="number"||ai(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var yi=Array.isArray;function si(e,i,n,t){if(e=e.options,i){i={};for(var p=0;p<n.length;p++)i["$"+n[p]]=!0;for(n=0;n<e.length;n++)p=i.hasOwnProperty("$"+e[n].value),e[n].selected!==p&&(e[n].selected=p),p&&t&&(e[n].defaultSelected=!0)}else{for(n=""+se(n),i=null,p=0;p<e.length;p++){if(e[p].value===n){e[p].selected=!0,t&&(e[p].defaultSelected=!0);return}i!==null||e[p].disabled||(i=e[p])}i!==null&&(i.selected=!0)}}function Le(e,i){if(i.dangerouslySetInnerHTML! Line truncated
`).replace(uv,"")}function ls(e,i,n){if(i=op(i),op(e)!==i&&n)throw Error(o(425))}function cs(){}var Hl=null,Vl=null;function Ul(e,i){return e==="textarea"||e==="noscript"||typeof i.children=="string"||typeof i.children=="number"||typeof i.dangerouslySetInnerHTML=="object"&&i.dangerouslySetInnerHTML!==null&&i.dangerouslySetInnerHTML.__html!=null}var Wl=typeof setTimeout=="function"?setTimeout:void 0,dv=typeof clearTimeout=="function"?clearTimeout:void 0,rp=typeof Promise=="function"?Promise:void 0,pv=typeof queueMicrotask=="function"?queueMicrotask:typeof rp<"u"?function(e){return rp.resolve(null).then(e).catch(mv)}:Wl;function mv(e){setTimeout(function(){throw e})}function $l(e,i){var n=i,t=0;do{var p=n.nextSibling;if(e.removeChild(n),p&&p.nodeType===8)if(n=p.data,n==="/$"){if(t===0){e.removeChild(p),Uo(i);return}t--}else n!=="$"&&n!=="$?"&&n!=="$!"||t++;n=p}while(n);Uo(i)}function un(e){for(;e!=null;e=e.nextSibling){var i=e.nodeType;if(i===1||i===3)break;if(i===8){if(i=e.data,i==="$"||i==="$!"||i==="$?")break;if(i==="/$")return null}}return e}function sp(e){e=e.previousSibling;for(var i=0;e;){if(e.nodeType===8){var n=e.data;if(n==="$"||n==="$!"||n==="$?"){if(i===0)return e;i--}else n==="/$"&&i++}e=e.previousSibling}return null}var so=Math.random().toString(36).slice(2),Fa="__reactFiber$"+so,ir="__reactProps$"+so,Va="__reactContainer$"+so,Gl="__reactEvents$"+so,hv="__reactListeners$"+so,gv="__reactHandles$"+so;function Sn(e){var i=e[Fa];if(i)return i;for(var n=e.parentNode;n;){if(i=n[Va]||n[Fa]){if(n=i.alternate,i.child!==null||n!==null&&n.child!==null)for(e=sp(e);e!==null;){if(n=e[Fa])return n;e=sp(e)}return i}e=n,n=e.parentNode}return null}function ar(e){return e=e[Fa]||e[Va],!e||e.tag!==5&&e.tag!==6&&e.tag!==13&&e.tag!==3?null:e}function to(e){if(e.tag===5||e.tag===6)return e.stateNode;throw Error(o(33))}function us(e){return e[ir]||null}var Ql=[],lo=-1;function dn(e){return{current:e}}function li(e){0>lo||(e.current=Ql[lo],Ql[lo]=null,lo--)}function ri(e,i){lo++,Ql[lo]=e.current,e.current=i}var pn={},Li=dn(pn),Zi=dn(!1),Bn=pn;function co(e,i){var n=e.type.contextTypes;if(!n)return pn;var t=e.stateNode;if(t&&t.__reactInternalMemoizedUnmaskedChildContext===i)return t.__reactInternalMemoizedMaskedChildContext;var p={},b;for(b in n)p[b]=i[b];return t&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=i,e.__reactInternalMemoizedMaskedChildContext=p),p}function Xi(e){return e=e.childContextTypes,e!=null}function ds(){li(Zi),li(Li)}function tp(e,i,n){if(Li.current!==pn)throw Error(o(168));ri(Li,i),ri(Zi,n)}function lp(e,i,n){var t=e.stateNode;if(i=i.childContextTypes,typeof t.getChildContext!="function")return n;t=t.getChildContext();for(var p in t)if(!(p in i))throw Error(o(108,be(e)||"Unknown",p));return ee({},n,t)}function ps(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||pn,Bn=Li.current,ri(Li,e),ri(Zi,Zi.current),!0}function cp(e,i,n){var t=e.stateNode;if(!t)throw Error(o(169));n?(e=lp(e,i,Bn),t.__reactInternalMemoizedMergedChildContext=e,li(Zi),li(Li),ri(Li,e)):li(Zi),ri(Zi,n)}var Ua=null,ms=!1,Kl=!1;function up(e){Ua===null?Ua=[e]:Ua.push(e)}function yv(e){ms=!0,up(e)}function mn(){if(!Kl&&Ua!==null){Kl=!0;var e=0,i=ii;try{var n=Ua;for(ii=1;e<n.length;e++){var t=n[e];do t=t(!0);while(t!==null)}Ua=null,ms=!1}catch(p){throw Ua!==null&&(Ua=Ua.slice(e+1)),pd(bl,mn),p}finally{ii=i,Kl=!1}}return null}var uo=[],po=0,hs=null,gs=0,pa=[],ma=0,_n=null,Wa=1,$a="";function Dn(e,i){uo[po++]=gs,uo[po++]=hs,hs=e,gs=i}function dp(e,i,n){pa[ma++]=Wa,pa[ma++]=$a,pa[ma++]=_n,_n=e;var t=Wa;e=$a;var p=32-wa(t)-1;t&=~(1<<p),n+=1;var b=32-wa(i)+p;if(30<b){var E=p-p%5;b=(t&(1<<E)-1).toString(32),t>>=E,p-=E,Wa=1<<32-wa(i)+p|n<<p|t,$a=b+e}else Wa=1<<b|n<<p|t,$a=e}function Yl(e){e.return!==null&&(Dn(e,1),dp(e,1,0))}function Zl(e){for(;e===hs;)hs=uo[--po],uo[po]=null,gs=uo[--po],uo[po]=null;for(;e===_n;)_n=pa[--ma],pa[ma]=null,$a=pa[--ma],pa[ma]=null,Wa=pa[--ma],pa[ma]=null}var sa=null,ta=null,ci=!1,za=null;function pp(e,i){var n=ba(5,null,null,0);n.elementType="DELETED",n.stateNode=i,n.return=e,i=e.deletions,i===null?(e.deletions=[n],e.flags|=16):i.push(n)}function mp(e,i){switch(e.tag){case 5:var n=e.type;return i=i.nodeType!==1||n.toLowerCase()!==i.nodeName.toLowerCase()?null:i,i!==null?(e.stateNode=i,sa=e,ta=un(i.firstChild),!0):!1;case 6:return i=e.pendingProps===""||i.nodeType!==3?null:i,i!==null?(e.stateNode=i,sa=e,ta=null,!0):!1;case 13:return i=i.nodeType!==8?null:i,i!==null?(n=_n!==null?{id:Wa,overflow:$a}:null,e.memoizedState={dehydrated:i,treeContext:n,retryLane:1073741824},n=ba(18,null,null,0),n.stateNode=i,n.return=e,e.child=n,sa=e,ta=null,!0):!1;default:return!1}}function Xl(e){return(e.mode&1)!==0&&(e.flags&128)===0}function Jl(e){if(ci){var i=ta;if(i){var n=i;if(!mp(e,i)){if(Xl(e))throw Error(o(418));i=un(n.nextSibling);var t=sa;i&&mp(e,i)?pp(t,n):(e.flags=e.flags&-4097|2,ci=!1,sa=e)}}else{if(Xl(e))throw Error(o(418));e.flags=e.flags&-4097|2,ci=!1,sa=e}}}function hp(e){for(e=e Line truncated
Error generating stack: `+b.message+`
`+b.stack}return{value:e,source:i,stack:p,digest:null}}function wc(e,i,n){return{value:e,source:null,stack:n??null,digest:i??null}}function xc(e,i){try{console.error(i.value)}catch(n){setTimeout(function(){throw n})}}var jv=typeof WeakMap=="function"?WeakMap:Map;function Qp(e,i,n){n=Qa(-1,n),n.tag=3,n.payload={element:null};var t=i.value;return n.callback=function(){Ts||(Ts=!0,Nc=t),xc(e,i)},n}function Kp(e,i,n){n=Qa(-1,n),n.tag=3;var t=e.type.getDerivedStateFromError;if(typeof t=="function"){var p=i.value;n.payload=function(){return t(p)},n.callback=function(){xc(e,i)}}var b=e.stateNode;return b!==null&&typeof b.componentDidCatch=="function"&&(n.callback=function(){xc(e,i),typeof t!="function"&&(bn===null?bn=new Set([this]):bn.add(this));var E=i.stack;this.componentDidCatch(i.value,{componentStack:E!==null?E:""})}),n}function Yp(e,i,n){var t=e.pingCache;if(t===null){t=e.pingCache=new jv;var p=new Set;t.set(i,p)}else p=t.get(i),p===void 0&&(p=new Set,t.set(i,p));p.has(n)||(p.add(n),e=Mv.bind(null,e,i,n),i.then(e,e))}function Zp(e){do{var i;if((i=e.tag===13)&&(i=e.memoizedState,i=i!==null?i.dehydrated!==null:!0),i)return e;e=e.return}while(e!==null);return null}function Xp(e,i,n,t,p){return(e.mode&1)===0?(e===i?e.flags|=65536:(e.flags|=128,n.flags|=131072,n.flags&=-52805,n.tag===1&&(n.alternate===null?n.tag=17:(i=Qa(-1,1),i.tag=2,gn(n,i,1))),n.lanes|=1),e):(e.flags|=65536,e.lanes=p,e)}var Av=_.ReactCurrentOwner,Ji=!1;function $i(e,i,n,t){i.child=e===null?fp(i,null,n,t):ho(i,e.child,n,t)}function Jp(e,i,n,t,p){n=n.render;var b=i.ref;return yo(i,p),t=mc(e,i,n,t,b,p),n=hc(),e!==null&&!Ji?(i.updateQueue=e.updateQueue,i.flags&=-2053,e.lanes&=~p,Ka(e,i,p)):(ci&&n&&Yl(i),i.flags|=1,$i(e,i,t,p),i.child)}function em(e,i,n,t,p){if(e===null){var b=n.type;return typeof b=="function"&&!Uc(b)&&b.defaultProps===void 0&&n.compare===null&&n.defaultProps===void 0?(i.tag=15,i.type=b,im(e,i,b,t,p)):(e=Ls(n.type,null,t,i,i.mode,p),e.ref=i.ref,e.return=i,i.child=e)}if(b=e.child,(e.lanes&p)===0){var E=b.memoizedProps;if(n=n.compare,n=n!==null?n:Yo,n(E,t)&&e.ref===i.ref)return Ka(e,i,p)}return i.flags|=1,e=wn(b,t),e.ref=i.ref,e.return=i,i.child=e}function im(e,i,n,t,p){if(e!==null){var b=e.memoizedProps;if(Yo(b,t)&&e.ref===i.ref)if(Ji=!1,i.pendingProps=t=b,(e.lanes&p)!==0)(e.flags&131072)!==0&&(Ji=!0);else return i.lanes=e.lanes,Ka(e,i,p)}return zc(e,i,n,t,p)}function am(e,i,n){var t=i.pendingProps,p=t.children,b=e!==null?e.memoizedState:null;if(t.mode==="hidden")if((i.mode&1)===0)i.memoizedState={baseLanes:0,cachePool:null,transitions:null},ri(ko,la),la|=n;else{if((n&1073741824)===0)return e=b!==null?b.baseLanes|n:n,i.lanes=i.childLanes=1073741824,i.memoizedState={baseLanes:e,cachePool:null,transitions:null},i.updateQueue=null,ri(ko,la),la|=e,null;i.memoizedState={baseLanes:0,cachePool:null,transitions:null},t=b!==null?b.baseLanes:n,ri(ko,la),la|=t}else b!==null?(t=b.baseLanes|n,i.memoizedState=null):t=n,ri(ko,la),la|=t;return $i(e,i,p,n),i.child}function nm(e,i){var n=i.ref;(e===null&&n!==null||e!==null&&e.ref!==n)&&(i.flags|=512,i.flags|=2097152)}function zc(e,i,n,t,p){var b=Xi(n)?Bn:Li.current;return b=co(i,b),yo(i,p),n=mc(e,i,n,t,b,p),t=hc(),e!==null&&!Ji?(i.updateQueue=e.updateQueue,i.flags&=-2053,e.lanes&=~p,Ka(e,i,p)):(ci&&t&&Yl(i),i.flags|=1,$i(e,i,n,p),i.child)}function om(e,i,n,t,p){if(Xi(n)){var b=!0;ps(i)}else b=!1;if(yo(i,p),i.stateNode===null)Bs(e,i),$p(i,n,t),kc(i,n,t,p),t=!0;else if(e===null){var E=i.stateNode,P=i.memoizedProps;E.props=P;var M=E.context,Z=n.contextType;typeof Z=="object"&&Z!==null?Z=ha(Z):(Z=Xi(n)?Bn:Li.current,Z=co(i,Z));var le=n.getDerivedStateFromProps,ue=typeof le=="function"||typeof E.getSnapshotBeforeUpdate=="function";ue||typeof E.UNSAFE_componentWillReceiveProps!="function"&&typeof E.componentWillReceiveProps!="function"||(P!==t||M!==Z)&&Gp(i,E,t,Z),hn=!1;var te=i.memoizedState;E.state=te,ws(i,t,E,p),M=i.memoizedState,P!==t||te!==M||Zi.current||hn?(typeof le=="function"&&(vc(i,n,le,t),M=i.memoizedState),(P=hn||Wp(i,n,P,t,te,M,Z))?(ue||typeof E.UNSAFE_componentWillMount!="function"&&typeof E.componentWillMount!="function"||(typeof E.componentWillMount=="function"&&E.componentWillMount(),typeof E.UNSAFE_componentWillMount=="function"&&E.UNSAFE_componentWillMount()),typeof E.componentDidMount=="function"&&(i.flags|=4194308)):(typeof E.componentDidMount=="function"&&(i.flags|=4194308),i.memoizedProps=t,i.memoizedState=M),E.props=t,E.state=M,E.context=Z,t=P):(typeof E.componentDidMount=="function"&&(i.flags|=4194308),t=!1)}else{E=i.stateNode,kp(e,i),P=i.memoizedProps,Z=i.type===i.elementType?P:qa(i.type,P),E.props=Z,ue=i.pendingProps,te=E.context,M=n.contextType,typeof M=="object"&&M!==null?M=ha(M):(M=Xi(n)?Bn:Li.current,M=co(i,M));var ve=n.getDerivedStateFromProps;(le=typeof ve=="function"||typeof E.getSnapshotBeforeUpdate=="function")||typeof E.UNSAFE_componentWillReceiveProps!="function"&&typeof E.componentWillReceiveProps!="function"||(P!==ue||te!==M)&&Gp(i,E,t,M),hn=!1,te=i.memoizedState,E Line truncated
* @license lucide-react v0.562.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const uk=a=>a.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),dk=a=>a.replace(/^([A-Z])|[\s-_]+(\w)/g,(r,o,s)=>s?s.toUpperCase():o.toLowerCase()),Qm=a=>{const r=dk(a);return r.charAt(0).toUpperCase()+r.slice(1)},Wg=(...a)=>a.filter((r,o,s)=>!!r&&r.trim()!==""&&s.indexOf(r)===o).join(" ").trim(),pk=a=>{for(const r in a)if(r.startsWith("aria-")||r==="role"||r==="title")return!0};/**
* @license lucide-react v0.562.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/var mk={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/**
* @license lucide-react v0.562.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const hk=S.forwardRef(({color:a="currentColor",size:r=24,strokeWidth:o=2,absoluteStrokeWidth:s,className:u="",children:l,iconNode:m,...c},h)=>S.createElement("svg",{ref:h,...mk,width:r,height:r,stroke:a,strokeWidth:s?Number(o)*24/Number(r):o,className:Wg("lucide",u),...!l&&!pk(c)&&{"aria-hidden":"true"},...c},[...m.map(([d,y])=>S.createElement(d,y)),...Array.isArray(l)?l:[l]]));/**
* @license lucide-react v0.562.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const Nu=(a,r)=>{const o=S.forwardRef(({className:s,...u},l)=>S.createElement(hk,{ref:l,iconNode:r,className:Wg(`lucide-${uk(Qm(a))}`,`lucide-${a}`,s),...u}));return o.displayName=Qm(a),o};/**
* @license lucide-react v0.562.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const gk=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],yk=Nu("check",gk);/**
* @license lucide-react v0.562.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const bk=[["path",{d:"M4 5h16",key:"1tepv9"}],["path",{d:"M4 12h16",key:"1lakjw"}],["path",{d:"M4 19h16",key:"1djgab"}]],fk=Nu("menu",bk);/**
* @license lucide-react v0.562.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const vk=[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]],$g=Nu("x",vk);var Gg=Ug();const Qg=Fo(Gg);function kk(a){if(typeof document>"u")return;let r=document.head||document.getElementsByTagName("head")[0],o=document.createElement("style");o.type="text/css",r.appendChild(o),o.styleSheet?o.styleSheet.cssText=a:o.appendChild(document.createTextNode(a))}const wk=a=>{switch(a){case"success":return qk;case"info":return Ak;case"warning":return jk;case"error":return Ck;default:return null}},xk=Array(12).fill(0),zk=({visible:a,className:r})=>he.createElement("div",{className:["sonner-loading-wrapper",r].filter(Boolean).join(" "),"data-visible":a},he.createElement("div",{className:"sonner-spinner"},xk.map((o,s)=>he.createElement("div",{className:"sonner-loading-bar",key:`spinner-bar-${s}`})))),qk=he.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",fill:"currentColor",height:"20",width:"20"},he.createElement("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z",clipRule:"evenodd"})),jk=he.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor",height:"20",width:"20"},he.createElement("path",{fillRule:"evenodd",d:"M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z",clipRule:"evenodd"})),Ak=he.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",fill:"currentColor",height:"20",width:"20"},he.createElement("path",{fillRule:"evenodd",d:"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z",clipRule:"evenodd"})),Ck=he.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",fill:"currentColor",height:"20",width:"20"},he.createElement("path",{fillRule:"evenodd",d:"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z",clipRule:"evenodd"})),Ek=he.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"},he.createElement("line",{x1:"18",y1:"6",x2:"6",y2:"18"}),he.createElement("line",{x1:"6",y1:"6",x2:"18",y2:"18"})),Sk=()=>{const[a,r]=he.useState(document.hidden);return he.useEffect(()=>{const o=()=>{r(document.hidden)};return document.addEventListener("visibilitychange",o),()=>window.removeEventListener("visibilitychange",o)},[]),a};let ku=1;class Bk{constructor(){this.subscribe=r=>(this.subscribers.push(r),()=>{const o=this.subscribers.indexOf(r);this.subscribers.splice(o,1)}),this.publish=r=>{this.subscribers.forEach(o=>o(r))},this.addToast=r=>{this.publish(r),this.toasts=[...this.toasts,r]},this.create=r=>{var o;const{message:s,...u}=r,l=typeof(r==null?void 0:r.id)=="number"||((o=r.id)==null?void 0:o.length)>0?r.id:ku++,m=this.toasts.find(h=>h.id===l),c=r.dismissible===void 0?!0:r.dismissible;return this.dismissedToasts.has(l)&&this.dismissedToasts.delete(l),m?this.toasts=this.toasts.map(h=>h.id===l?(this.publish({...h,...r,id:l,title:s}),{...h,...r,id:l,dismissible:c,title:s}):h):this.addToast({title:s,...u,dismissible:c,id:l}),l},this.dismiss=r=>(r?(this.dismissedToasts.add(r),requestAnimationFrame(()=>this.subscribers.forEach(o=>o({id:r,dismiss:!0})))):this.toasts.forEach(o=>{this.subscribers.forEach(s=>s({id:o.id,dismiss:!0}))}),r),this.message=(r,o)=>this.create({...o,message:r}),this.error=(r,o)=>this.create({...o,message:r,type:"error"}),this.success=(r,o)=>this.create({...o,type:"success",message:r}),this.info=(r,o)=>this.create({...o,type:"info",message:r}),this.warning=(r,o)=>this.create({...o,type:"warning",message:r}),this.loading=(r,o)=>this.create({...o,type:"loading",message:r}),this.promise=(r,o)=>{if(!o)return;let s;o.loading!==void 0&&(s=this.create({...o,promise:r,type:"loading",message:o.loading,description:typeof o.description!="function"?o.description:void 0}));const u=Promise.resolve(r instanceof Function?r():r);let l=s!==void 0,m;const c=u.then(async d=>{if(m=["resolve",d],he.isValidElement(d))l=!1,this.create({id:s,type:"default",message:d});else if(Dk(d)&&!d.ok){l=!1;const g=typeof o.error=="function"?await o.error(`HTTP error! status: ${d.status}`):o.error,f=typeof o.description=="function"?await o.description(`HTTP error! status: ${d.status}`):o.description,k=typeof g=="object"&&!he.isValidElement(g)?g:{message:g};this.create({id:s,type:"error",description:f,...k})}else if(d instanceof Error){l=!1;const g=typeof o.error=="function"?await o.error(d):o.error,Line truncated
`).map((q,I,L)=>B.jsxs("span",{children:[nx(q,c),I<L.length-1&&B.jsx("br",{})]},I)),!X&&B.jsx("span",{className:"text-[10px] text-muted-foreground/50 ml-2",children:th(x.sender_timestamp||x.received_at)}),x.outgoing&&(x.acked>0?` ✓${x.acked>1?x.acked:""}`:" ?")]})]})]},x.id)})]}),g&&B.jsx("button",{onClick:j,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"})})})]})}const rx=S.forwardRef(function({onSend:r,disabled:o,placeholder:s,isRepeaterMode:u},l){const[m,c]=S.useState(""),[h,d]=S.useState(!1),y=S.useRef(null);S.useImperativeHandle(l,()=>({appendText:k=>{var j;c(v=>v+k),(j=y.current)==null||j.focus()}}));const g=S.useCallback(async k=>{k.preventDefault();const j=m.trim();if(u){if(h||o)return;const v=j==="."?"":j;d(!0);try{await r(v),c("")}catch(A){console.error("Failed to request telemetry:",A);return}finally{d(!1)}setTimeout(()=>{var A;return(A=y.current)==null?void 0:A.focus()},0)}else{if(!j||h||o)return;d(!0);try{await r(j),c("")}catch(v){console.error("Failed to send message:",v);return}finally{d(!1)}setTimeout(()=>{var v;return(v=y.current)==null?void 0:v.focus()},0)}},[m,h,o,r,u]),f=S.useCallback(k=>{k.key==="Enter"&&!k.shiftKey&&(k.preventDefault(),g(k))},[g]),z=u?m.trim().length>0||m===".":m.trim().length>0;return B.jsxs("form",{className:"px-4 py-3 border-t border-border flex gap-2",onSubmit:g,children:[B.jsx(zi,{ref:y,type:u?"password":"text",value:m,onChange:k=>c(k.target.value),onKeyDown:f,placeholder:s||(u?"Enter password (or . for none)...":"Type a message..."),disabled:o||h,className:"flex-1"}),B.jsx(La,{type:"submit",disabled:o||h||!z,children:h?u?"Fetching...":"Sending...":u?"Fetch":"Send"})]})});function Si(a,r,{checkForDefaultPrevented:o=!0}={}){return function(u){if(a==null||a(u),o===!1||!u.defaultPrevented)return r==null?void 0:r(u)}}function sx(a,r){const o=S.createContext(r),s=l=>{const{children:m,...c}=l,h=S.useMemo(()=>c,Object.values(c));return B.jsx(o.Provider,{value:h,children:m})};s.displayName=a+"Provider";function u(l){const m=S.useContext(o);if(m)return m;if(r!==void 0)return r;throw new Error(`\`${l}\` must be used within \`${a}\``)}return[s,u]}function Lr(a,r=[]){let o=[];function s(l,m){const c=S.createContext(m),h=o.length;o=[...o,m];const d=g=>{var A;const{scope:f,children:z,...k}=g,j=((A=f==null?void 0:f[a])==null?void 0:A[h])||c,v=S.useMemo(()=>k,Object.values(k));return B.jsx(j.Provider,{value:v,children:z})};d.displayName=l+"Provider";function y(g,f){var j;const z=((j=f==null?void 0:f[a])==null?void 0:j[h])||c,k=S.useContext(z);if(k)return k;if(m!==void 0)return m;throw new Error(`\`${g}\` must be used within \`${l}\``)}return[d,y]}const u=()=>{const l=o.map(m=>S.createContext(m));return function(c){const h=(c==null?void 0:c[a])||l;return S.useMemo(()=>({[`__scope${a}`]:{...c,[a]:h}}),[c,h])}};return u.scopeName=a,[s,tx(u,...r)]}function tx(...a){const r=a[0];if(a.length===1)return r;const o=()=>{const s=a.map(u=>({useScope:u(),scopeName:u.scopeName}));return function(l){const m=s.reduce((c,{useScope:h,scopeName:d})=>{const g=h(l)[`__scope${d}`];return{...c,...g}},{});return S.useMemo(()=>({[`__scope${r.scopeName}`]:m}),[m])}};return o.scopeName=r.scopeName,o}var _o=globalThis!=null&&globalThis.document?S.useLayoutEffect:()=>{},lx=Pu[" useId ".trim().toString()]||(()=>{}),cx=0;function Pr(a){const[r,o]=S.useState(lx());return _o(()=>{o(s=>s??String(cx++))},[a]),r?`radix-${r}`:""}var ux=Pu[" useInsertionEffect ".trim().toString()]||_o;function nl({prop:a,defaultProp:r,onChange:o=()=>{},caller:s}){const[u,l,m]=dx({defaultProp:r,onChange:o}),c=a!==void 0,h=c?a:u;{const y=S.useRef(a!==void 0);S.useEffect(()=>{const g=y.current;g!==c&&console.warn(`${s} is changing from ${g?"controlled":"uncontrolled"} to ${c?"controlled":"uncontrolled"}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`),y.current=c},[c,s])}const d=S.useCallback(y=>{var g;if(c){const f=px(y)?y(a):y;f!==a&&((g=m.current)==null||g.call(m,f))}else l(y)},[c,a,l,m]);return[h,d]}function dx({defaultProp:a,onChange:r}){const[o,s]=S.useState(a),u=S.useRef(o),l=S.useRef(r);return ux(()=>{l.current=r},[r]),S.useEffect(()=>{var m;u.current!==o&&((m=l.current)==null||m.call(l,o),u.current=o)},[o,u]),[o,s,l]}function px(a){return typeof a=="function"}function mx(a){const r=hx(a),o=S.forwardRef((s,u)=>{const{children:l,...m}=s,c=S.Children.toArray(l),h=c.find(yx);if(h){const d=h.props.children,y=c.map(g=>g===h?S.Children.count(d)>1?S.Children.only(null):S.isValidElement(d)?d.prLine truncated
.`.concat(Hx,` {
overflow: hidden `).concat(s,`;
padding-right: `).concat(c,"px ").concat(s,`;
}
body[`).concat(Eo,`] {
overflow: hidden `).concat(s,`;
overscroll-behavior: contain;
`).concat([r&&"position: relative ".concat(s,";"),o==="margin"&&`
padding-left: `.concat(u,`px;
padding-top: `).concat(l,`px;
padding-right: `).concat(m,`px;
margin-left:0;
margin-top:0;
margin-right: `).concat(c,"px ").concat(s,`;
`),o==="padding"&&"padding-right: ".concat(c,"px ").concat(s,";")].filter(Boolean).join(""),`
}
.`).concat(ot,` {
right: `).concat(c,"px ").concat(s,`;
}
.`).concat(rt,` {
margin-right: `).concat(c,"px ").concat(s,`;
}
.`).concat(ot," .").concat(ot,` {
right: 0 `).concat(s,`;
}
.`).concat(rt," .").concat(rt,` {
margin-right: 0 `).concat(s,`;
}
body[`).concat(Eo,`] {
`).concat(Vx,": ").concat(c,`px;
}
`)},yh=function(){var a=parseInt(document.body.getAttribute(Eo)||"0",10);return isFinite(a)?a:0},lz=function(){S.useEffect(function(){return document.body.setAttribute(Eo,(yh()+1).toString()),function(){var a=yh()-1;a<=0?document.body.removeAttribute(Eo):document.body.setAttribute(Eo,a.toString())}},[])},cz=function(a){var r=a.noRelative,o=a.noImportant,s=a.gapMode,u=s===void 0?"margin":s;lz();var l=S.useMemo(function(){return rz(u)},[u]);return S.createElement(sz,{styles:tz(l,!r,u,o?"":"!important")})},Au=!1;if(typeof window<"u")try{var et=Object.defineProperty({},"passive",{get:function(){return Au=!0,!0}});window.addEventListener("test",et,et),window.removeEventListener("test",et,et)}catch{Au=!1}var qo=Au?{passive:!1}:!1,uz=function(a){return a.tagName==="TEXTAREA"},qy=function(a,r){if(!(a instanceof Element))return!1;var o=window.getComputedStyle(a);return o[r]!=="hidden"&&!(o.overflowY===o.overflowX&&!uz(a)&&o[r]==="visible")},dz=function(a){return qy(a,"overflowY")},pz=function(a){return qy(a,"overflowX")},bh=function(a,r){var o=r.ownerDocument,s=r;do{typeof ShadowRoot<"u"&&s instanceof ShadowRoot&&(s=s.host);var u=jy(a,s);if(u){var l=Ay(a,s),m=l[1],c=l[2];if(m>c)return!0}s=s.parentNode}while(s&&s!==o.body);return!1},mz=function(a){var r=a.scrollTop,o=a.scrollHeight,s=a.clientHeight;return[r,o,s]},hz=function(a){var r=a.scrollLeft,o=a.scrollWidth,s=a.clientWidth;return[r,o,s]},jy=function(a,r){return a==="v"?dz(r):pz(r)},Ay=function(a,r){return a==="v"?mz(r):hz(r)},gz=function(a,r){return a==="h"&&r==="rtl"?-1:1},yz=function(a,r,o,s,u){var l=gz(a,window.getComputedStyle(r).direction),m=l*s,c=o.target,h=r.contains(c),d=!1,y=m>0,g=0,f=0;do{if(!c)break;var z=Ay(a,c),k=z[0],j=z[1],v=z[2],A=j-v-l*k;(k||A)&&jy(a,c)&&(g+=A,f+=k);var C=c.parentNode;c=C&&C.nodeType===Node.DOCUMENT_FRAGMENT_NODE?C.host:C}while(!h&&c!==document.body||h&&(r.contains(c)||r===c));return(y&&Math.abs(g)<1||!y&&Math.abs(f)<1)&&(d=!0),d},it=function(a){return"changedTouches"in a?[a.changedTouches[0].clientX,a.changedTouches[0].clientY]:[0,0]},fh=function(a){return[a.deltaX,a.deltaY]},vh=function(a){return a&&"current"in a?a.current:a},bz=function(a,r){return a[0]===r[0]&&a[1]===r[1]},fz=function(a){return`
.block-interactivity-`.concat(a,` {pointer-events: none;}
.allow-interactivity-`).concat(a,` {pointer-events: all;}
`)},vz=0,jo=[];function kz(a){var r=S.useRef([]),o=S.useRef([0,0]),s=S.useRef(),u=S.useState(vz++)[0],l=S.useState(zy)[0],m=S.useRef(a);S.useEffect(function(){m.current=a},[a]),S.useEffect(function(){if(a.inert){document.body.classList.add("block-interactivity-".concat(u));var j=Lx([a.lockRef.current],(a.shards||[]).map(vh),!0).filter(Boolean);return j.forEach(function(v){return v.classList.add("allow-interactivity-".concat(u))}),function(){document.body.classList.remove("block-interactivity-".concat(u)),j.forEach(function(v){return v.classList.remove("allow-interactivity-".concat(u))})}}},[a.inert,a.lockRef.current,a.shards]);var c=S.useCallback(function(j,v){if("touches"in j&&j.touches.length===2||j.type==="wheel"&&j.ctrlKey)return!m.current.allowPinchZoom;var A=it(j),C=o.current,w="deltaX"in j?j.deltaX:C[0]-A[0],_="deltaY"in j?j.deltaY:C[1]-A[1],x,D=j.target,R=Math.abs(w)>Math.abs(_)?"h":"v";if("touches"in j&&R==="h"&&D.type==="range")return!1;var F=window.getSelection(),O=F&&F.anchorNode,N=O?O===D||O.contains(D):!1;if(N)return!1;var T=bh(R,D);if(!T)return!0;if(T?x=R:(x=R==="v"?"h":"v",T=bh(R,D)),!T)return!1;if(!s.current&&"changedTouches"in j&&(w||_)&&(s.current=x),!x)return!0;var W=s.current||x;return yz(W,v,j,W==="h"?w:_)},[]),h=S.useCallback(function(j){var v=j;if(!(!jo.length||jo[jo.length-1]!==l)){var A="deltaY"in v?fh(v):it(v),C=r.current.filter(function(x){return x.name===v.type&&(x.target===v.target||v.target===x.shadowParent)&&bz(x.delta,A)})[0];if(C&&C.should){v.cancelable&&v.preventDefault();return}if(!C){var w=(m.current.shards||[]).map(vh).filter(Boolean).filter(function(x){return x.contains(v.target)}),_=w.length>0?c(v,w[0]):!m.current.noIsolation;_&&v.cancelable&&v.preventDefault()}}},[]),d=S.useCallback(function(j,v,A,C){var w={name:j,delta:v,target:A,should:C,shadowParent:wz(A)};r.current.push(w),setTimeout(function(){r.current=r.current.filter(function(_){return _!==w})},1)},[]),y=S.useCallback(function(j){o.current=it(j),s.current=void 0},[]),g=S.useCallback(function(j){d(j.type,fh(j),j.target,c(j,a.lockRef.current))},[]),f=S.useCallback(function(j){d(j.type,it(j),j.target,c(j,a.lockRef.current))},[]);S.useEffect(function(){return jo.push(l),a.setCallbacks({onScrollCapture:g,onWheelCapture:g,onTouchMoveCapture:f}),document.addEventListener("wheel",h,qo),document.addEventListener("touchmove",h,qo),document.addEventListener("touchstart",y,qo),function(){jo=jo.filter(function(j){return j!==l}),document.removeEventListener("wheel",h,qo),document.removeEventListener("touchmove",h,qo),document.removeEventListener("touchstart",y,qo)}},[]);var z=a.removeScrollBar,k=a.inert;return S.createElement(S.Fragment,null,k?S.createElement(l,{styles:fz(u)}):null,z?S.createElement(cz,{noRelative:a.noRelative,gapMode:a.gapMode}):null)}function wz(a){for(var r=null;a!==null;)a instanceof ShadowRoot&&(r=a.host,a=a.host),a=a.parentNode;return r}const xz=Yx(xy,kz);var Cy=S.forwardRef(function(a,r){return S.createElement(ol,Oa({},a,{ref:r,sideCar:xz}))});Cy.classNames=ol.classNames;var zz=function(a){if(typeof document>"u")return null;var r=Array.isArray(a)?a[0]:a;return r.ownerDocument.body},Ao=new WeakMap,at=new WeakMap,nt={},mu=0,Ey=function(a){return a&&(a.host||Ey(a.parentNode))},qz=function(a,r){return r.map(function(o){if(a.contains(o))return o;var s=Ey(o);return s&&a.contains(s)?s:(console.error("aria-hidden",o,"in not contained inside",a,". Doing nothing"),null)}).filter(function(o){return!!o})},jz=function(a,r,o,s){var u=qz(r,Array.isArray(a)?a:[a]);nt[o]||(nt[o]=new WeakMap);var l=nt[o],m=[],c=new Set,h=new Set(u),d=function(g){!g||c.has(g)||(c.add(g),d(g.parentNode))};u.forEach(d);var y=function(g){!g||h.has(g)||Array.prototype.forEach.call(g.children,function(f){if(c.has(f))y(f);else try{var z=f.getAttribute(s),k=z!==null&&z!=="false",j=(Ao.get(f)||0)+1,v=(l.get(f)||0)+1;Ao.set(f,j),l.set(f,v),m.push(f),j===1&&k&&at.set(f,!0),v===1&&f.setAttribute(o,"true"),k||f.setAttribute(s,"true")}catch(A){console.error("aria-hidden: cannot operate on ",f,A)}})};return y(r),c.clear(),mu++,function(){m.forEach(function(g){var f=Ao.get(g)-1,z=l.get(g)-1;Ao.set(g,f),l.set(g,z),f||(at.has(g)||g.removeAttribute(s),at.delete(g)),z||g.removeAttribute(o)}),mu--,mu||(Ao=new WeakMap,Ao=new WeakMap,at=new WeakMap,nt={})}},Az=function(a,r,o){o===void 0&&(o="data-aria-hidden");var s=Array.from(Array.isArray(a)?a:[a]),u=zz(a);return u?(s.push.apply(s,Array.from(u.querySelectorAll("[aria-live], script"))),jz(s,u,o,"aria-hidden")):function(){return null}};function Cz(a){const r=Ez(a),o=S.forwardRef((s,u)=>{const{children:l,...m}=s,c=S.Children.toArray(l),h=c.find(Bz);if(h){const d=h.props.children,y=c.map(g=>g===h?S.Children.count(d)>1?S.Children.only(null):S.isValidElement(d)?d.props.children:null:g);return B.jsx(r,{...m,ref:u,children:S.isValidElement(d)?S.cloneElement(d,void 0,y):null})}return B.jsx(r,{...m,ref:u,children:l})});return o.displayName=`${a}.Slot`,o}function Ez(a){const r=S.forwardRef((o,s)=>{const{children:u Line truncated
If you want to hide the \`${r.titleName}\`, you can wrap it with our VisuallyHidden component.
For more information, see https://radix-ui.com/primitives/docs/components/${r.docsSlug}`;return S.useEffect(()=>{a&&(document.getElementById(a)||console.error(o))},[o,a]),null},Lz="DialogDescriptionWarning",Hz=({contentRef:a,descriptionId:r})=>{const s=`Warning: Missing \`Description\` or \`aria-describedby={undefined}\` for {${Vy(Lz).contentName}}.`;return S.useEffect(()=>{var l;const u=(l=a.current)==null?void 0:l.getAttribute("aria-describedby");r&&u&&(document.getElementById(r)||console.warn(s))},[s,a,r]),null},Uy=By,Wy=Fy,sl=Ry,tl=Ty,ll=Ny,cl=My,$y=Ly;const Gy=Uy,Vz=Wy,Qy=S.forwardRef(({className:a,...r},o)=>B.jsx(sl,{ref:o,className:$e("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",a),...r}));Qy.displayName=sl.displayName;const Vu=S.forwardRef(({className:a,children:r,...o},s)=>B.jsxs(Vz,{children:[B.jsx(Qy,{}),B.jsxs(tl,{ref:s,className:$e("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",a),...o,children:[r,B.jsxs($y,{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($g,{className:"h-4 w-4"}),B.jsx("span",{className:"sr-only",children:"Close"})]})]})]}));Vu.displayName=tl.displayName;const Uu=({className:a,...r})=>B.jsx("div",{className:$e("flex flex-col space-y-1.5 text-center sm:text-left",a),...r});Uu.displayName="DialogHeader";const Wu=({className:a,...r})=>B.jsx("div",{className:$e("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",a),...r});Wu.displayName="DialogFooter";const $u=S.forwardRef(({className:a,...r},o)=>B.jsx(ll,{ref:o,className:$e("text-lg font-semibold leading-none tracking-tight",a),...r}));$u.displayName=ll.displayName;const Uz=S.forwardRef(({className:a,...r},o)=>B.jsx(cl,{ref:o,className:$e("text-sm text-muted-foreground",a),...r}));Uz.displayName=cl.displayName;function kh(a){const r=Wz(a),o=S.forwardRef((s,u)=>{const{children:l,...m}=s,c=S.Children.toArray(l),h=c.find(Gz);if(h){const d=h.props.children,y=c.map(g=>g===h?S.Children.count(d)>1?S.Children.only(null):S.isValidElement(d)?d.props.children:null:g);return B.jsx(r,{...m,ref:u,children:S.isValidElement(d)?S.cloneElement(d,void 0,y):null})}return B.jsx(r,{...m,ref:u,children:l})});return o.displayName=`${a}.Slot`,o}function Wz(a){const r=S.forwardRef((o,s)=>{const{children:u,...l}=o;if(S.isValidElement(u)){const m=Kz(u),c=Qz(l,u.props);return u.type!==S.Fragment&&(c.ref=s?Or(s,m):m),S.cloneElement(u,c)}return S.Children.count(u)>1?S.Children.only(null):null});return r.displayName=`${a}.SlotClone`,r}var $z=Symbol("radix.slottable");function Gz(a){return S.isValidElement(a)&&typeof a.type=="function"&&"__radixId"in a.type&&a.type.__radixId===$z}function Qz(a,r){const o={...r};for(const s in r){const u=a[s],l=r[s];/^on[A-Z]/.test(s)?u&&l?o[s]=(...c)=>{const h=l(...c);return u(...c),h}:u&&(o[s]=u):s==="style"?o[s]={...u,...l}:s==="className"&&(o[s]=[u,l].filter(Boolean).join(" "))}return{...a,...o}}function Kz(a){var s,u;let r=(s=Object.getOwnPropertyDescriptor(a.props,"ref"))==null?void 0:s.get,o=r&&"isReactWarning"in r&&r.isReactWarning;return o?a.ref:(r=(u=Object.getOwnPropertyDescriptor(a,"ref"))==null?void 0:u.get,o=r&&"isReactWarning"in r&&r.isReactWarning,o?a.props.ref:a.props.ref||a.ref)}function Yz(a){const r=a+"CollectionProvider",[o,s]=Lr(r),[u,l]=o(r,{collectionRef:{current:null},itemMap:new Map}),m=j=>{const{scope:v,children:A}=j,C=he.useRef(null),w=he.useRef(new Map).current;return B.jsx(u,{scope:v,itemMap:w,collectionRef:C,children:A})};m.displayName=r;const c=a+"CollectionSlot",h=kh(c),d=he.forwardRef((j,v)=>{const{scope:A,children:C}=j,w=l(c,A),_=fa(v,w.collectionRef);return B.jsx(h,{ref:_,children:C})});d.displayName=c;const y=a+"CollectionItemSlot",g="data-radix-collection-item",f=kh(y),z=he.forwardRef((j,v)=>{const{scope:A,children:C,...w}=j,_=he.useRef(null),x=fa(v,_),D=l(y,A);return he.useEffect(()=>(D.itemMap.set(_,{ref:_,...w}),()=>void D.itemMap.delete(_))),B.jsx(f,{[g]:"",ref:x,children:C})});z.displayName=y;function k(j){const v=l(a+"CollectionConsumer",j);return he.useCallback(()=>{const C=v.collectionRef.current;if(!C)return[];const w=Array.from(C.querySelectorAll(`[${g}]`));return Array.from(v.itemMap.values()).sort((D,R)=>w.indexOf(D.ref.current)-w.indexOf(R.ref.current))},[v.coll Line 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:
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/return(function(s){var u=o,l=u.lib,m=l.WordArray,c=l.Hasher,h=u.algo,d=m.create([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13]),y=m.create([5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11]),g=m.create([11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6]),f=m.create([8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11]),z=m.create([0,1518500249,1859775393,2400959708,2840853838]),k=m.create([1352829926,1548603684,1836072691,2053994217,0]),j=h.RIPEMD160=c.extend({_doReset:function(){this._hash=m.create([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(D,R){for(var F=0;F<16;F++){var O=R+F,N=D[O];D[O]=(N<<8|N>>>24)&16711935|(N<<24|N>>>8)&4278255360}var T=this._hash.words,W=z.words,H=k.words,K=d.words,ae=y.words,X=g.words,ne=f.words,$,oe,ee,q,I,L,G,de,ge,be;L=$=T[0],G=oe=T[1],de=ee=T[2],ge=q=T[3],be=I=T[4];for(var se,F=0;F<80;F+=1)se=$+D[R+K[F]]|0,F<16?se+=v(oe,ee,q)+W[0]:F<32?se+=A(oe,ee,q)+W[1]:F<48?se+=C(oe,ee,q)+W[2]:F<64?se+=w(oe,ee,q)+W[3]:se+=_(oe,ee,q)+W[4],se=se|0,se=x(se,X[F]),se=se+I|0,$=I,I=q,q=x(ee,10),ee=oe,oe=se,se=L+D[R+ae[F]]|0,F<16?se+=_(G,de,ge)+H[0]:F<32?se+=w(G,de,ge)+H[1]:F<48?se+=C(G,de,ge)+H[2]:F<64?se+=A(G,de,ge)+H[3]:se+=v(G,de,ge)+H[4],se=se|0,se=x(se,ne[F]),se=se+be|0,L=be,be=ge,ge=x(de,10),de=G,G=se;se=T[1]+ee+ge|0,T[1]=T[2]+q+be|0,T[2]=T[3]+I+L|0,T[3]=T[4]+$+G|0,T[4]=T[0]+oe+de|0,T[0]=se},_doFinalize:function(){var D=this._data,R=D.words,F=this._nDataBytes*8,O=D.sigBytes*8;R[O>>>5]|=128<<24-O%32,R[(O+64>>>9<<4)+14]=(F<<8|F>>>24)&16711935|(F<<24|F>>>8)&4278255360,D.sigBytes=(R.length+1)*4,this._process();for(var N=this._hash,T=N.words,W=0;W<5;W++){var H=T[W];T[W]=(H<<8|H>>>24)&16711935|(H<<24|H>>>8)&4278255360}return N},clone:function(){var D=c.clone.call(this);return D._hash=this._hash.clone(),D}});function v(D,R,F){return D^R^F}function A(D,R,F){return D&R|~D&F}function C(D,R,F){return(D|~R)^F}function w(D,R,F){return D&F|R&~F}function _(D,R,F){return D^(R|~F)}function x(D,R){return D<<R|D>>>32-R}u.RIPEMD160=c._createHelper(j),u.HmacRIPEMD160=c._createHmacHelper(j)})(),o.RIPEMD160})})(wt)),wt.exports}var xt={exports:{}},sq=xt.exports,Oh;function ml(){return Oh||(Oh=1,(function(a,r){(function(o,s){a.exports=s(Ie())})(sq,function(o){(function(){var s=o,u=s.lib,l=u.Base,m=s.enc,c=m.Utf8,h=s.algo;h.HMAC=l.extend({init:function(d,y){d=this._hasher=new d.init,typeof y=="string"&&(y=c.parse(y));var g=d.blockSize,f=g*4;y.sigBytes>f&&(y=d.finalize(y)),y.clamp();for(var z=this._oKey=y.clone(),k=this._iKey=y.clone(),j=z.words,v=k.words,A=0;A<g;A++)j[A]^=1549556828,v[A]^=909522486;z.sigBytes=k.sigBytes=f,this.reset()},reset:function(){var d=this._hasher;d.reset(),d.update(this._iKey)},update:function(d){return this._hasher.update(d),this},finalize:function(d){var y=this._hasher,g=y.finalize(d);y.reset();var f=y.finalize(this._oKey.clone().concat(g));return f}})})()})})(xt)),xt.exports}var zt={exports:{}},tq=zt.exports,Lh;function lq(){return Lh||(Lh=1,(function(a,r){(function(o,s,u){a.exports=s(Ie(),Vr(),ml())})(tq,function(o){return(function(){var s=o,u=s.lib,l=u.Base,m=u.WordArray,c=s.algo,h=c.SHA256,d=c.HMAC,y=c.PBKDF2=l.extend({cfg:l.extend({keySize:128/32,hasher:h,iterations:25e4}),init:function(g){this.cfg=this.cfg.extend(g)},compute:function(g,f){for(var z=this.cfg,k=d.create(z.hasher,g),j=m.create(),v=m.create([1]),A=j.words,C=v.words,w=z.keySize,_=z.iterations;A.length<w;){var x=k.update(f).finalize(v);k.reset();for(var D=x.words,R=D.length,F=x,O=1;O<_;O++){F=k.finalize(F),k.reset();for(var N=F.words,T=0;T<R;T++)D[T]^=N[T]}j.concat(x),C[0]++}return j.sigBytes=w*4,j}});s.PBKDF2=function(g,f,z){return y.create(z).compute(g,f)}})(),o.PBKDF2})})(zt)),zt.exports}var qt={exports:{}},cq=qt.exports,Hh;function Cn(){return Hh||(Hh=1,(function(a,r){(function(o,s,u){a.exports=s(Ie(),Bb(),ml())})(cq,function(o){return(function(){var s=o,u=s.lib,l=u.Base,m=u.WordArray,c=s.algo,h=c.MD5,d=c.EvpKDF=l.extend({cfg:l.extend({keySize:128/32,hasher:h,iterations:1}),init:function(y){this.cfg=this.cfg.extend(y)},compute:function(y,g){for(var f,z=this.cfg,k=z.hasher.create(),j=m.create(),v=j.words,A=z.keySize,C=z.iterations;v.length<A;){f&&k.update(f),f=k.update(y).finalize(g),k.reset();for(var w=1;w<C;w++)f=k.finalize(f),k.reset();j.concat(f)}return j.sigBytes=A*4,j}});s.EvpKDF=function(y,g,f){return d.create(f).compute(y,g)}})(),o.EvpKDF})})(qt)),qt.exports}var jt={exports:{}},uq=jt.exports,Vh;fuLine truncated
* 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 s=o.lib.BlockCipherMode.extend();function u(c){if((c>>24&255)===255){var h=c>>16&255,d=c>>8&255,y=c&255;h===255?(h=0,d===255?(d=0,y===255?y=0:++y):++d):++h,c=0,c+=h<<16,c+=d<<8,c+=y}else c+=1<<24;return c}function l(c){return(c[0]=u(c[0]))===0&&(c[1]=u(c[1])),c}var m=s.Encryptor=s.extend({processBlock:function(c,h){var d=this._cipher,y=d.blockSize,g=this._iv,f=this._counter;g&&(f=this._counter=g.slice(0),this._iv=void 0),l(f);var z=f.slice(0);d.encryptBlock(z,0);for(var k=0;k<y;k++)c[h+k]^=z[k]}});return s.Decryptor=m,s})(),o.mode.CTRGladman})})(Et)),Et.exports}var St={exports:{}},bq=St.exports,Gh;function fq(){return Gh||(Gh=1,(function(a,r){(function(o,s,u){a.exports=s(Ie(),Ri())})(bq,function(o){return o.mode.OFB=(function(){var s=o.lib.BlockCipherMode.extend(),u=s.Encryptor=s.extend({processBlock:function(l,m){var c=this._cipher,h=c.blockSize,d=this._iv,y=this._keystream;d&&(y=this._keystream=d.slice(0),this._iv=void 0),c.encryptBlock(y,0);for(var g=0;g<h;g++)l[m+g]^=y[g]}});return s.Decryptor=u,s})(),o.mode.OFB})})(St)),St.exports}var Bt={exports:{}},vq=Bt.exports,Qh;function kq(){return Qh||(Qh=1,(function(a,r){(function(o,s,u){a.exports=s(Ie(),Ri())})(vq,function(o){return o.mode.ECB=(function(){var s=o.lib.BlockCipherMode.extend();return s.Encryptor=s.extend({processBlock:function(u,l){this._cipher.encryptBlock(u,l)}}),s.Decryptor=s.extend({processBlock:function(u,l){this._cipher.decryptBlock(u,l)}}),s})(),o.mode.ECB})})(Bt)),Bt.exports}var _t={exports:{}},wq=_t.exports,Kh;function xq(){return Kh||(Kh=1,(function(a,r){(function(o,s,u){a.exports=s(Ie(),Ri())})(wq,function(o){return o.pad.AnsiX923={pad:function(s,u){var l=s.sigBytes,m=u*4,c=m-l%m,h=l+c-1;s.clamp(),s.words[h>>>2]|=c<<24-h%4*8,s.sigBytes+=c},unpad:function(s){var u=s.words[s.sigBytes-1>>>2]&255;s.sigBytes-=u}},o.pad.Ansix923})})(_t)),_t.exports}var Dt={exports:{}},zq=Dt.exports,Yh;function qq(){return Yh||(Yh=1,(function(a,r){(function(o,s,u){a.exports=s(Ie(),Ri())})(zq,function(o){return o.pad.Iso10126={pad:function(s,u){var l=u*4,m=l-s.sigBytes%l;s.concat(o.lib.WordArray.random(m-1)).concat(o.lib.WordArray.create([m<<24],1))},unpad:function(s){var u=s.words[s.sigBytes-1>>>2]&255;s.sigBytes-=u}},o.pad.Iso10126})})(Dt)),Dt.exports}var Ft={exports:{}},jq=Ft.exports,Zh;function Aq(){return Zh||(Zh=1,(function(a,r){(function(o,s,u){a.exports=s(Ie(),Ri())})(jq,function(o){return o.pad.Iso97971={pad:function(s,u){s.concat(o.lib.WordArray.create([2147483648],1)),o.pad.ZeroPadding.pad(s,u)},unpad:function(s){o.pad.ZeroPadding.unpad(s),s.sigBytes--}},o.pad.Iso97971})})(Ft)),Ft.exports}var Rt={exports:{}},Cq=Rt.exports,Xh;function Eq(){return Xh||(Xh=1,(function(a,r){(function(o,s,u){a.exports=s(Ie(),Ri())})(Cq,function(o){return o.pad.ZeroPadding={pad:function(s,u){var l=u*4;s.clamp(),s.sigBytes+=l-(s.sigBytes%l||l)},unpad:function(s){for(var u=s.words,l=s.sigBytes-1,l=s.sigBytes-1;l>=0;l--)if(u[l>>>2]>>>24-l%4*8&255){s.sigBytes=l+1;break}}},o.pad.ZeroPadding})})(Rt)),Rt.exports}var Tt={exports:{}},Sq=Tt.exports,Jh;function Bq(){return Jh||(Jh=1,(function(a,r){(function(o,s,u){a.exports=s(Ie(),Ri())})(Sq,function(o){return o.pad.NoPadding={pad:function(){},unpad:function(){}},o.pad.NoPadding})})(Tt)),Tt.exports}var Pt={exports:{}},_q=Pt.exports,eg;function Dq(){return eg||(eg=1,(function(a,r){(function(o,s,u){a.exports=s(Ie(),Ri())})(_q,function(o){return(function(s){var u=o,l=u.lib,m=l.CipherParams,c=u.enc,h=c.Hex,d=u.format;d.Hex={stringify:function(y){return y.ciphertext.toString(h)},parse:function(y){var g=h.parse(y);return m.create({ciphertext:g})}}})(),o.format.Hex})})(Pt)),Pt.exports}var Nt={exports:{}},Fq=Nt.exports,ig;function Rq(){return ig||(ig=1,(function(a,r){(function(o,s,u){a.exports=s(Ie(),Xn(),Jn(),Cn(),Ri())})(Fq,function(o){return(function(){var s=o,u=s.lib,l=u.BlockCipher,m=s.algo,c=[],h=[],d=[],y=[],g=[],f=[],z=[],k=[],j=[],v=[];(function(){for(var w=[],_=0;_<256;_++)_<128?w[_]=_<<1:w[_]=_<<1^283;for(var x=0,D=0,_=0;_<256;_++){var R=D^D<<1^D<<2^D<<3^D<<4;R=R>>>8^R&255^99,c[x]=R,h[R]=x;var F=w[x],O=w[F],N=w[O],T=w[R]*257^R*16843008;d[x]=T<<24|T>>>8,y[x]=T<<16|T>>>16,g[x]=T<<8|T>>>24,f[x]=T;var T=N*16843009^O*65537^F*257^x*16843008;z[R]=T<<24|T>>>8,k[R]=T<<16|T>>>16,j[R]=T<<8|T>>>24,v[R]=T,x?(x=F^w[w[w[N^F]]],D^=w[w[D]]):x=D=1}})();var A=[0,1,2,4,8,16,32,64,128,27,54],C=m.AES=l.extend({_doReset:function(){var w;if(!(this._nRounds&&this._keyPriorReset===this._key)){for(var _=this._keyPriorReset=this._key,x=_.words,D=_.sigBytes/4,R=this._nRounds=D+6,F=(R+1)*4,O=this._keySchedule=[],N=0;N<F;N++)N<D?O[N]=x[N]:(w=O[N-1],N%D?D>6&&N%D==4&&(w=c[w>>>24]<<24|c[w>>>16&255]<<16|c[w>>>8&255]<<8|c[w&255]):(w=w<<8|w>>>24,w=c[w>>>24]<<24|c[w>>>16&255]<<16|c[w>>>8&255]<<8|c[w&255],w^=A[N/D|0]<<24),O[N]=O[N-D]^w);for(var T=this._invKeySchedule=[],W=0;W<F;W++){var N=F-W;if(W%4)var w=O[N];else var w=O[N-4];W<4||N<=4?T[W]=w:T[W]=z[c[w>>>24]]^k[c[w>>>16&255]]^j[c[w>>>8&255]]^v[c[w&255]]}}},encryptBlock: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:c+h,endByte:c+u.length-1,value:f});const z={type:a.PayloadType.Request,version:a.PayloadVersion.Version1,isValid:!0,timestamp:0,requestType:a.RequestType.GetStats,requestData:"",destinationHash:d,sourceHash:y,cipherMac:g,ciphertext:f};return l!=null&&l.includeSegments&&(z.segments=m),z}catch(m){return{type:a.PayloadType.Request,version:a.PayloadVersion.Version1,isValid:!1,errors:[m instanceof Error?m.message:"Failed to decode request payload"],timestamp:0,requestType:a.RequestType.GetStats,requestData:"",destinationHash:"",sourceHash:"",cipherMac:"",ciphertext:""}}}}return jr.RequestPayloadDecoder=o,jr}var Ar={},qg;function yj(){if(qg)return Ar;qg=1,Object.defineProperty(Ar,"__esModule",{value:!0}),Ar.ResponsePayloadDecoder=void 0;const a=ua(),r=Ii();class o{static decode(u,l){try{if(u.length<4){const k={type:a.PayloadType.Response,version:a.PayloadVersion.Version1,isValid:!1,errors:["Response payload too short (minimum 4 bytes: dest + source + MAC)"],destinationHash:"",sourceHash:"",cipherMac:"",ciphertext:"",ciphertextLength:0};return l!=null&&l.includeSegments&&(k.segments=[{name:"Invalid Response Data",description:"Response payload too short (minimum 4 bytes required)",startByte:l.segmentOffset||0,endByte:(l.segmentOffset||0)+u.length-1,value:(0,r.bytesToHex)(u)}]),k}const m=[],c=(l==null?void 0:l.segmentOffset)||0;let h=0;const d=(0,r.byteToHex)(u[h]);l!=null&&l.includeSegments&&m.push({name:"Destination Hash",description:"First byte of destination node public key",startByte:c+h,endByte:c+h,value:d}),h+=1;const y=(0,r.byteToHex)(u[h]);l!=null&&l.includeSegments&&m.push({name:"Source Hash",description:"First byte of source node public key",startByte:c+h,endByte:c+h,value:y}),h+=1;const g=(0,r.bytesToHex)(u.subarray(h,h+2));l!=null&&l.includeSegments&&m.push({name:"Cipher MAC",description:"MAC for encrypted data in next field",startByte:c+h,endByte:c+h+1,value:g}),h+=2;const f=(0,r.bytesToHex)(u.subarray(h));l!=null&&l.includeSegments&&u.length>h&&m.push({name:"Ciphertext",description:"Encrypted response data (tag + content)",startByte:c+h,endByte:c+u.length-1,value:f});const z={type:a.PayloadType.Response,version:a.PayloadVersion.Version1,isValid:!0,destinationHash:d,sourceHash:y,cipherMac:g,ciphertext:f,ciphertextLength:u.length-4};return l!=null&&l.includeSegments&&(z.segments=m),z}catch(m){return{type:a.PayloadType.Response,version:a.PayloadVersion.Version1,isValid:!1,errors:[m instanceof Error?m.message:"Failed to decode response payload"],destinationHash:"",sourceHash:"",cipherMac:"",ciphertext:"",ciphertextLength:0}}}}return Ar.ResponsePayloadDecoder=o,Ar}var Cr={},jg;function bj(){if(jg)return Cr;jg=1,Object.defineProperty(Cr,"__esModule",{value:!0}),Cr.AnonRequestPayloadDecoder=void 0;const a=ua(),r=Ii();class o{static decode(u,l){try{if(u.length<35){const k={type:a.PayloadType.AnonRequest,version:a.PayloadVersion.Version1,isValid:!1,errors:["AnonRequest payload too short (minimum 35 bytes: dest + public key + MAC)"],destinationHash:"",senderPublicKey:"",cipherMac:"",ciphertext:"",ciphertextLength:0};return l!=null&&l.includeSegments&&(k.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:l.segmentOffset||0,endByte:(l.segmentOffset||0)+u.length-1,value:(0,r.bytesToHex)(u)}]),k}const m=[],c=(l==null?void 0:l.segmentOffset)||0;let h=0;const d=(0,r.byteToHex)(u[0]);l!=null&&l.includeSegments&&m.push({name:"Destination Hash",description:`First byte of destination node public key: 0x${d}`,startByte:c+h,endByte:c+h,value:d}),h+=1;const y=(0,r.bytesToHex)(u.subarray(1,33));l!=null&&l.includeSegments&&m.push({name:"Sender Public Key",description:"Ed25519 public key of the sender (32 bytes)",startByte:c+h,endByte:c+h+31,value:y}),h+=32;const g=(0,r.bytesToHex)(u.subarray(33,35));l!=null&&l.includeSegments&&m.push({name:"Cipher MAC",description:"MAC for encrypted data verification (2 bytes)",startByte:c+h,endByte:c+h+1,value:g}),h+=2;const f=(0,r.bytesToHex)(u.subarray(35));l!=null&&l.includeSegments&&u.length>35&&m.push({name:"Ciphertext",description:`Encrypted message data (${u.length-35} bytes). Contains encrypted plaintext with this structure:
• Timestamp (4 bytes) - send time as unix timestamp
• Sync Timestamp (4 bytes) - room server only, sender's "sync messages SINCE x" timestamp
• Password (remaining bytes) - password for repeater/room`,startByte:c+h,endByte:c+u.length-1,value:f});const z={type:a.PayloadType.AnonRequest,version:a.PayloadVersion.Version1,isValid:!0,destinationHash:d,senderPublicKey:y,cipherMac:g,ciphertext:f,ciphertextLength:u.length-35};return l!=null&&l.includeSegments&&(z.segments=m),z}catch(m){return{type:a.PayloadType.AnonRequest,version:a.PayloadVersion.Version1,isValid:!1,errors:[m instanceof Error?m.message:"Failed to decode AnonRequest payload"],destinationHash:"",senderPublicKey:"",cipherMac:"",ciphertext:"",ciphertextLength:0}}}}return Cr.AnonRequestPayloadDecoder=o,Cr}var Er={},Ag;function fj(){if(Ag)return Er;Ag=1,Object.defineProperty(Er,"__esModule",{value:!0}),Er.AckPayloadDecoder=void 0;const a=ua(),r=Ii();class o{static decode(u,l){try{if(u.length<4){const y={type:a.PayloadType.Ack,version:a.PayloadVersion.Version1,isValid:!1,errors:["Ack payload too short (minimum 4 bytes for checksum)"],checksum:""};return l!=null&&l.includeSegments&&(y.segments=[{name:"Invalid Ack Data",description:"Ack payload too short (minimum 4 bytes required for checksum)",startByte:l.segmentOffset||0,endByte:(l.segmentOffset||0)+u.length-1,value:(0,r.bytesToHex)(u)}]),y}const m=[],c=(l==null?void 0:l.segmentOffset)||0,h=(0,r.bytesToHex)(u.subarray(0,4));l!=null&&l.includeSegments&&m.push({name:"Checksum",description:`CRC checksum of message timestamp, text, and sender pubkey: 0x${h}`,startByte:c,endByte:c+3,value:h}),l!=null&&l.includeSegments&&u.length>4&&m.push({name:"Additional Data",description:"Extra data in Ack payload",startByte:c+4,endByte:c+u.length-1,value:(0,r.bytesToHex)(u.subarray(4))});const d={type:a.PayloadType.Ack,version:a.PayloadVersion.Version1,isValid:!0,checksum:h};return l!=null&&l.includeSegments&&(d.segments=m),d}catch(m){return{type:a.PayloadType.Ack,version:a.PayloadVersion.Version1,isValid:!1,errors:[m instanceof Error?m.message:"Failed to decode Ack payload"],checksum:""}}}}return Er.AckPayloadDecoder=o,Er}var Sr={},Cg;function vj(){if(Cg)return Sr;Cg=1,Object.defineProperty(Sr,"__esModule",{value:!0}),Sr.PathPayloadDecoder=void 0;const a=ua(),r=Ii();class o{static decode(u){try{if(u.length<2)return{type:a.PayloadType.Path,version:a.PayloadVersion.Version1,isValid:!1,errors:["Path payload too short (minimum 2 bytes: path length + extra type)"],pathLength:0,pathHashes:[],extraType:0,extraData:""};const l=u[0];if(u.length<1+l+1)return{type:a.PayloadType.Path,version:a.PayloadVersion.Version1,isValid:!1,errors:[`Path payload too short (need ${1+l+1} bytes for path length + path + extra type)`],pathLength:l,pathHashes:[],extraType:0,extraData:""};const m=[];for(let d=0;d<l;d++)m.push((0,r.byteToHex)(u[1+d]));const c=u[1+l];let h="";return u.length>1+l+1&&(h=(0,r.bytesToHex)(u.subarray(1+l+1))),{type:a.PayloadType.Path,version:a.PayloadVersion.Version1,isValid:!0,pathLength:l,pathHashes:m,extraType:c,extraData:h}}catch(l){return{type:a.PayloadType.Path,version:a.PayloadVersion.Version1,isValid:!1,errors:[l instanceof Error?l.message:"Failed to decode Path payload"],pathLength:0,pathHashes:[],extraType:0,extraData:""}}}}return Sr.PathPayloadDecoder=o,Sr}var Br={},Eg;function kj(){if(Eg)return Br;Eg=1,Object.defineProperty(Br,"__esModule",{value:!0}),Br.TextMessagePayloadDecoder=void 0;const a=ua(),r=Ii();class o{static decode(u,l){try{if(u.length<4){const k={type:a.PayloadType.TextMessage,version:a.PayloadVersion.Version1,isValid:!1,errors:["TextMessage payload too short (minimum 4 bytes: dest + source + MAC)"],destinationHash:"",sourceHash:"",cipherMac:"",ciphertext:"",ciphertextLength:0};return l!=null&&l.includeSegments&&(k.segments=[{name:"Invalid TextMessage Data",description:"TextMessage payload too short (minimum 4 bytes required)",startByte:l.segmentOffset||0,endByte:(l.segmentOffset||0)+u.length-1,value:(0,r.bytesToHex)(u)}]),k}const m=[],c=(l==null?void 0:l.segmentOffset)||0;let h=0;const d=(0,r.byteToHex)(u[h]);l!=null&&l.includeSegments&&m.push({name:"Destination Hash",description:"First byte of destination node public key",startByte:c+h,endByte:c+h,value:d}),h+=1;const y=(0,r.byteToHex)(u[h]);l!=null&&l.includeSegments&&m.push({name:"Source Hash",description:"First byte of source node public key",startByte:c+h,endByte:c+h,value:y}),h+=1;const g=(0,r.bytesToHex)(u.subarray(h,h+2));l!=null&&l.includeSegments&&m.push({name:"Cipher MAC",description:"MAC for encrypted data in next field",startByte:c+h,endByte:c+h+1,value:g}),h+=2;const f=(0,r.bytesToHex)(u.subarray(h));l!=null&&l.includeSegments&&u.length>h&&m.push({name:"Ciphertext",description:"Encrypted message data (timestamp + message text)",startByte:c+h,endByte:c+u.length-1,value:f});const z={type:a.PayloadType.TextMessage,version:a.PayloadVersion.Version1,isValid:!0,destinationHash:d,sourceHash:y,cipherMac:g,ciphertext:f,ciphertextLength:u.length-4};return l!=null&&l.includeSegments&&(z.segments=m),z}catch(m){return{type:a.PayloadType.TextMessage,version:a.PayloadVersion.Version1,i Line truncated
// SHA256 round constants
const K: array<u32, 64> = array<u32, 64>(
0x428a2f98u, 0x71374491u, 0xb5c0fbcfu, 0xe9b5dba5u, 0x3956c25bu, 0x59f111f1u, 0x923f82a4u, 0xab1c5ed5u,
0xd807aa98u, 0x12835b01u, 0x243185beu, 0x550c7dc3u, 0x72be5d74u, 0x80deb1feu, 0x9bdc06a7u, 0xc19bf174u,
0xe49b69c1u, 0xefbe4786u, 0x0fc19dc6u, 0x240ca1ccu, 0x2de92c6fu, 0x4a7484aau, 0x5cb0a9dcu, 0x76f988dau,
0x983e5152u, 0xa831c66du, 0xb00327c8u, 0xbf597fc7u, 0xc6e00bf3u, 0xd5a79147u, 0x06ca6351u, 0x14292967u,
0x27b70a85u, 0x2e1b2138u, 0x4d2c6dfcu, 0x53380d13u, 0x650a7354u, 0x766a0abbu, 0x81c2c92eu, 0x92722c85u,
0xa2bfe8a1u, 0xa81a664bu, 0xc24b8b70u, 0xc76c51a3u, 0xd192e819u, 0xd6990624u, 0xf40e3585u, 0x106aa070u,
0x19a4c116u, 0x1e376c08u, 0x2748774cu, 0x34b0bcb5u, 0x391c0cb3u, 0x4ed8aa4au, 0x5b9cca4fu, 0x682e6ff3u,
0x748f82eeu, 0x78a5636fu, 0x84c87814u, 0x8cc70208u, 0x90befffau, 0xa4506cebu, 0xbef9a3f7u, 0xc67178f2u
);
// Character lookup table (a-z = 0-25, 0-9 = 26-35, dash = 36)
const CHARS: array<u32, 37> = array<u32, 37>(
0x61u, 0x62u, 0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u, 0x69u, 0x6au, // a-j
0x6bu, 0x6cu, 0x6du, 0x6eu, 0x6fu, 0x70u, 0x71u, 0x72u, 0x73u, 0x74u, // k-t
0x75u, 0x76u, 0x77u, 0x78u, 0x79u, 0x7au, // u-z
0x30u, 0x31u, 0x32u, 0x33u, 0x34u, 0x35u, 0x36u, 0x37u, 0x38u, 0x39u, // 0-9
0x2du // dash
);
struct Params {
target_channel_hash: u32,
batch_offset: u32,
name_length: u32,
batch_size: u32,
target_mac: u32, // First 2 bytes of target MAC (in high 16 bits)
ciphertext_words: u32, // Number of 32-bit words in ciphertext
ciphertext_len_bits: u32, // Length of ciphertext in bits
verify_mac: u32, // 1 to verify MAC, 0 to skip
}
@group(0) @binding(0) var<uniform> params: Params;
@group(0) @binding(1) var<storage, read_write> match_count: atomic<u32>;
@group(0) @binding(2) var<storage, read_write> match_indices: array<u32>;
@group(0) @binding(3) var<storage, read> ciphertext: array<u32>; // Ciphertext data
fn rotr(x: u32, n: u32) -> u32 {
return (x >> n) | (x << (32u - n));
}
fn ch(x: u32, y: u32, z: u32) -> u32 {
return (x & y) ^ (~x & z);
}
fn maj(x: u32, y: u32, z: u32) -> u32 {
return (x & y) ^ (x & z) ^ (y & z);
}
fn sigma0(x: u32) -> u32 {
return rotr(x, 2u) ^ rotr(x, 13u) ^ rotr(x, 22u);
}
fn sigma1(x: u32) -> u32 {
return rotr(x, 6u) ^ rotr(x, 11u) ^ rotr(x, 25u);
}
fn gamma0(x: u32) -> u32 {
return rotr(x, 7u) ^ rotr(x, 18u) ^ (x >> 3u);
}
fn gamma1(x: u32) -> u32 {
return rotr(x, 17u) ^ rotr(x, 19u) ^ (x >> 10u);
}
// Convert index to room name bytes, returns the hash as a u32 for the first byte check
fn index_to_room_name(idx: u32, length: u32, msg: ptr<function, array<u32, 16>>) -> bool {
// Message starts with '#' (0x23)
var byte_pos = 0u;
var word_idx = 0u;
var current_word = 0x23000000u; // '#' in big-endian position 0
byte_pos = 1u;
var remaining = idx;
var prev_was_dash = false;
// Generate room name from index
for (var i = 0u; i < length; i++) {
let char_count = select(37u, 36u, i == 0u || i == length - 1u); // no dash at start/end
var char_idx = remaining % char_count;
remaining = remaining / char_count;
// Check for consecutive dashes (invalid)
let is_dash = char_idx == 36u && i > 0u && i < length - 1u;
if (is_dash && prev_was_dash) {
return false; // Invalid: consecutive dashes
}
prev_was_dash = is_dash;
// Map char index to actual character
let c = CHARS[char_idx];
// Pack byte into current word (big-endian)
let shift = (3u - byte_pos % 4u) * 8u;
if (byte_pos % 4u == 0u && byte_pos > 0u) {
(*msg)[word_idx] = current_word;
word_idx = word_idx + 1u;
current_word = 0u;
}
current_word = current_word | (c << shift);
byte_pos = byte_pos + 1u;
}
// Add padding: 0x80 followed by zeros, then length in bits
let msg_len_bits = (length + 1u) * 8u; // +1 for '#'
// Add 0x80 padding byte
let shift = (3u - byte_pos % 4u) * 8u;
if (byte_pos % 4u == 0u) {
(*msg)[word_idx] = current_word;
word_idx = word_idx + 1u;
current_word = 0x80000000u;
} else {
current_word = current_word | (0x80u << shift);
}
byte_pos = byte_pos + 1u;
// Store current word
if (byte_pos % 4u == 0u || word_idx < 14u) {
(*msg)[word_idx] = current_word;
word_idx = word_idx + 1u;
}
// Zero-fill until word 14
for (var i = word_idx; i < 14u; i++) {
(*msg)[i] = 0u;
}
// Length in bits (64-bit, but we only use lower 32 bits for short messages)
(*msg)[14u] = 0u;
(*msg)[15u] = msg_len_bits;
return true;
}
fn sha256_block(msg: ptr<function, array<u32, 16>>) -> array<u32, 8> {
// Initialize hash values
var h: array<u32, 8> = array<u32, 8>(
0x6a09e667u, 0xbb67ae85u, 0x3c6ef372u, 0xa54ff53au,
0x510e527fu, 0x9b05688cu, 0x1f83d9abu, 0x5be0cd19u
);
// Message schedule
var w: array<u32, 64>;
for (var i = 0u; i < 16u; i++) {
w[i] = (*msg)[i];
}
for (var i = 16u; i < 64u; i++) {
w[i] = gamma1(w[i-2u]) + w[i-7u] + gamma0(w[i-15u]) + w[i-16u];
}
// Compression
var a = h[0]; var b = h[1]; var c = h[2]; var d = h[3];
var e = h[4]; var f = h[5]; var g = h[6]; var hh = h[7];
for (var i = 0u; i < 64u; i++) {
let t1 = hh + sigma1(e) + ch(e, f, g) + K[i] + w[i];
let t2 = sigma0(a) + maj(a, b, c);
hh = g; g = f; f = e; e = d + t1;
d = c; c = b; b = a; a = t1 + t2;
}
h[0] = h[0] + a; h[1] = h[1] + b; h[2] = h[2] + c; h[3] = h[3] + d;
h[4] = h[4] + e; h[5] = h[5] + f; h[6] = h[6] + g; h[7] = h[7] + hh;
return h;
}
// Compute SHA256 of the key (16 bytes) to get channel hash
fn sha256_key(key: array<u32, 4>) -> u32 {
var msg: array<u32, 16>;
// Key bytes (16 bytes = 4 words)
msg[0] = key[0];
msg[1] = key[1];
msg[2] = key[2];
msg[3] = key[3];
// Padding: 0x80 followed by zeros
msg[4] = 0x80000000u;
for (var i = 5u; i < 14u; i++) {
msg[i] = 0u;
}
// Length: 128 bits
msg[14] = 0u;
msg[15] = 128u;
let hash = sha256_block(&msg);
// Return first byte of hash (big-endian)
return hash[0] >> 24u;
}
// HMAC-SHA256 for MAC verification
// Key is 16 bytes (4 words), padded to 32 bytes with zeros for MeshCore
// Returns first 2 bytes of HMAC (as u32 in high 16 bits)
fn hmac_sha256_mac(key: array<u32, 4>, ciphertext_len: u32) -> u32 {
// HMAC: H((K' ^ opad) || H((K' ^ ipad) || message))
// K' is 64 bytes (32 bytes key + 32 bytes zero padding for MeshCore, then padded to 64)
// ipad = 0x36 repeated, opad = 0x5c repeated
// Build padded key (64 bytes = 16 words)
// MeshCore uses 32-byte secret: 16-byte key + 16 zero bytes
var k_pad: array<u32, 16>;
k_pad[0] = key[0];
k_pad[1] = key[1];
k_pad[2] = key[2];
k_pad[3] = key[3];
for (var i = 4u; i < 16u; i++) {
k_pad[i] = 0u;
}
// Inner hash: SHA256((K' ^ ipad) || message)
// First block: K' ^ ipad (64 bytes)
var inner_block: array<u32, 16>;
for (var i = 0u; i < 16u; i++) {
inner_block[i] = k_pad[i] ^ 0x36363636u;
}
// Initialize hash state with first block
var h: array<u32, 8> = sha256_block(&inner_block);
// Process ciphertext blocks (continuing from h state)
let ciphertext_words = params.ciphertext_words;
var word_idx = 0u;
// Process full 64-byte blocks of ciphertext
while (word_idx + 16u <= ciphertext_words) {
var block: array<u32, 16>;
for (var i = 0u; i < 16u; i++) {
block[i] = ciphertext[word_idx + i];
}
h = sha256_block_continue(&block, h);
word_idx = word_idx + 16u;
}
// Final block with remaining ciphertext + padding
var final_block: array<u32, 16>;
var remaining = ciphertext_words - word_idx;
for (var i = 0u; i < 16u; i++) {
if (i < remaining) {
final_block[i] = ciphertext[word_idx + i];
} else if (i == remaining) {
// Add 0x80 padding
final_block[i] = 0x80000000u;
} else {
final_block[i] = 0u;
}
}
// Add length (64 bytes of ipad + ciphertext length)
let total_bits = 512u + params.ciphertext_len_bits;
if (remaining < 14u) {
final_block[14] = 0u;
final_block[15] = total_bits;
h = sha256_block_continue(&final_block, h);
} else {
// Need extra block for length
h = sha256_block_continue(&final_block, h);
var len_block: array<u32, 16>;
for (var i = 0u; i < 14u; i++) {
len_block[i] = 0u;
}
len_block[14] = 0u;
len_block[15] = total_bits;
h = sha256_block_continue(&len_block, h);
}
let inner_hash = h;
// Outer hash: SHA256((K' ^ opad) || inner_hash)
var outer_block: array<u32, 16>;
for (var i = 0u; i < 16u; i++) {
outer_block[i] = k_pad[i] ^ 0x5c5c5c5cu;
}
h = sha256_block(&outer_block);
// Second block: inner_hash (32 bytes) + padding
var hash_block: array<u32, 16>;
for (var i = 0u; i < 8u; i++) {
hash_block[i] = inner_hash[i];
}
hash_block[8] = 0x80000000u;
for (var i = 9u; i < 14u; i++) {
hash_block[i] = 0u;
}
hash_block[14] = 0u;
hash_block[15] = 512u + 256u; // 64 bytes opad + 32 bytes inner hash
h = sha256_block_continue(&hash_block, h);
// Return first 2 bytes (high 16 bits of first word)
return h[0] & 0xFFFF0000u;
}
// SHA256 block computation continuing from existing state
fn sha256_block_continue(msg: ptr<function, array<u32, 16>>, h_in: array<u32, 8>) -> array<u32, 8> {
var h = h_in;
// Message schedule
var w: array<u32, 64>;
for (var i = 0u; i < 16u; i++) {
w[i] = (*msg)[i];
}
for (var i = 16u; i < 64u; i++) {
w[i] = gamma1(w[i-2u]) + w[i-7u] + gamma0(w[i-15u]) + w[i-16u];
}
// Compression
var a = h[0]; var b = h[1]; var c = h[2]; var d = h[3];
var e = h[4]; var f = h[5]; var g = h[6]; var hh = h[7];
for (var i = 0u; i < 64u; i++) {
let t1 = hh + sigma1(e) + ch(e, f, g) + K[i] + w[i];
let t2 = sigma0(a) + maj(a, b, c);
hh = g; g = f; f = e; e = d + t1;
d = c; c = b; b = a; a = t1 + t2;
}
h[0] = h[0] + a; h[1] = h[1] + b; h[2] = h[2] + c; h[3] = h[3] + d;
h[4] = h[4] + e; h[5] = h[5] + f; h[6] = h[6] + g; h[7] = h[7] + hh;
return h;
}
// Process a single candidate and record match if found
fn process_candidate(name_idx: u32) {
// Generate message for this room name
var msg: array<u32, 16>;
let valid = index_to_room_name(name_idx, params.name_length, &msg);
if (!valid) {
return;
}
// Compute SHA256("#roomname") - this gives us the key
let key_hash = sha256_block(&msg);
// Take first 16 bytes (4 words) as the key
var key: array<u32, 4>;
key[0] = key_hash[0];
key[1] = key_hash[1];
key[2] = key_hash[2];
key[3] = key_hash[3];
// Compute SHA256(key) to get channel hash
let channel_hash = sha256_key(key);
// Check if channel hash matches target
if (channel_hash != params.target_channel_hash) {
return;
}
// Channel hash matches - verify MAC if enabled
if (params.verify_mac == 1u) {
let computed_mac = hmac_sha256_mac(key, params.ciphertext_len_bits);
if (computed_mac != params.target_mac) {
return;
}
}
// Found a match - record the index
let match_idx = atomicAdd(&match_count, 1u);
if (match_idx < 1024u) { // Limit stored matches
match_indices[match_idx] = name_idx;
}
}
// Each thread processes 16 candidates to amortize thread overhead
const CANDIDATES_PER_THREAD: u32 = 16u;
@compute @workgroup_size(256)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
let base_idx = global_id.x * CANDIDATES_PER_THREAD;
for (var i = 0u; i < CANDIDATES_PER_THREAD; i++) {
let idx = base_idx + i;
if (idx >= params.batch_size) {
return;
}
let name_idx = params.batch_offset + idx;
process_candidate(name_idx);
}
}
`}async init(){if(!navigator.gpu)return console.warn("WebGPU not supported"),!1;try{const r=await navigator.gpu.requestAdapter();if(!r)return console.warn("No GPU adapter found"),!1;this.device=await r.requestDevice(),this.bindGroupLayout=this.device.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.COMPUTE,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.COMPUTE,buffer:{type:"storage"}},{binding:2,visibility:GPUShaderStage.COMPUTE,buffer:{type:"storage"}},{binding:3,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}}]}),this.paramsBuffer=this.device.createBuffer({size:32,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),this.matchCountBuffer=this.device.createBuffer({size:4,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_SRC|GPUBufferUsage.COPY_DST}),this.matchIndicesBuffer=this.device.createBuffer({size:1024*4,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_SRC});for(let u=0;u<2;u++)this.matchCountReadBuffers[u]=this.device.createBuffer({size:4,usage:GPUBufferUsage.MAP_READ|GPUBufferUsage.COPY_DST}),this.matchIndicesReadBuffers[u]=this.device.createBuffer({size:1024*4,usage:GPUBufferUsage.MAP_READ|GPUBufferUsage.COPY_DST});const o=this.device.createShaderModule({code:this.shaderCode}),s=this.device.createPipelineLayout({bindGroupLayouts:[this.bindGroupLayout]});return this.pipeline=this.device.createComputePipeline({layout:s,compute:{module:o,entryPoint:"main"}}),!0}catch(r){return console.error("WebGPU initialization failed:",r),!1}}isAvailable(){return this.device!==null&&this.pipeline!==null}indexToRoomName(r,o){return Qn(o,r)}countNamesForLength(r){return Rr(r)}async runBatch(r,o,s,u,l,m){if(!this.device||!this.pipeline||!this.bindGroupLayout||!this.paramsBuffer||!this.matchCountBuffer||!this.matchIndicesBuffer||!this.matchCountReadBuffers[0]||!this.matchCountReadBuffers[1]||!this.matchIndicesReadBuffers[0]||!this.matchIndicesReadBuffers[1])throw new Error("GPU not initialized");const c=this.currentReadBufferIndex;this.currentReadBufferIndex=1-this.currentReadBufferIndex;const h=this.matchCountReadBuffers[c],d=this.matchIndicesReadBuffers[c],y=l&&m?1:0;let g,f=0,z=0;if(y){const x=new Uint8Array(l.length/2);for(let N=0;N<x.length;N++)x[N]=parseInt(l.substr(N*2,2),16);f=x.length*8;const D=Math.ceil(x.length/4)*4,R=new Uint8Array(D);R.set(x),g=new Uint32Array(D/4);for(let N=0;N<g.length;N++)g[N]=R[N*4]<<24|R[N*4+1]<<16|R[N*4+2]<<8|R[N*4+3];const F=parseInt(m.substr(0,2),16),O=parseInt(m.substr(2,2),16);z=F<<24|O<<16}else g=new Uint32Array([0]);const k=Math.max(g.length*4,4);(!this.ciphertextBuffer||this.ciphertextBufferSize<k)&&(this.ciphertextBuffer&&this.ciphertextBuffer.destroy(),this.ciphertextBuffer=this.device.createBuffer({size:k,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST}),this.ciphertextBufferSize=k,this.bindGroupDirty=!0);const j=new Uint32Array([r,s,o,u,z,g.length,f,y]);this.device.queue.writeBuffer(this.paramsBuffer,0,j),this.device.queue.writeBuffer(this.ciphertextBuffer,0,g),this.device.queue.writeBuffer(this.matchCountBuffer,0,gl.ZERO_DATA),(this.bindGroupDirty||!this.bindGroup)&&(this.bindGroup=this.device.createBindGroup({layout:this.bindGroupLayout,entries:[{binding:0,resource:{buffer:this.paramsBuffer}},{binding:1,resource:{buffer:this.matchCountBuffer}},{binding:2,resource:{buffer:this.matchIndicesBuffer}},{binding:3,resource:{buffer:this.ciphertextBuffer}}]}),this.bindGroupDirty=!1);const v=this.device.createCommandEncoder(),A=v.beginComputePass();A.setPipeline(this.pipeline),A.setBindGroup(0,this.bindGroup),A.dispatchWorkgroups(Math.ceil(u/(256*16))),A.end(),v.copyBufferToBuffer(this.matchCountBuffer,0,h,0,4),v.copyBufferToBuffer(this.matchIndicesBuffer,0,d,0,1024*4),this.device.queue.submit([v.finish()]),await h.mapAsync(GPUMapMode.READ);const w=new Uint32Array(h.getMappedRange())[0];h.unmap();const _=[];if(w>0){await d.mapAsync(GPUMapMode.READ);const x=new Uint32Array(d.getMappedRange());for(let D=0;D<Math.min(w,1024);D++)_.push(x[D]);d.unmap()}return _}destroy(){var r,o,s,u,l,m,c,h;(r=this.paramsBuffer)==null||r.destroy(),(o=this.matchCountBuffer)==null||o.destroy(),(s=this.matchIndicesBuffer)==null||s.destroy(),(u=this.ciphertextBuffer)==null||u.destroy(),(l=this.matchCountReadBuffers[0])==null||l.destroy(),(m=this.matchCountReadBuffers[1])==null||m.destroy(),(c=this.matchIndicesReadBuffers[0])==null||c.destroy(),(h=this.matchIndicesReadBuffers[1])==null||h.destroy(),this.paramsBuffer=null,this.matchCountBuffer=null,this.matchIndicesBuffer=null,this.ciphertextBuffer=null,this.ciphertextBufferSize=0,this.matchCountReadBuffers=[null,null],this.matchIndicesReadBuffers=[null,null],this.currentReadBufferIndex=0,this.bindGroup=null,this.bindGroupDirty=!0,this.device&&(this.device.destroy(),this.device=null),this.pipeline=null,this.bindGroupLayout=null}}gl.ZERO_DATA=new Uint32Array([0]);function Rj(){return typeof navigator<"u"&&"gpu"in navigator}class Ig{runBatch(r,o,s,u,l,m){const c=[],h=r.toString(16).padStart(2,"0") Line truncated
`).map(l=>l.trim().toLowerCase()).filter(l=>l.length>0);this.wordlist=u.filter(Mg)}setWordlist(r){this.wordlist=r.map(o=>o.trim().toLowerCase()).filter(Mg)}abort(){this.abortFlag=!0}isGpuAvailable(){return Rj()}async decodePacket(r){var s;const o=r.trim().replace(/\s+/g,"").replace(/^0x/i,"");if(!o||!/^[0-9a-fA-F]+$/.test(o))return null;try{const l=(s=(await Tg.MeshCorePacketDecoder.decodeWithVerification(o,{})).payload)==null?void 0:s.decoded;return!(l!=null&&l.channelHash)||!(l!=null&&l.ciphertext)||!(l!=null&&l.cipherMac)?null:{channelHash:l.channelHash,ciphertext:l.ciphertext,cipherMac:l.cipherMac,isGroupText:!0}}catch{return null}}async crack(r,o,s){this.abortFlag=!1,this.useTimestampFilter=(o==null?void 0:o.useTimestampFilter)??!0,this.useUtf8Filter=(o==null?void 0:o.useUtf8Filter)??!0,this.validSeconds=(o==null?void 0:o.validSeconds)??Du,this.useCpu=(o==null?void 0:o.forceCpu)??!1;const u=(o==null?void 0:o.maxLength)??8,l=(o==null?void 0:o.startingLength)??1,m=(o==null?void 0:o.useDictionary)??!0,c=(o==null?void 0:o.startFromType)??"bruteforce",h=r.toLowerCase(),d=await this.decodePacket(h);if(!d)return{found:!1,error:"Invalid packet or not a GroupText packet"};const{channelHash:y,ciphertext:g,cipherMac:f}=d,z=parseInt(y,16);this.useCpu?this.cpuInstance||(this.cpuInstance=new Ig):this.gpuInstance||(this.gpuInstance=new gl,await this.gpuInstance.init()||(this.useCpu=!0,this.cpuInstance=new Ig));const k=performance.now();let j=0,v=performance.now(),A=l,C=0,w=0,_=!1;if(o!=null&&o.startFrom){const H=o.startFrom.toLowerCase();if(c==="dictionary"){const K=this.wordlist.indexOf(H);K>=0&&(w=K+1)}else{_=!0;const K=_j(H);K&&(A=Math.max(l,K.length),C=K.index+1,C>=Rr(A)&&(A++,C=0))}}let x=0;m&&!_&&this.wordlist.length>0&&(x+=this.wordlist.length-w);for(let H=A;H<=u;H++)x+=Rr(H);x-=C;const D=(H,K,ae)=>{if(!s)return;const ne=(performance.now()-k)/1e3,$=ne>0?Math.round(j/ne):0,oe=x-j,ee=$>0?oe/$:0;s({checked:j,total:x,percent:x>0?Math.min(100,j/x*100):0,rateKeysPerSec:$,etaSeconds:ee,elapsedSeconds:ne,currentLength:K,currentPosition:ae,phase:H})},R=H=>{if(!Xb(g,f,H))return{valid:!1};const K=Tg.ChannelCrypto.decryptGroupTextMessage(g,f,H);return!K.success||!K.data?{valid:!1}:this.useTimestampFilter&&!Dj(K.data.timestamp,this.validSeconds)?{valid:!1}:this.useUtf8Filter&&!Fj(K.data.message)?{valid:!1}:{valid:!0,message:K.data.message}};if(!_&&w===0&&A===l&&C===0){D("public-key",0,_u);const H=Ru($t);if(y===H){const K=R($t);if(K.valid)return{found:!0,roomName:_u,key:$t,decryptedMessage:K.message}}}if(m&&!_&&this.wordlist.length>0)for(let H=w;H<this.wordlist.length;H++){if(this.abortFlag)return{found:!1,aborted:!0,resumeFrom:this.wordlist[H],resumeType:"dictionary"};const K=this.wordlist[H],ae=Fu("#"+K),X=Ru(ae);if(parseInt(X,16)===z){const $=R(ae);if($.valid)return{found:!0,roomName:K,key:ae,decryptedMessage:$.message,resumeFrom:K,resumeType:"dictionary"}}j++;const ne=performance.now();ne-v>=200&&(D("wordlist",K.length,K),v=ne,await new Promise($=>setTimeout($,0)))}const F=this.useCpu?1024:32768,O=(o==null?void 0:o.gpuDispatchMs)??1e3;let N=F,T=!1;for(let H=A;H<=u;H++){if(this.abortFlag)return{found:!1,aborted:!0,resumeFrom:Qn(H,0)||void 0,resumeType:"bruteforce"};const K=Rr(H);let ae=H===A?C:0;for(;ae<K;){if(this.abortFlag)return{found:!1,aborted:!0,resumeFrom:Qn(H,ae)||void 0,resumeType:"bruteforce"};const X=Math.min(N,K-ae),ne=performance.now();let $;this.useCpu?$=this.cpuInstance.runBatch(z,H,ae,X,g,f):$=await this.gpuInstance.runBatch(z,H,ae,X,g,f);const oe=performance.now()-ne;if(j+=X,!this.useCpu&&!T&&X>=F&&oe>0){const q=O/oe,I=Math.round(X*q),L=Math.pow(2,Math.round(Math.log2(Math.max(F,I))));N=Math.max(F,L),T=!0}for(const q of $){const I=Qn(H,q);if(!I)continue;const L=Fu("#"+I),G=R(L);if(G.valid)return{found:!0,roomName:I,key:L,decryptedMessage:G.message,resumeFrom:I,resumeType:"bruteforce"}}ae+=X;const ee=performance.now();if(ee-v>=200){const q=Qn(H,Math.min(ae,K-1))||"";D("bruteforce",H,q),v=ee,await new Promise(I=>setTimeout(I,0))}}}return{found:!1,resumeFrom:Qn(u,Rr(u)-1)||void 0,resumeType:"bruteforce"}}destroy(){this.gpuInstance&&(this.gpuInstance.destroy(),this.gpuInstance=null),this.cpuInstance&&(this.cpuInstance.destroy(),this.cpuInstance=null)}}const Mj=["a","aa","aaa","aah","aahed","aahing","aahs","aal","aalii","aaliis","aals","aam","aani","aardvark","aardvarks","aardwolf","aardwolves","aargh","aaron","aaronic","aaronical","aaronite","aaronitic","aarrgh","aarrghh","aaru","aas","aasvogel","aasvogels","ab","aba","ababdeh","ababua","abac","abaca","abacay","abacas","abacate","abacaxi","abaci","abacinate","abacination","abacisci","abaciscus","abacist","aback","abacli","abacot","abacterial","abactinal","abactinally","abaction","abactor","abaculi","abaculus","abacus","abacuses","abada","abaddon","abadejo","abadengo","abadia","abadite","abaff","abaft","abay","abayah","abaisance","abaised","abaiser","abaisse","abaissed","abaka","abakas","abalation","abalienate","abalienated","abalienating","abalienation","ab Line truncated
NoSleep enabled for older iOS devices. This can interrupt
active or long-running network requests from completing successfully.
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(c=>(this.enabled=!0,c)).catch(c=>{throw this.enabled=!1,c})}disable(){s()?(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 vu=u,vu}var Hj=Lj();const Vj=Fo(Hj);function Uj({packets:a,channels:r,onChannelCreate:o,onRunningChange:s}){const[u,l]=S.useState(!1),[m,c]=S.useState(6),[h,d]=S.useState(!1),[y,g]=S.useState(!0),[f,z]=S.useState(!1),[k,j]=S.useState(null),[v,A]=S.useState(new Map),[C,w]=S.useState([]),[_,x]=S.useState(!1),[D,R]=S.useState(null),[F,O]=S.useState(null),N=S.useRef(null),T=S.useRef(null),W=S.useRef(!1),H=S.useRef(!1),K=S.useRef(!1),ae=S.useRef(new Map),X=S.useRef(!1),ne=S.useRef(6),$=S.useRef(!0),oe=S.useRef(!1),ee=S.useRef(new Set);S.useEffect(()=>{const ce=new Ij;N.current=ce,R(ce.isGpuAvailable());const Ae=new Vj;return T.current=Ae,ce.setWordlist(Mj),x(!0),()=>{ce.destroy(),N.current=null,Ae.disable(),T.current=null}},[]),S.useEffect(()=>{const ce=()=>{ei.getUndecryptedPacketCount().then(({count:Fe})=>O(Fe)).catch(()=>O(null))};ce();const Ae=setInterval(ce,3e4);return()=>clearInterval(Ae)},[]);const q=new Set(r.map(ce=>ce.key.toUpperCase())),I=a.filter(ce=>ce.payload_type==="GROUP_TEXT"&&!ce.decrypted);S.useEffect(()=>{A(ce=>{const Ae=new Map(ce);let Fe=!1;for(const He of I)Ae.has(He.id)||(Ae.set(He.id,{packet:He,attempts:0,lastAttemptLength:0,status:"pending"}),Fe=!0);return Fe?(ae.current=Ae,Ae):ce})},[I.length]),S.useEffect(()=>{ae.current=v},[v]),S.useEffect(()=>{X.current=h},[h]),S.useEffect(()=>{ne.current=m},[m]),S.useEffect(()=>{$.current=y},[y]),S.useEffect(()=>{oe.current=f},[f]),S.useEffect(()=>{ee.current=new Set(I.map(ce=>ce.id))},[I]),S.useEffect(()=>{s==null||s(u)},[u,s]);const L=Array.from(v.values()).filter(ce=>ce.status==="pending").length,G=Array.from(v.values()).filter(ce=>ce.status==="cracked").length,de=Array.from(v.values()).filter(ce=>ce.status==="failed").length,ge=S.useCallback(async()=>{if(K.current||!N.current||!W.current)return;const ce=ae.current;let Ae=null,Fe=null;for(const[Oe,Me]of ce.entries())if(Me.status==="pending"){Ae=Me,Fe=Oe;break}if(!Ae&&X.current){const Oe=Array.from(ce.entries()).filter(([,Me])=>Me.status==="failed"&&Me.lastAttemptLength<10);Oe.length>0&&(Oe.sort((Me,Xe)=>Me[1].lastAttemptLength-Xe[1].lastAttemptLength),[Fe,Ae]=Oe[0])}if(!Ae||Fe===null){W.current&&setTimeout(()=>ge(),1e3);return}if(!ee.current.has(Fe)){A(Oe=>{const Me=new Map(Oe);return Me.delete(Fe),Me}),W.current&&setTimeout(()=>ge(),10);return}K.current=!0;const He=ne.current,ai=Ae.lastAttemptLength>0,Ge=ai?Ae.lastAttemptLength+1:He;try{const Oe=await N.current.crack(Ae.packet.data,{maxLength:Ge,useTimestampFilter:!0,useUtf8Filter:!0,...oe.current&&{gpuDispatchMs:1e4},...ai&&{useDictionary:!1,startingLength:Ge}},Me=>{j(Me)});if(H.current){H.current=!1,K.current=!1,j(null);return}if(Oe.found&&Oe.roomName&&Oe.key){A(Ve=>{const Qe=new Map(Ve),yi=Qe.get(Fe);return yi&&Qe.set(Fe,{...yi,status:"cracked",attempts:yi.attempts+1,lastAttemptLength:Ge}),Qe});const Me={roomName:Oe.roomName,key:Oe.key,packetId:Fe,message:Oe.decryptedMessage||"",crackedAt:Date.now()};w(Ve=>[...Ve,Me]);const Xe=Oe.key.toUpperCase();if(!q.has(Xe))try{const Ve="#"+Oe.roomName;await o(Ve,Oe.key),$.current&&await ei.decryptHistoricalPackets({key_type:"channel",channel_name:Ve})}catch(Ve){console.error("Failed to create channel or decrypt historical:",Ve)}}else A(Me=>{const Xe=new Map(Me),Ve=Xe.get(Fe);return Ve&&Xe.set(Fe,{...Ve,status:"failed",attempts:Ve.attempts+1,lastAttemptLength:Ge}),Xe})}catch(Oe){console.error("Cracking error:",Oe),A(Me=>{const Xe=new Map(Me),Ve=Xe.get(Fe);return Ve&&Xe.set(Fe,{...Ve,status:"failed",attempts:Ve.attempts+1,lastAttemptLength:Ge}),Xe})}K.current=!1,j(null),W.current&&setTimeout(()=>ge(),100)},[q,o]),be=()=>{var ce;if(!D){alert("WebGPU is not available in your browser. Please use Chrome 113+ or Edge 113+.");return}l(!0),W.current=!0,H.current=!1,(ce=T.current)==null||ce.enable(),ge()},se=()=>{var ce,Ae;l(!1),W.current=!1,H.current=!0,(ce=N.current)==null||ce.abort(),(Ae=T.current)==null||Ae.disable()};return B.jsxs("div",{className:"flex flex-col h-full p-3 gap-3 bg-background border-t border-border",children:[B.jsxs("p",{className:"text-xs text-muted-foreground leading-relaxed",children:["This will attempt to dictionary attack, then brute force GroupText packets as they arrive, testing room names up to the specified length.",B.jsx("strong",{children:" Retry failed at n+1"})," will let the cracker return to the failed queue and pick up messages it couldn't crack, attempting them at one longer length.",B.jsx("strong",{children:" Decrypt historical"})," will run an async job on any room name it finds to see if any historically captured packets will decrypt with that key.",B.jsx("strong",{children:" Turbo mode"})," will push your GPU to the max (target dispatch time of 10s) and may allow accelerated cracking and/or system instability."]}),B.jsxs("div",{className:"flex items-center gap-3 flex-wrap",children:[B.jsx("button",{onClick:u?se:be,disabled:!_||D===!1,className:$e( Line truncated
`)}function Yj(a){if(a.length===0)return`Neighbors
No neighbors reported`;const r=[...a].sort((s,u)=>u.snr-s.snr),o=[`Neighbors (${r.length})`];for(const s of r){const u=s.name||s.pubkey_prefix,l=s.snr>=0?`+${s.snr.toFixed(1)}`:s.snr.toFixed(1);o.push(`${u}, ${l} dB [${Gt(s.last_heard_seconds)} ago]`)}return o.join(`
`)}function Zj(a){if(a.length===0)return`ACL
No ACL entries`;const r=[`ACL (${a.length})`];for(const o of a){const s=o.name||o.pubkey_prefix;r.push(`${s}: ${o.permission_name}`)}return r.join(`
`)}function Xj(a){return`${a.type}-${a.conversation_key}-${a.text}-${a.sender_timestamp}`}function Jj(){const a=window.location.hash.slice(1);if(!a)return null;if(a==="raw")return{type:"raw",name:"raw"};const r=a.indexOf("/");if(r===-1)return null;const o=a.slice(0,r),s=decodeURIComponent(a.slice(r+1));return(o==="channel"||o==="contact")&&s?{type:o,name:s}:null}function eA(a){if(!a)return"";if(a.type==="raw")return"#raw";const r=a.type==="channel"&&a.name.startsWith("#")?a.name.slice(1):a.name;return`#${a.type}/${encodeURIComponent(r)}`}function iA(){const a=S.useRef(null),r=S.useRef(null),o=S.useRef(new Set),[s,u]=S.useState(null),[l,m]=S.useState(null),[c,h]=S.useState(null),[d,y]=S.useState([]),[g,f]=S.useState([]),[z,k]=S.useState([]),[j,v]=S.useState(!1),[A,C]=S.useState(!1),[w,_]=S.useState([]),[x,D]=S.useState(null),[R,F]=S.useState(!1),[O,N]=S.useState(!1),[T,W]=S.useState(!1),[H,K]=S.useState(!1),[ae,X]=S.useState(0),[ne,$]=S.useState(!1),[oe,ee]=S.useState(!1),[q,I]=S.useState(!1),[L,G]=S.useState(Zm),[de,ge]=S.useState({}),be=S.useRef(null),se=S.useMemo(()=>({onHealth:V=>{const J=be.current;be.current=V,u(V),J!==null&&J.radio_connected!==V.radio_connected&&(V.radio_connected?Tr.success("Radio connected",{description:V.serial_port?`Connected to ${V.serial_port}`:void 0}):Tr.error("Radio disconnected",{description:"Check radio connection and power"}))},onError:V=>{Tr.error(V.message,{description:V.details})},onContacts:V=>y(V),onChannels:V=>f(V),onMessage:V=>{const J=r.current,re=Xj(V);if(o.current.has(re)){console.debug("Duplicate message content ignored:",re.slice(0,50));return}if(o.current.add(re),o.current.size>1e3){const _e=Array.from(o.current);o.current=new Set(_e.slice(-500))}let ye=null;V.type==="CHAN"&&V.conversation_key?ye=an("channel",V.conversation_key):V.type==="PRIV"&&V.conversation_key&&(ye=an("contact",V.conversation_key));const fe=J?V.type==="CHAN"&&J.type==="channel"?V.conversation_key===J.id:V.type==="PRIV"&&J.type==="contact"?V.conversation_key&&Kg(J.id,V.conversation_key):!1:!1;if(fe&&k(_e=>_e.some(ni=>ni.id===V.id)?_e:[..._e,V]),ye){const _e=V.received_at||Math.floor(Date.now()/1e3),ni=nu(ye,_e);G(ni),!V.outgoing&&!fe&&ge(Ce=>({...Ce,[ye]:(Ce[ye]||0)+1}))}},onContact:V=>{y(J=>{const re=J.findIndex(ye=>ye.public_key===V.public_key);if(re>=0){const ye=[...J],fe=J[re];return ye[re]={...fe,...V,name:V.name??fe.name,last_path:V.last_path??fe.last_path,lat:V.lat??fe.lat,lon:V.lon??fe.lon},ye}return[...J,V]})},onRawPacket:V=>{_(J=>{if(J.some(ye=>ye.id===V.id))return J;const re=[...J,V];return re.length>Hg?re.slice(-Hg):re})},onMessageAcked:(V,J)=>{k(re=>{const ye=re.findIndex(fe=>fe.id===V);if(ye>=0){const fe=[...re];return fe[ye]={...re[ye],acked:J},fe}return re})}}),[]);ck(se);const ce=S.useCallback(async()=>{try{const V=await ei.getRadioConfig();m(V)}catch(V){console.error("Failed to fetch config:",V)}},[]),Ae=S.useCallback(async()=>{try{const V=await ei.getSettings();h(V)}catch(V){console.error("Failed to fetch app settings:",V)}},[]),Fe=S.useCallback(async()=>{try{const V=await ei.getUndecryptedPacketCount();X(V.count)}catch(V){console.error("Failed to fetch undecrypted count:",V)}},[]),He=200,ai=S.useCallback(async(V=!1)=>{if(!x){k([]),v(!1);return}V&&F(!0);try{const J=await ei.getMessages({type:x.type==="channel"?"CHAN":"PRIV",conversation_key:x.id,limit:He});k(J),v(J.length>=He)}catch(J){console.error("Failed to fetch messages:",J)}finally{V&&F(!1)}},[x]),Ge=S.useCallback(async()=>{if(!(!x||A||!j)){C(!0);try{const V=await ei.getMessages({type:x.type==="channel"?"CHAN":"PRIV",conversation_key:x.id,limit:He,offset:z.length});V.length>0&&k(J=>[...J,...V]),v(V.length>=He)}catch(V){console.error("Failed to fetch older messages:",V)}finally{C(!1)}}},[x,A,j,z.length]);S.useEffect(()=>{ce(),Ae(),Fe()},[ce,Ae,Fe]);const Oe=S.useCallback(()=>{const V=Jj();if(!V)return null;if(V.type==="raw")return{type:"raw",id:"raw",name:"Raw Packet Feed"};if(V.type==="channel"){const J=g.find(re=>re.name===V.name||re.name===`#${V.name}`);if(J)return{type:"channel",id:J.key,name:J.name}}if(V.type==="contact"){const J=d.find(re=>Kn(re.name,re.public_key)===V.name);if(J)return{type:"contact",id:J.public_key,name:Kn(J.name,J.public_key)}}return null},[g,d]),Me=S.useRef(!1);S.useEffect(()=>{if(Me.current||x||g.length===0&&d.length===0)return;const V=Oe();if(V){D(V),Me.current=!0;return}const J=g.find(re=>re.name==="Public");J&&(D({type:"channel",id:J.key,name:J.name}),Me.current=!0)},[g,d,x,Oe]);const Xe=S.useRef(new Set),Ve=S.useRef(new Set);S.useEffect(()=>{const V=g.filter(ye=>!Xe.current.has(ye.key)),J=d.filter(ye=>ye.public_key&&!Ve.current.has(ye.public_key));if(V.length===0&&J.length===0)return;V.forEach(ye=>Xe.current.add(ye.key)),J.forEach(ye=>Ve.current.add(ye.public_key)),(async()=>{const ye=[...V.map(fe=>({type:"CHAN",conversation_key:fe.key})),...J.map(fe=>({type:"PRIV",conversation_key:fe.public_key}))];if(ye.length!==0)try{const fe=await ei.getMessagesBulk(ye,100),_e=Yk(),ni={},Ce={};for Line truncated
+1 -1
View File
@@ -13,7 +13,7 @@
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<script type="module" crossorigin src="/assets/index-CeWxUYMt.js"></script>
<script type="module" crossorigin src="/assets/index-BQNVNq9u.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CacA_plj.css">
</head>
<body>
+51 -552
View File
@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { api } from './api';
import { useWebSocket } from './useWebSocket';
import { useRepeaterMode, useUnreadCounts, useConversationMessages } from './hooks';
import { StatusBar } from './components/StatusBar';
import { Sidebar } from './components/Sidebar';
import { MessageList } from './components/MessageList';
@@ -11,18 +12,11 @@ import { RawPacketList } from './components/RawPacketList';
import { CrackerPanel } from './components/CrackerPanel';
import { Sheet, SheetContent, SheetHeader, SheetTitle } from './components/ui/sheet';
import { Toaster, toast } from './components/ui/sonner';
import {
getLastMessageTimes,
getLastReadTimes,
setLastMessageTime,
setLastReadTime,
getStateKey,
type ConversationTimes,
} from './utils/conversationState';
import { getStateKey } from './utils/conversationState';
import { pubkeysMatch, getContactDisplayName } from './utils/pubkey';
import { parseHashConversation, updateUrlHash } from './utils/urlHash';
import { cn } from '@/lib/utils';
import type {
AclEntry,
AppSettings,
AppSettingsUpdate,
Contact,
@@ -30,157 +24,61 @@ import type {
Conversation,
HealthStatus,
Message,
NeighborInfo,
RawPacket,
RadioConfig,
RadioConfigUpdate,
TelemetryResponse,
} from './types';
import { CONTACT_TYPE_REPEATER } from './types';
const MAX_RAW_PACKETS = 500; // Limit stored packets to prevent memory issues
// Format seconds into human-readable duration (e.g., 1d17h2m, 1h5m, 3m)
function formatDuration(seconds: number): string {
if (seconds < 60) return `${seconds}s`;
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const mins = Math.floor((seconds % 3600) / 60);
if (days > 0) {
if (hours > 0 && mins > 0) return `${days}d${hours}h${mins}m`;
if (hours > 0) return `${days}d${hours}h`;
if (mins > 0) return `${days}d${mins}m`;
return `${days}d`;
}
if (hours > 0) {
return mins > 0 ? `${hours}h${mins}m` : `${hours}h`;
}
return `${mins}m`;
}
// Format telemetry response as human-readable text
function formatTelemetry(telemetry: TelemetryResponse): string {
const lines = [
`Telemetry`,
`Battery Voltage: ${telemetry.battery_volts.toFixed(3)}V`,
`Uptime: ${formatDuration(telemetry.uptime_seconds)}`,
`TX Airtime: ${formatDuration(telemetry.airtime_seconds)}`,
`RX Airtime: ${formatDuration(telemetry.rx_airtime_seconds)}`,
'',
`Noise Floor: ${telemetry.noise_floor_dbm} dBm`,
`Last RSSI: ${telemetry.last_rssi_dbm} dBm`,
`Last SNR: ${telemetry.last_snr_db.toFixed(1)} dB`,
'',
`Packets: ${telemetry.packets_received.toLocaleString()} rx / ${telemetry.packets_sent.toLocaleString()} tx`,
`Flood: ${telemetry.recv_flood.toLocaleString()} rx / ${telemetry.sent_flood.toLocaleString()} tx`,
`Direct: ${telemetry.recv_direct.toLocaleString()} rx / ${telemetry.sent_direct.toLocaleString()} tx`,
`Duplicates: ${telemetry.flood_dups.toLocaleString()} flood / ${telemetry.direct_dups.toLocaleString()} direct`,
'',
`TX Queue: ${telemetry.tx_queue_len}`,
`Debug Flags: ${telemetry.full_events}`,
];
return lines.join('\n');
}
// Format neighbors list as human-readable text
function formatNeighbors(neighbors: NeighborInfo[]): string {
if (neighbors.length === 0) {
return 'Neighbors\nNo neighbors reported';
}
// Sort by SNR descending (highest first)
const sorted = [...neighbors].sort((a, b) => b.snr - a.snr);
const lines = [`Neighbors (${sorted.length})`];
for (const n of sorted) {
const name = n.name || n.pubkey_prefix;
const snr = n.snr >= 0 ? `+${n.snr.toFixed(1)}` : n.snr.toFixed(1);
lines.push(`${name}, ${snr} dB [${formatDuration(n.last_heard_seconds)} ago]`);
}
return lines.join('\n');
}
// Format ACL list as human-readable text
function formatAcl(acl: AclEntry[]): string {
if (acl.length === 0) {
return 'ACL\nNo ACL entries';
}
const lines = [`ACL (${acl.length})`];
for (const entry of acl) {
const name = entry.name || entry.pubkey_prefix;
lines.push(`${name}: ${entry.permission_name}`);
}
return lines.join('\n');
}
// Generate a key for deduplicating messages by content
function getMessageContentKey(msg: Message): string {
return `${msg.type}-${msg.conversation_key}-${msg.text}-${msg.sender_timestamp}`;
}
// Parse URL hash to get conversation (e.g., #channel/Public or #contact/JohnDoe or #raw)
function parseHashConversation(): { type: 'channel' | 'contact' | 'raw'; name: string } | null {
const hash = window.location.hash.slice(1); // Remove leading #
if (!hash) return null;
if (hash === 'raw') {
return { type: 'raw', name: 'raw' };
}
const slashIndex = hash.indexOf('/');
if (slashIndex === -1) return null;
const type = hash.slice(0, slashIndex);
const name = decodeURIComponent(hash.slice(slashIndex + 1));
if ((type === 'channel' || type === 'contact') && name) {
return { type, name };
}
return null;
}
// Generate URL hash from conversation
function getConversationHash(conv: Conversation | null): string {
if (!conv) return '';
if (conv.type === 'raw') return '#raw';
// Strip leading # from channel names for cleaner URLs
const name = conv.type === 'channel' && conv.name.startsWith('#')
? conv.name.slice(1)
: conv.name;
return `#${conv.type}/${encodeURIComponent(name)}`;
}
const MAX_RAW_PACKETS = 500;
export function App() {
const messageInputRef = useRef<MessageInputHandle>(null);
const activeConversationRef = useRef<Conversation | null>(null);
const seenMessageContent = useRef<Set<string>>(new Set());
const [health, setHealth] = useState<HealthStatus | null>(null);
const [config, setConfig] = useState<RadioConfig | null>(null);
const [appSettings, setAppSettings] = useState<AppSettings | null>(null);
const [contacts, setContacts] = useState<Contact[]>([]);
const [channels, setChannels] = useState<Channel[]>([]);
const [messages, setMessages] = useState<Message[]>([]);
const [hasOlderMessages, setHasOlderMessages] = useState(false);
const [loadingOlder, setLoadingOlder] = useState(false);
const [rawPackets, setRawPackets] = useState<RawPacket[]>([]);
const [activeConversation, setActiveConversation] = useState<Conversation | null>(null);
const [messagesLoading, setMessagesLoading] = useState(false);
const [showNewMessage, setShowNewMessage] = useState(false);
const [showConfig, setShowConfig] = useState(false);
const [sidebarOpen, setSidebarOpen] = useState(false);
const [undecryptedCount, setUndecryptedCount] = useState(0);
const [showCracker, setShowCracker] = useState(false);
const [crackerRunning, setCrackerRunning] = useState(false);
// Track if we've logged into the current repeater (for CLI command mode)
const [repeaterLoggedIn, setRepeaterLoggedIn] = useState(false);
// Track last message times (persisted in localStorage, used for sorting)
const [lastMessageTimes, setLastMessageTimes] = useState<ConversationTimes>(getLastMessageTimes);
// Track unread counts (calculated on load and incremented during session)
const [unreadCounts, setUnreadCounts] = useState<Record<string, number>>({});
// Track previous health status to detect changes
const prevHealthRef = useRef<HealthStatus | null>(null);
// Custom hooks for extracted functionality
const {
messages,
messagesLoading,
loadingOlder,
hasOlderMessages,
setMessages,
fetchMessages,
fetchOlderMessages,
addMessageIfNew,
updateMessageAck,
} = useConversationMessages(activeConversation);
const {
unreadCounts,
lastMessageTimes,
incrementUnread,
markAllRead,
trackNewMessage,
} = useUnreadCounts(channels, contacts, activeConversation);
const {
repeaterLoggedIn,
activeContactIsRepeater,
handleTelemetryRequest,
handleRepeaterCommand,
} = useRepeaterMode(activeConversation, contacts, setMessages);
// WebSocket handlers - memoized to prevent reconnection loops
const wsHandlers = useMemo(() => ({
onHealth: (data: HealthStatus) => {
@@ -211,27 +109,6 @@ export function App() {
onMessage: (msg: Message) => {
const activeConv = activeConversationRef.current;
// Skip duplicate messages (same content + timestamp)
const contentKey = getMessageContentKey(msg);
if (seenMessageContent.current.has(contentKey)) {
console.debug('Duplicate message content ignored:', contentKey.slice(0, 50));
return;
}
seenMessageContent.current.add(contentKey);
// Limit set size to prevent memory issues (keep last 1000)
if (seenMessageContent.current.size > 1000) {
const entries = Array.from(seenMessageContent.current);
seenMessageContent.current = new Set(entries.slice(-500));
}
// Determine conversation key for this message
let conversationKey: string | null = null;
if (msg.type === 'CHAN' && msg.conversation_key) {
conversationKey = getStateKey('channel', msg.conversation_key);
} else if (msg.type === 'PRIV' && msg.conversation_key) {
conversationKey = getStateKey('contact', msg.conversation_key);
}
// Check if message belongs to the active conversation
const isForActiveConversation = (() => {
if (!activeConv) return false;
@@ -239,7 +116,6 @@ export function App() {
return msg.conversation_key === activeConv.id;
}
if (msg.type === 'PRIV' && activeConv.type === 'contact') {
// Match by public key or prefix (either could be full key or prefix)
return msg.conversation_key && pubkeysMatch(activeConv.id, msg.conversation_key);
}
return false;
@@ -247,37 +123,31 @@ export function App() {
// Only add to message list if it's for the active conversation
if (isForActiveConversation) {
setMessages((prev) => {
if (prev.some((m) => m.id === msg.id)) {
return prev;
}
return [...prev, msg];
});
addMessageIfNew(msg);
}
// Track last message time for sorting and unread detection
if (conversationKey) {
const timestamp = msg.received_at || Math.floor(Date.now() / 1000);
const updated = setLastMessageTime(conversationKey, timestamp);
setLastMessageTimes(updated);
// Track for unread counts and sorting
trackNewMessage(msg);
// Count unread messages during this session (for non-active, incoming messages)
if (!msg.outgoing && !isForActiveConversation) {
setUnreadCounts((prev) => ({
...prev,
[conversationKey]: (prev[conversationKey] || 0) + 1,
}));
// Count unread for non-active, incoming messages
if (!msg.outgoing && !isForActiveConversation) {
let stateKey: string | null = null;
if (msg.type === 'CHAN' && msg.conversation_key) {
stateKey = getStateKey('channel', msg.conversation_key);
} else if (msg.type === 'PRIV' && msg.conversation_key) {
stateKey = getStateKey('contact', msg.conversation_key);
}
if (stateKey) {
incrementUnread(stateKey);
}
}
},
onContact: (contact: Contact) => {
// Update or add contact, preserving existing non-null values
setContacts((prev) => {
const idx = prev.findIndex((c) => c.public_key === contact.public_key);
if (idx >= 0) {
const updated = [...prev];
const existing = prev[idx];
// Merge: prefer new non-null values, but keep existing values if new is null
updated[idx] = {
...existing,
...contact,
@@ -293,11 +163,9 @@ export function App() {
},
onRawPacket: (packet: RawPacket) => {
setRawPackets((prev) => {
// Check if packet already exists
if (prev.some((p) => p.id === packet.id)) {
return prev;
}
// Limit to MAX_RAW_PACKETS, removing oldest
const updated = [...prev, packet];
if (updated.length > MAX_RAW_PACKETS) {
return updated.slice(-MAX_RAW_PACKETS);
@@ -306,18 +174,9 @@ export function App() {
});
},
onMessageAcked: (messageId: number, ackCount: number) => {
// Update message acked count
setMessages((prev) => {
const idx = prev.findIndex((m) => m.id === messageId);
if (idx >= 0) {
const updated = [...prev];
updated[idx] = { ...prev[idx], acked: ackCount };
return updated;
}
return prev;
});
updateMessageAck(messageId, ackCount);
},
}), []);
}), [addMessageIfNew, trackNewMessage, incrementUnread, updateMessageAck]);
// Connect to WebSocket
useWebSocket(wsHandlers);
@@ -352,64 +211,7 @@ export function App() {
}
}, []);
const MESSAGE_PAGE_SIZE = 200;
// Fetch messages for active conversation
const fetchMessages = useCallback(async (showLoading = false) => {
if (!activeConversation) {
setMessages([]);
setHasOlderMessages(false);
return;
}
if (showLoading) {
setMessagesLoading(true);
}
try {
const data = await api.getMessages({
type: activeConversation.type === 'channel' ? 'CHAN' : 'PRIV',
conversation_key: activeConversation.id,
limit: MESSAGE_PAGE_SIZE,
});
setMessages(data);
// If we got a full page, there might be more
setHasOlderMessages(data.length >= MESSAGE_PAGE_SIZE);
} catch (err) {
console.error('Failed to fetch messages:', err);
} finally {
if (showLoading) {
setMessagesLoading(false);
}
}
}, [activeConversation]);
// Fetch older messages (pagination)
const fetchOlderMessages = useCallback(async () => {
if (!activeConversation || loadingOlder || !hasOlderMessages) return;
setLoadingOlder(true);
try {
const data = await api.getMessages({
type: activeConversation.type === 'channel' ? 'CHAN' : 'PRIV',
conversation_key: activeConversation.id,
limit: MESSAGE_PAGE_SIZE,
offset: messages.length,
});
if (data.length > 0) {
// Prepend older messages (they come sorted DESC, so older are at the end)
setMessages(prev => [...prev, ...data]);
}
// If we got less than a full page, no more messages
setHasOlderMessages(data.length >= MESSAGE_PAGE_SIZE);
} catch (err) {
console.error('Failed to fetch older messages:', err);
} finally {
setLoadingOlder(false);
}
}, [activeConversation, loadingOlder, hasOlderMessages, messages.length]);
// Initial fetch for config and settings (WebSocket handles health/contacts/channels)
// Initial fetch for config and settings
useEffect(() => {
fetchConfig();
fetchAppSettings();
@@ -425,7 +227,6 @@ export function App() {
return { type: 'raw', id: 'raw', name: 'Raw Packet Feed' };
}
if (hashConv.type === 'channel') {
// Match with or without leading # (URL strips it for cleaner URLs)
const channel = channels.find(c => c.name === hashConv.name || c.name === `#${hashConv.name}`);
if (channel) {
return { type: 'channel', id: channel.key, name: channel.name };
@@ -450,7 +251,6 @@ export function App() {
if (hasSetDefaultConversation.current || activeConversation) return;
if (channels.length === 0 && contacts.length === 0) return;
// Try to restore from URL hash first
const conv = resolveHashToConversation();
if (conv) {
setActiveConversation(conv);
@@ -458,7 +258,6 @@ export function App() {
return;
}
// Fall back to Public channel
const publicChannel = channels.find(c => c.name === 'Public');
if (publicChannel) {
setActiveConversation({
@@ -470,137 +269,14 @@ export function App() {
}
}, [channels, contacts, activeConversation, resolveHashToConversation]);
// Fetch messages and count unreads for all conversations on load (single bulk request)
const fetchedChannels = useRef<Set<string>>(new Set());
const fetchedContacts = useRef<Set<string>>(new Set());
useEffect(() => {
// Find channels and contacts we haven't fetched yet
const newChannels = channels.filter(c => !fetchedChannels.current.has(c.key));
const newContacts = contacts.filter(c => c.public_key && !fetchedContacts.current.has(c.public_key));
if (newChannels.length === 0 && newContacts.length === 0) return;
// Mark as fetched before starting (to avoid duplicate fetches if effect re-runs)
newChannels.forEach(c => fetchedChannels.current.add(c.key));
newContacts.forEach(c => fetchedContacts.current.add(c.public_key));
const fetchAndCountUnreads = async () => {
// Build list of conversations to fetch
const conversations: Array<{ type: 'PRIV' | 'CHAN'; conversation_key: string }> = [
...newChannels.map(c => ({ type: 'CHAN' as const, conversation_key: c.key })),
...newContacts.map(c => ({ type: 'PRIV' as const, conversation_key: c.public_key })),
];
if (conversations.length === 0) return;
try {
// Single bulk request for all conversations
const bulkMessages = await api.getMessagesBulk(conversations, 100);
// Read lastReadTimes fresh from localStorage for accurate comparison
const currentReadTimes = getLastReadTimes();
const newUnreadCounts: Record<string, number> = {};
const newLastMessageTimes: Record<string, number> = {};
// Process channel messages
for (const channel of newChannels) {
const msgs = bulkMessages[`CHAN:${channel.key}`] || [];
if (msgs.length > 0) {
const key = getStateKey('channel', channel.key);
const lastRead = currentReadTimes[key] || 0;
const unreadCount = msgs.filter(m => !m.outgoing && m.received_at > lastRead).length;
if (unreadCount > 0) {
newUnreadCounts[key] = unreadCount;
}
const latestTime = Math.max(...msgs.map(m => m.received_at));
newLastMessageTimes[key] = latestTime;
setLastMessageTime(key, latestTime);
}
}
// Process contact messages
for (const contact of newContacts) {
const msgs = bulkMessages[`PRIV:${contact.public_key}`] || [];
if (msgs.length > 0) {
const key = getStateKey('contact', contact.public_key);
const lastRead = currentReadTimes[key] || 0;
const unreadCount = msgs.filter(m => !m.outgoing && m.received_at > lastRead).length;
if (unreadCount > 0) {
newUnreadCounts[key] = unreadCount;
}
const latestTime = Math.max(...msgs.map(m => m.received_at));
newLastMessageTimes[key] = latestTime;
setLastMessageTime(key, latestTime);
}
}
// Update state with all the counts and times
if (Object.keys(newUnreadCounts).length > 0) {
setUnreadCounts(prev => ({ ...prev, ...newUnreadCounts }));
}
setLastMessageTimes(getLastMessageTimes());
} catch (err) {
console.error('Failed to fetch messages bulk:', err);
}
};
fetchAndCountUnreads();
}, [channels, contacts]);
// Keep ref in sync with state and mark conversation as read when viewed
// Keep ref in sync and update URL hash
useEffect(() => {
activeConversationRef.current = activeConversation;
// Reset repeater login state when conversation changes
setRepeaterLoggedIn(false);
// Mark conversation as read when user views it
if (activeConversation && activeConversation.type !== 'raw') {
const key = getStateKey(
activeConversation.type as 'channel' | 'contact',
activeConversation.id
);
// Update localStorage-based read time
const now = Math.floor(Date.now() / 1000);
setLastReadTime(key, now);
// Clear unread count for this conversation
setUnreadCounts((prev) => {
if (prev[key]) {
const next = { ...prev };
delete next[key];
return next;
}
return prev;
});
}
// Update URL hash (replaceState doesn't add to history)
if (activeConversation) {
const newHash = getConversationHash(activeConversation);
if (newHash !== window.location.hash) {
window.history.replaceState(null, '', newHash);
}
updateUrlHash(activeConversation);
}
}, [activeConversation]);
// Fetch messages when conversation changes
useEffect(() => {
fetchMessages(true);
}, [fetchMessages]);
// Check if active conversation is a repeater
const activeContactIsRepeater = useMemo(() => {
if (!activeConversation || activeConversation.type !== 'contact') return false;
const contact = contacts.find(c => c.public_key === activeConversation.id);
return contact?.type === CONTACT_TYPE_REPEATER;
}, [activeConversation, contacts]);
// Send message handler
const handleSendMessage = useCallback(
async (text: string) => {
@@ -611,159 +287,11 @@ export function App() {
} else {
await api.sendDirectMessage(activeConversation.id, text);
}
// Message will arrive via WebSocket, but fetch to be safe
await fetchMessages();
},
[activeConversation, fetchMessages]
);
// Request telemetry from a repeater
const handleTelemetryRequest = useCallback(
async (password: string) => {
if (!activeConversation || activeConversation.type !== 'contact') return;
if (!activeContactIsRepeater) return;
try {
const telemetry = await api.requestTelemetry(activeConversation.id, password);
const now = Math.floor(Date.now() / 1000);
// Create a local message to display the telemetry (not persisted to database)
const telemetryMessage: Message = {
id: -Date.now(), // Negative ID to avoid collision with real messages
type: 'PRIV',
conversation_key: activeConversation.id,
text: formatTelemetry(telemetry),
sender_timestamp: now,
received_at: now,
path_len: null,
txt_type: 0,
signature: null,
outgoing: false, // Show as incoming (from the repeater)
acked: 1, // Mark as acked since it's a response
};
// Create a second message for neighbors
const neighborsMessage: Message = {
id: -Date.now() - 1, // Different ID
type: 'PRIV',
conversation_key: activeConversation.id,
text: formatNeighbors(telemetry.neighbors),
sender_timestamp: now,
received_at: now,
path_len: null,
txt_type: 0,
signature: null,
outgoing: false,
acked: 1,
};
// Create a third message for ACL
const aclMessage: Message = {
id: -Date.now() - 2, // Different ID
type: 'PRIV',
conversation_key: activeConversation.id,
text: formatAcl(telemetry.acl),
sender_timestamp: now,
received_at: now,
path_len: null,
txt_type: 0,
signature: null,
outgoing: false,
acked: 1,
};
// Add all messages to the list
setMessages((prev) => [...prev, telemetryMessage, neighborsMessage, aclMessage]);
// Mark as logged in for CLI command mode
setRepeaterLoggedIn(true);
} catch (err) {
// Show error as a local message
const errorMessage: Message = {
id: -Date.now(),
type: 'PRIV',
conversation_key: activeConversation.id,
text: `Telemetry request failed: ${err instanceof Error ? err.message : 'Unknown error'}`,
sender_timestamp: Math.floor(Date.now() / 1000),
received_at: Math.floor(Date.now() / 1000),
path_len: null,
txt_type: 0,
signature: null,
outgoing: false,
acked: 1,
};
setMessages((prev) => [...prev, errorMessage]);
}
},
[activeConversation, activeContactIsRepeater]
);
// Send CLI command to a repeater (after logged in)
const handleRepeaterCommand = useCallback(
async (command: string) => {
if (!activeConversation || activeConversation.type !== 'contact') return;
if (!activeContactIsRepeater || !repeaterLoggedIn) return;
const now = Math.floor(Date.now() / 1000);
// Show the command as an outgoing message
const commandMessage: Message = {
id: -Date.now(),
type: 'PRIV',
conversation_key: activeConversation.id,
text: `> ${command}`,
sender_timestamp: now,
received_at: now,
path_len: null,
txt_type: 0,
signature: null,
outgoing: true,
acked: 1,
};
setMessages((prev) => [...prev, commandMessage]);
try {
const response = await api.sendRepeaterCommand(activeConversation.id, command);
// Use the actual timestamp from the repeater if available
const responseTimestamp = response.sender_timestamp ?? now;
// Show the response
const responseMessage: Message = {
id: -Date.now() - 1,
type: 'PRIV',
conversation_key: activeConversation.id,
text: response.response,
sender_timestamp: responseTimestamp,
received_at: now,
path_len: null,
txt_type: 0,
signature: null,
outgoing: false,
acked: 1,
};
setMessages((prev) => [...prev, responseMessage]);
} catch (err) {
const errorMessage: Message = {
id: -Date.now() - 1,
type: 'PRIV',
conversation_key: activeConversation.id,
text: `Command failed: ${err instanceof Error ? err.message : 'Unknown error'}`,
sender_timestamp: now,
received_at: now,
path_len: null,
txt_type: 0,
signature: null,
outgoing: false,
acked: 1,
};
setMessages((prev) => [...prev, errorMessage]);
}
},
[activeConversation, activeContactIsRepeater, repeaterLoggedIn]
);
// Config save handler
const handleSaveConfig = useCallback(async (update: RadioConfigUpdate) => {
await api.updateRadioConfig(update);
@@ -785,12 +313,9 @@ export function App() {
// Reboot radio handler
const handleReboot = useCallback(async () => {
await api.rebootRadio();
// Immediately show disconnected state
setHealth((prev) =>
prev ? { ...prev, radio_connected: false } : prev
);
// Health updates will come via WebSocket when reconnected
// But also poll as backup
const pollUntilReconnected = async () => {
for (let i = 0; i < 30; i++) {
await new Promise((r) => setTimeout(r, 1000));
@@ -829,28 +354,6 @@ export function App() {
setSidebarOpen(false);
}, []);
// Mark all conversations as read
const handleMarkAllRead = useCallback(() => {
const now = Math.floor(Date.now() / 1000);
// Update localStorage for all channels
for (const channel of channels) {
const key = getStateKey('channel', channel.key);
setLastReadTime(key, now);
}
// Update localStorage for all contacts
for (const contact of contacts) {
if (contact.public_key) {
const key = getStateKey('contact', contact.public_key);
setLastReadTime(key, now);
}
}
// Clear all unread counts
setUnreadCounts({});
}, [channels, contacts]);
// Delete channel handler
const handleDeleteChannel = useCallback(async (key: string) => {
if (!confirm('Delete this channel? Message history will be preserved.')) return;
@@ -893,7 +396,6 @@ export function App() {
};
setContacts((prev) => [...prev, newContact]);
// Open the new contact
setActiveConversation({
type: 'contact',
id: publicKey,
@@ -911,11 +413,9 @@ export function App() {
const handleCreateChannel = useCallback(
async (name: string, key: string, tryHistorical: boolean) => {
const created = await api.createChannel(name, key);
// Channel will be broadcast via WebSocket, but fetch to be safe
const data = await api.getChannels();
setChannels(data);
// Open the new channel (use created.key as the id)
setActiveConversation({
type: 'channel',
id: created.key,
@@ -942,7 +442,6 @@ export function App() {
const data = await api.getChannels();
setChannels(data);
// Open the new channel (use created.key as the id)
setActiveConversation({
type: 'channel',
id: created.key,
@@ -976,7 +475,7 @@ export function App() {
showCracker={showCracker}
crackerRunning={crackerRunning}
onToggleCracker={() => setShowCracker((prev) => !prev)}
onMarkAllRead={handleMarkAllRead}
onMarkAllRead={markAllRead}
/>
);
+3
View File
@@ -0,0 +1,3 @@
export { useRepeaterMode, type UseRepeaterModeResult, formatDuration, formatTelemetry, formatNeighbors, formatAcl } from './useRepeaterMode';
export { useUnreadCounts, type UseUnreadCountsResult } from './useUnreadCounts';
export { useConversationMessages, type UseConversationMessagesResult, getMessageContentKey } from './useConversationMessages';
@@ -0,0 +1,154 @@
import { useState, useCallback, useEffect, useRef } from 'react';
import { api } from '../api';
import type { Conversation, Message } from '../types';
const MESSAGE_PAGE_SIZE = 200;
// Generate a key for deduplicating messages by content
export function getMessageContentKey(msg: Message): string {
return `${msg.type}-${msg.conversation_key}-${msg.text}-${msg.sender_timestamp}`;
}
export interface UseConversationMessagesResult {
messages: Message[];
messagesLoading: boolean;
loadingOlder: boolean;
hasOlderMessages: boolean;
setMessages: React.Dispatch<React.SetStateAction<Message[]>>;
fetchMessages: (showLoading?: boolean) => Promise<void>;
fetchOlderMessages: () => Promise<void>;
addMessageIfNew: (msg: Message) => boolean;
updateMessageAck: (messageId: number, ackCount: number) => void;
}
export function useConversationMessages(
activeConversation: Conversation | null
): UseConversationMessagesResult {
const [messages, setMessages] = useState<Message[]>([]);
const [messagesLoading, setMessagesLoading] = useState(false);
const [loadingOlder, setLoadingOlder] = useState(false);
const [hasOlderMessages, setHasOlderMessages] = useState(false);
// Track seen message content for deduplication
const seenMessageContent = useRef<Set<string>>(new Set());
// Fetch messages for active conversation
const fetchMessages = useCallback(async (showLoading = false) => {
if (!activeConversation || activeConversation.type === 'raw') {
setMessages([]);
setHasOlderMessages(false);
return;
}
if (showLoading) {
setMessagesLoading(true);
}
try {
const data = await api.getMessages({
type: activeConversation.type === 'channel' ? 'CHAN' : 'PRIV',
conversation_key: activeConversation.id,
limit: MESSAGE_PAGE_SIZE,
});
setMessages(data);
// Track seen content for new messages
seenMessageContent.current.clear();
for (const msg of data) {
seenMessageContent.current.add(getMessageContentKey(msg));
}
// If we got a full page, there might be more
setHasOlderMessages(data.length >= MESSAGE_PAGE_SIZE);
} catch (err) {
console.error('Failed to fetch messages:', err);
} finally {
if (showLoading) {
setMessagesLoading(false);
}
}
}, [activeConversation]);
// Fetch older messages (pagination)
const fetchOlderMessages = useCallback(async () => {
if (!activeConversation || activeConversation.type === 'raw' || loadingOlder || !hasOlderMessages) return;
setLoadingOlder(true);
try {
const data = await api.getMessages({
type: activeConversation.type === 'channel' ? 'CHAN' : 'PRIV',
conversation_key: activeConversation.id,
limit: MESSAGE_PAGE_SIZE,
offset: messages.length,
});
if (data.length > 0) {
// Prepend older messages (they come sorted DESC, so older are at the end)
setMessages(prev => [...prev, ...data]);
// Track seen content
for (const msg of data) {
seenMessageContent.current.add(getMessageContentKey(msg));
}
}
// If we got less than a full page, no more messages
setHasOlderMessages(data.length >= MESSAGE_PAGE_SIZE);
} catch (err) {
console.error('Failed to fetch older messages:', err);
} finally {
setLoadingOlder(false);
}
}, [activeConversation, loadingOlder, hasOlderMessages, messages.length]);
// Fetch messages when conversation changes
useEffect(() => {
fetchMessages(true);
}, [fetchMessages]);
// Add a message if it's new (deduplication)
// Returns true if the message was added, false if it was a duplicate
const addMessageIfNew = useCallback((msg: Message): boolean => {
const contentKey = getMessageContentKey(msg);
if (seenMessageContent.current.has(contentKey)) {
console.debug('Duplicate message content ignored:', contentKey.slice(0, 50));
return false;
}
seenMessageContent.current.add(contentKey);
// Limit set size to prevent memory issues (keep last 500)
if (seenMessageContent.current.size > 1000) {
const entries = Array.from(seenMessageContent.current);
seenMessageContent.current = new Set(entries.slice(-500));
}
setMessages((prev) => {
if (prev.some((m) => m.id === msg.id)) {
return prev;
}
return [...prev, msg];
});
return true;
}, []);
// Update a message's ack count
const updateMessageAck = useCallback((messageId: number, ackCount: number) => {
setMessages((prev) => {
const idx = prev.findIndex((m) => m.id === messageId);
if (idx >= 0) {
const updated = [...prev];
updated[idx] = { ...prev[idx], acked: ackCount };
return updated;
}
return prev;
});
}, []);
return {
messages,
messagesLoading,
loadingOlder,
hasOlderMessages,
setMessages,
fetchMessages,
fetchOlderMessages,
addMessageIfNew,
updateMessageAck,
};
}
+226
View File
@@ -0,0 +1,226 @@
import { useState, useCallback, useMemo, useEffect } from 'react';
import { api } from '../api';
import type { Contact, Conversation, Message, TelemetryResponse, NeighborInfo, AclEntry } from '../types';
import { CONTACT_TYPE_REPEATER } from '../types';
// Format seconds into human-readable duration (e.g., 1d17h2m, 1h5m, 3m)
export function formatDuration(seconds: number): string {
if (seconds < 60) return `${seconds}s`;
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const mins = Math.floor((seconds % 3600) / 60);
if (days > 0) {
if (hours > 0 && mins > 0) return `${days}d${hours}h${mins}m`;
if (hours > 0) return `${days}d${hours}h`;
if (mins > 0) return `${days}d${mins}m`;
return `${days}d`;
}
if (hours > 0) {
return mins > 0 ? `${hours}h${mins}m` : `${hours}h`;
}
return `${mins}m`;
}
// Format telemetry response as human-readable text
export function formatTelemetry(telemetry: TelemetryResponse): string {
const lines = [
`Telemetry`,
`Battery Voltage: ${telemetry.battery_volts.toFixed(3)}V`,
`Uptime: ${formatDuration(telemetry.uptime_seconds)}`,
`TX Airtime: ${formatDuration(telemetry.airtime_seconds)}`,
`RX Airtime: ${formatDuration(telemetry.rx_airtime_seconds)}`,
'',
`Noise Floor: ${telemetry.noise_floor_dbm} dBm`,
`Last RSSI: ${telemetry.last_rssi_dbm} dBm`,
`Last SNR: ${telemetry.last_snr_db.toFixed(1)} dB`,
'',
`Packets: ${telemetry.packets_received.toLocaleString()} rx / ${telemetry.packets_sent.toLocaleString()} tx`,
`Flood: ${telemetry.recv_flood.toLocaleString()} rx / ${telemetry.sent_flood.toLocaleString()} tx`,
`Direct: ${telemetry.recv_direct.toLocaleString()} rx / ${telemetry.sent_direct.toLocaleString()} tx`,
`Duplicates: ${telemetry.flood_dups.toLocaleString()} flood / ${telemetry.direct_dups.toLocaleString()} direct`,
'',
`TX Queue: ${telemetry.tx_queue_len}`,
`Debug Flags: ${telemetry.full_events}`,
];
return lines.join('\n');
}
// Format neighbors list as human-readable text
export function formatNeighbors(neighbors: NeighborInfo[]): string {
if (neighbors.length === 0) {
return 'Neighbors\nNo neighbors reported';
}
// Sort by SNR descending (highest first)
const sorted = [...neighbors].sort((a, b) => b.snr - a.snr);
const lines = [`Neighbors (${sorted.length})`];
for (const n of sorted) {
const name = n.name || n.pubkey_prefix;
const snr = n.snr >= 0 ? `+${n.snr.toFixed(1)}` : n.snr.toFixed(1);
lines.push(`${name}, ${snr} dB [${formatDuration(n.last_heard_seconds)} ago]`);
}
return lines.join('\n');
}
// Format ACL list as human-readable text
export function formatAcl(acl: AclEntry[]): string {
if (acl.length === 0) {
return 'ACL\nNo ACL entries';
}
const lines = [`ACL (${acl.length})`];
for (const entry of acl) {
const name = entry.name || entry.pubkey_prefix;
lines.push(`${name}: ${entry.permission_name}`);
}
return lines.join('\n');
}
// Create a local message object (not persisted to database)
function createLocalMessage(
conversationKey: string,
text: string,
outgoing: boolean,
idOffset = 0
): Message {
const now = Math.floor(Date.now() / 1000);
return {
id: -Date.now() - idOffset,
type: 'PRIV',
conversation_key: conversationKey,
text,
sender_timestamp: now,
received_at: now,
path_len: null,
txt_type: 0,
signature: null,
outgoing,
acked: 1,
};
}
export interface UseRepeaterModeResult {
repeaterLoggedIn: boolean;
activeContactIsRepeater: boolean;
handleTelemetryRequest: (password: string) => Promise<void>;
handleRepeaterCommand: (command: string) => Promise<void>;
}
export function useRepeaterMode(
activeConversation: Conversation | null,
contacts: Contact[],
setMessages: React.Dispatch<React.SetStateAction<Message[]>>
): UseRepeaterModeResult {
const [repeaterLoggedIn, setRepeaterLoggedIn] = useState(false);
// Reset login state when conversation changes
useEffect(() => {
setRepeaterLoggedIn(false);
}, [activeConversation?.id]);
// Check if active conversation is a repeater
const activeContactIsRepeater = useMemo(() => {
if (!activeConversation || activeConversation.type !== 'contact') return false;
const contact = contacts.find(c => c.public_key === activeConversation.id);
return contact?.type === CONTACT_TYPE_REPEATER;
}, [activeConversation, contacts]);
// Request telemetry from a repeater
const handleTelemetryRequest = useCallback(
async (password: string) => {
if (!activeConversation || activeConversation.type !== 'contact') return;
if (!activeContactIsRepeater) return;
try {
const telemetry = await api.requestTelemetry(activeConversation.id, password);
// Create local messages to display the telemetry (not persisted to database)
const telemetryMessage = createLocalMessage(
activeConversation.id,
formatTelemetry(telemetry),
false,
0
);
const neighborsMessage = createLocalMessage(
activeConversation.id,
formatNeighbors(telemetry.neighbors),
false,
1
);
const aclMessage = createLocalMessage(
activeConversation.id,
formatAcl(telemetry.acl),
false,
2
);
// Add all messages to the list
setMessages((prev) => [...prev, telemetryMessage, neighborsMessage, aclMessage]);
// Mark as logged in for CLI command mode
setRepeaterLoggedIn(true);
} catch (err) {
const errorMessage = createLocalMessage(
activeConversation.id,
`Telemetry request failed: ${err instanceof Error ? err.message : 'Unknown error'}`,
false,
0
);
setMessages((prev) => [...prev, errorMessage]);
}
},
[activeConversation, activeContactIsRepeater, setMessages]
);
// Send CLI command to a repeater (after logged in)
const handleRepeaterCommand = useCallback(
async (command: string) => {
if (!activeConversation || activeConversation.type !== 'contact') return;
if (!activeContactIsRepeater || !repeaterLoggedIn) return;
// Show the command as an outgoing message
const commandMessage = createLocalMessage(
activeConversation.id,
`> ${command}`,
true,
0
);
setMessages((prev) => [...prev, commandMessage]);
try {
const response = await api.sendRepeaterCommand(activeConversation.id, command);
// Use the actual timestamp from the repeater if available
const responseMessage = createLocalMessage(
activeConversation.id,
response.response,
false,
1
);
if (response.sender_timestamp) {
responseMessage.sender_timestamp = response.sender_timestamp;
}
setMessages((prev) => [...prev, responseMessage]);
} catch (err) {
const errorMessage = createLocalMessage(
activeConversation.id,
`Command failed: ${err instanceof Error ? err.message : 'Unknown error'}`,
false,
1
);
setMessages((prev) => [...prev, errorMessage]);
}
},
[activeConversation, activeContactIsRepeater, repeaterLoggedIn, setMessages]
);
return {
repeaterLoggedIn,
activeContactIsRepeater,
handleTelemetryRequest,
handleRepeaterCommand,
};
}
+197
View File
@@ -0,0 +1,197 @@
import { useState, useCallback, useEffect, useRef } from 'react';
import { api } from '../api';
import {
getLastMessageTimes,
getLastReadTimes,
setLastMessageTime,
setLastReadTime,
getStateKey,
type ConversationTimes,
} from '../utils/conversationState';
import type { Channel, Contact, Conversation, Message } from '../types';
export interface UseUnreadCountsResult {
unreadCounts: Record<string, number>;
lastMessageTimes: ConversationTimes;
incrementUnread: (stateKey: string) => void;
markAllRead: () => void;
markConversationRead: (conv: Conversation) => void;
trackNewMessage: (msg: Message) => void;
}
export function useUnreadCounts(
channels: Channel[],
contacts: Contact[],
activeConversation: Conversation | null
): UseUnreadCountsResult {
const [unreadCounts, setUnreadCounts] = useState<Record<string, number>>({});
const [lastMessageTimes, setLastMessageTimes] = useState<ConversationTimes>(getLastMessageTimes);
// Track which channels/contacts we've already fetched unreads for
const fetchedChannels = useRef<Set<string>>(new Set());
const fetchedContacts = useRef<Set<string>>(new Set());
// Fetch messages and count unreads for new channels/contacts
useEffect(() => {
const newChannels = channels.filter(c => !fetchedChannels.current.has(c.key));
const newContacts = contacts.filter(c => c.public_key && !fetchedContacts.current.has(c.public_key));
if (newChannels.length === 0 && newContacts.length === 0) return;
// Mark as fetched before starting (to avoid duplicate fetches if effect re-runs)
newChannels.forEach(c => fetchedChannels.current.add(c.key));
newContacts.forEach(c => fetchedContacts.current.add(c.public_key));
const fetchAndCountUnreads = async () => {
const conversations: Array<{ type: 'PRIV' | 'CHAN'; conversation_key: string }> = [
...newChannels.map(c => ({ type: 'CHAN' as const, conversation_key: c.key })),
...newContacts.map(c => ({ type: 'PRIV' as const, conversation_key: c.public_key })),
];
if (conversations.length === 0) return;
try {
const bulkMessages = await api.getMessagesBulk(conversations, 100);
const currentReadTimes = getLastReadTimes();
const newUnreadCounts: Record<string, number> = {};
const newLastMessageTimes: Record<string, number> = {};
// Process channel messages
for (const channel of newChannels) {
const msgs = bulkMessages[`CHAN:${channel.key}`] || [];
if (msgs.length > 0) {
const key = getStateKey('channel', channel.key);
const lastRead = currentReadTimes[key] || 0;
const unreadCount = msgs.filter(m => !m.outgoing && m.received_at > lastRead).length;
if (unreadCount > 0) {
newUnreadCounts[key] = unreadCount;
}
const latestTime = Math.max(...msgs.map(m => m.received_at));
newLastMessageTimes[key] = latestTime;
setLastMessageTime(key, latestTime);
}
}
// Process contact messages
for (const contact of newContacts) {
const msgs = bulkMessages[`PRIV:${contact.public_key}`] || [];
if (msgs.length > 0) {
const key = getStateKey('contact', contact.public_key);
const lastRead = currentReadTimes[key] || 0;
const unreadCount = msgs.filter(m => !m.outgoing && m.received_at > lastRead).length;
if (unreadCount > 0) {
newUnreadCounts[key] = unreadCount;
}
const latestTime = Math.max(...msgs.map(m => m.received_at));
newLastMessageTimes[key] = latestTime;
setLastMessageTime(key, latestTime);
}
}
if (Object.keys(newUnreadCounts).length > 0) {
setUnreadCounts(prev => ({ ...prev, ...newUnreadCounts }));
}
setLastMessageTimes(getLastMessageTimes());
} catch (err) {
console.error('Failed to fetch messages bulk:', err);
}
};
fetchAndCountUnreads();
}, [channels, contacts]);
// Mark conversation as read when user views it
useEffect(() => {
if (activeConversation && activeConversation.type !== 'raw') {
const key = getStateKey(
activeConversation.type as 'channel' | 'contact',
activeConversation.id
);
const now = Math.floor(Date.now() / 1000);
setLastReadTime(key, now);
setUnreadCounts((prev) => {
if (prev[key]) {
const next = { ...prev };
delete next[key];
return next;
}
return prev;
});
}
}, [activeConversation]);
// Increment unread count for a conversation
const incrementUnread = useCallback((stateKey: string) => {
setUnreadCounts((prev) => ({
...prev,
[stateKey]: (prev[stateKey] || 0) + 1,
}));
}, []);
// Mark all conversations as read
const markAllRead = useCallback(() => {
const now = Math.floor(Date.now() / 1000);
for (const channel of channels) {
const key = getStateKey('channel', channel.key);
setLastReadTime(key, now);
}
for (const contact of contacts) {
if (contact.public_key) {
const key = getStateKey('contact', contact.public_key);
setLastReadTime(key, now);
}
}
setUnreadCounts({});
}, [channels, contacts]);
// Mark a specific conversation as read
const markConversationRead = useCallback((conv: Conversation) => {
if (conv.type === 'raw') return;
const key = getStateKey(conv.type as 'channel' | 'contact', conv.id);
const now = Math.floor(Date.now() / 1000);
setLastReadTime(key, now);
setUnreadCounts((prev) => {
if (prev[key]) {
const next = { ...prev };
delete next[key];
return next;
}
return prev;
});
}, []);
// Track a new incoming message for unread counts
const trackNewMessage = useCallback((msg: Message) => {
let conversationKey: string | null = null;
if (msg.type === 'CHAN' && msg.conversation_key) {
conversationKey = getStateKey('channel', msg.conversation_key);
} else if (msg.type === 'PRIV' && msg.conversation_key) {
conversationKey = getStateKey('contact', msg.conversation_key);
}
if (conversationKey) {
const timestamp = msg.received_at || Math.floor(Date.now() / 1000);
const updated = setLastMessageTime(conversationKey, timestamp);
setLastMessageTimes(updated);
}
}, []);
return {
unreadCounts,
lastMessageTimes,
incrementUnread,
markAllRead,
markConversationRead,
trackNewMessage,
};
}
+192
View File
@@ -0,0 +1,192 @@
/**
* Tests for URL hash utilities.
*
* These tests verify the URL hash parsing and generation
* for deep linking to conversations.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { parseHashConversation, getConversationHash } from '../utils/urlHash';
import type { Conversation } from '../types';
describe('parseHashConversation', () => {
let originalHash: string;
beforeEach(() => {
originalHash = window.location.hash;
});
afterEach(() => {
window.location.hash = originalHash;
});
it('returns null for empty hash', () => {
window.location.hash = '';
const result = parseHashConversation();
expect(result).toBeNull();
});
it('parses #raw as raw type', () => {
window.location.hash = '#raw';
const result = parseHashConversation();
expect(result).toEqual({ type: 'raw', name: 'raw' });
});
it('parses channel hash', () => {
window.location.hash = '#channel/Public';
const result = parseHashConversation();
expect(result).toEqual({ type: 'channel', name: 'Public' });
});
it('parses contact hash', () => {
window.location.hash = '#contact/Alice';
const result = parseHashConversation();
expect(result).toEqual({ type: 'contact', name: 'Alice' });
});
it('decodes URL-encoded names', () => {
window.location.hash = '#contact/John%20Doe';
const result = parseHashConversation();
expect(result).toEqual({ type: 'contact', name: 'John Doe' });
});
it('returns null for invalid type', () => {
window.location.hash = '#invalid/Test';
const result = parseHashConversation();
expect(result).toBeNull();
});
it('returns null for hash without slash', () => {
window.location.hash = '#channelPublic';
const result = parseHashConversation();
expect(result).toBeNull();
});
it('returns null for hash with empty name', () => {
window.location.hash = '#channel/';
const result = parseHashConversation();
expect(result).toBeNull();
});
it('handles channel names with special characters', () => {
window.location.hash = '#channel/Test%20Channel%21';
const result = parseHashConversation();
expect(result).toEqual({ type: 'channel', name: 'Test Channel!' });
});
});
describe('getConversationHash', () => {
it('returns empty string for null conversation', () => {
const result = getConversationHash(null);
expect(result).toBe('');
});
it('returns #raw for raw conversation', () => {
const conv: Conversation = { type: 'raw', id: 'raw', name: 'Raw Packet Feed' };
const result = getConversationHash(conv);
expect(result).toBe('#raw');
});
it('generates channel hash', () => {
const conv: Conversation = { type: 'channel', id: 'key123', name: 'Public' };
const result = getConversationHash(conv);
expect(result).toBe('#channel/Public');
});
it('generates contact hash', () => {
const conv: Conversation = { type: 'contact', id: 'pubkey123', name: 'Alice' };
const result = getConversationHash(conv);
expect(result).toBe('#contact/Alice');
});
it('strips leading # from channel names', () => {
const conv: Conversation = { type: 'channel', id: 'key123', name: '#TestChannel' };
const result = getConversationHash(conv);
expect(result).toBe('#channel/TestChannel');
});
it('encodes special characters in names', () => {
const conv: Conversation = { type: 'contact', id: 'key', name: 'John Doe' };
const result = getConversationHash(conv);
expect(result).toBe('#contact/John%20Doe');
});
it('does not strip # from contact names', () => {
const conv: Conversation = { type: 'contact', id: 'key', name: '#Hashtag' };
const result = getConversationHash(conv);
expect(result).toBe('#contact/%23Hashtag');
});
});
describe('parseHashConversation and getConversationHash roundtrip', () => {
let originalHash: string;
beforeEach(() => {
originalHash = window.location.hash;
});
afterEach(() => {
window.location.hash = originalHash;
});
it('channel roundtrip preserves data', () => {
const conv: Conversation = { type: 'channel', id: 'key123', name: 'Test Channel' };
const hash = getConversationHash(conv);
window.location.hash = hash;
const parsed = parseHashConversation();
expect(parsed).toEqual({ type: 'channel', name: 'Test Channel' });
});
it('contact roundtrip preserves data', () => {
const conv: Conversation = { type: 'contact', id: 'pubkey', name: 'Alice Bob' };
const hash = getConversationHash(conv);
window.location.hash = hash;
const parsed = parseHashConversation();
expect(parsed).toEqual({ type: 'contact', name: 'Alice Bob' });
});
it('raw roundtrip preserves type', () => {
const conv: Conversation = { type: 'raw', id: 'raw', name: 'Raw Packet Feed' };
const hash = getConversationHash(conv);
window.location.hash = hash;
const parsed = parseHashConversation();
expect(parsed).toEqual({ type: 'raw', name: 'raw' });
});
});
@@ -0,0 +1,112 @@
/**
* Tests for useConversationMessages hook utilities.
*
* These tests verify the message deduplication key generation.
*/
import { describe, it, expect } from 'vitest';
import { getMessageContentKey } from '../hooks/useConversationMessages';
import type { Message } from '../types';
function createMessage(overrides: Partial<Message> = {}): Message {
return {
id: 1,
type: 'CHAN',
conversation_key: 'channel123',
text: 'Hello world',
sender_timestamp: 1700000000,
received_at: 1700000001,
path_len: null,
txt_type: 0,
signature: null,
outgoing: false,
acked: 0,
...overrides,
};
}
describe('getMessageContentKey', () => {
it('generates key from type, conversation_key, text, and sender_timestamp', () => {
const msg = createMessage({
type: 'CHAN',
conversation_key: 'abc123',
text: 'Hello',
sender_timestamp: 1700000000,
});
const key = getMessageContentKey(msg);
expect(key).toBe('CHAN-abc123-Hello-1700000000');
});
it('generates different keys for different message types', () => {
const chanMsg = createMessage({ type: 'CHAN' });
const privMsg = createMessage({ type: 'PRIV' });
expect(getMessageContentKey(chanMsg)).not.toBe(getMessageContentKey(privMsg));
});
it('generates different keys for different conversation keys', () => {
const msg1 = createMessage({ conversation_key: 'channel1' });
const msg2 = createMessage({ conversation_key: 'channel2' });
expect(getMessageContentKey(msg1)).not.toBe(getMessageContentKey(msg2));
});
it('generates different keys for different text', () => {
const msg1 = createMessage({ text: 'Hello' });
const msg2 = createMessage({ text: 'World' });
expect(getMessageContentKey(msg1)).not.toBe(getMessageContentKey(msg2));
});
it('generates different keys for different timestamps', () => {
const msg1 = createMessage({ sender_timestamp: 1700000000 });
const msg2 = createMessage({ sender_timestamp: 1700000001 });
expect(getMessageContentKey(msg1)).not.toBe(getMessageContentKey(msg2));
});
it('generates same key for messages with same content', () => {
const msg1 = createMessage({
id: 1,
type: 'CHAN',
conversation_key: 'abc',
text: 'Test',
sender_timestamp: 1700000000,
});
const msg2 = createMessage({
id: 2, // Different ID
type: 'CHAN',
conversation_key: 'abc',
text: 'Test',
sender_timestamp: 1700000000,
});
expect(getMessageContentKey(msg1)).toBe(getMessageContentKey(msg2));
});
it('handles null sender_timestamp', () => {
const msg = createMessage({ sender_timestamp: null });
const key = getMessageContentKey(msg);
expect(key).toBe('CHAN-channel123-Hello world-null');
});
it('handles empty text', () => {
const msg = createMessage({ text: '' });
const key = getMessageContentKey(msg);
expect(key).toContain('--'); // Empty text between dashes
});
it('handles text with special characters', () => {
const msg = createMessage({ text: 'Hello: World! @user #channel' });
const key = getMessageContentKey(msg);
expect(key).toContain('Hello: World! @user #channel');
});
});
+239
View File
@@ -0,0 +1,239 @@
/**
* Tests for useRepeaterMode hook utilities.
*
* These tests verify the formatting functions used to display
* telemetry data from repeaters.
*/
import { describe, it, expect } from 'vitest';
import {
formatDuration,
formatTelemetry,
formatNeighbors,
formatAcl,
} from '../hooks/useRepeaterMode';
import type { TelemetryResponse, NeighborInfo, AclEntry } from '../types';
describe('formatDuration', () => {
it('formats seconds under a minute', () => {
expect(formatDuration(0)).toBe('0s');
expect(formatDuration(30)).toBe('30s');
expect(formatDuration(59)).toBe('59s');
});
it('formats minutes only', () => {
expect(formatDuration(60)).toBe('1m');
expect(formatDuration(120)).toBe('2m');
expect(formatDuration(300)).toBe('5m');
expect(formatDuration(3599)).toBe('59m');
});
it('formats hours and minutes', () => {
expect(formatDuration(3600)).toBe('1h');
expect(formatDuration(3660)).toBe('1h1m');
expect(formatDuration(7200)).toBe('2h');
expect(formatDuration(7380)).toBe('2h3m');
});
it('formats days only', () => {
expect(formatDuration(86400)).toBe('1d');
expect(formatDuration(172800)).toBe('2d');
});
it('formats days and hours', () => {
expect(formatDuration(90000)).toBe('1d1h');
expect(formatDuration(97200)).toBe('1d3h');
});
it('formats days and minutes (no hours)', () => {
expect(formatDuration(86700)).toBe('1d5m');
});
it('formats days, hours, and minutes', () => {
expect(formatDuration(90060)).toBe('1d1h1m');
expect(formatDuration(148920)).toBe('1d17h22m');
});
});
describe('formatTelemetry', () => {
it('formats telemetry response with all fields', () => {
const telemetry: TelemetryResponse = {
pubkey_prefix: 'abc123',
battery_volts: 4.123,
uptime_seconds: 90060, // 1d1h1m
airtime_seconds: 3600, // 1h
rx_airtime_seconds: 7200, // 2h
noise_floor_dbm: -120,
last_rssi_dbm: -90,
last_snr_db: 8.5,
packets_received: 1000,
packets_sent: 500,
recv_flood: 800,
sent_flood: 400,
recv_direct: 200,
sent_direct: 100,
flood_dups: 50,
direct_dups: 10,
tx_queue_len: 2,
full_events: 0,
neighbors: [],
acl: [],
};
const result = formatTelemetry(telemetry);
expect(result).toContain('Telemetry');
expect(result).toContain('Battery Voltage: 4.123V');
expect(result).toContain('Uptime: 1d1h1m');
expect(result).toContain('TX Airtime: 1h');
expect(result).toContain('RX Airtime: 2h');
expect(result).toContain('Noise Floor: -120 dBm');
expect(result).toContain('Last RSSI: -90 dBm');
expect(result).toContain('Last SNR: 8.5 dB');
expect(result).toContain('Packets: 1,000 rx / 500 tx');
expect(result).toContain('Flood: 800 rx / 400 tx');
expect(result).toContain('Direct: 200 rx / 100 tx');
expect(result).toContain('Duplicates: 50 flood / 10 direct');
expect(result).toContain('TX Queue: 2');
});
it('formats battery voltage with 3 decimal places', () => {
const telemetry: TelemetryResponse = {
pubkey_prefix: 'abc123',
battery_volts: 3.7,
uptime_seconds: 0,
airtime_seconds: 0,
rx_airtime_seconds: 0,
noise_floor_dbm: 0,
last_rssi_dbm: 0,
last_snr_db: 0,
packets_received: 0,
packets_sent: 0,
recv_flood: 0,
sent_flood: 0,
recv_direct: 0,
sent_direct: 0,
flood_dups: 0,
direct_dups: 0,
tx_queue_len: 0,
full_events: 0,
neighbors: [],
acl: [],
};
const result = formatTelemetry(telemetry);
expect(result).toContain('Battery Voltage: 3.700V');
});
});
describe('formatNeighbors', () => {
it('returns "No neighbors" message for empty list', () => {
const result = formatNeighbors([]);
expect(result).toBe('Neighbors\nNo neighbors reported');
});
it('formats single neighbor', () => {
const neighbors: NeighborInfo[] = [
{ pubkey_prefix: 'abc123', name: 'Alice', snr: 8.5, last_heard_seconds: 60 },
];
const result = formatNeighbors(neighbors);
expect(result).toContain('Neighbors (1)');
expect(result).toContain('Alice, +8.5 dB [1m ago]');
});
it('sorts neighbors by SNR descending', () => {
const neighbors: NeighborInfo[] = [
{ pubkey_prefix: 'aaa', name: 'Low', snr: -5, last_heard_seconds: 10 },
{ pubkey_prefix: 'bbb', name: 'High', snr: 10, last_heard_seconds: 20 },
{ pubkey_prefix: 'ccc', name: 'Mid', snr: 5, last_heard_seconds: 30 },
];
const result = formatNeighbors(neighbors);
const lines = result.split('\n');
expect(lines[1]).toContain('High');
expect(lines[2]).toContain('Mid');
expect(lines[3]).toContain('Low');
});
it('uses pubkey_prefix when name is null', () => {
const neighbors: NeighborInfo[] = [
{ pubkey_prefix: 'abc123def456', name: null, snr: 5, last_heard_seconds: 120 },
];
const result = formatNeighbors(neighbors);
expect(result).toContain('abc123def456, +5.0 dB [2m ago]');
});
it('formats negative SNR without plus sign', () => {
const neighbors: NeighborInfo[] = [
{ pubkey_prefix: 'abc', name: 'Test', snr: -3.5, last_heard_seconds: 60 },
];
const result = formatNeighbors(neighbors);
expect(result).toContain('Test, -3.5 dB');
});
it('formats last heard in various durations', () => {
const neighbors: NeighborInfo[] = [
{ pubkey_prefix: 'a', name: 'Seconds', snr: 0, last_heard_seconds: 45 },
{ pubkey_prefix: 'b', name: 'Minutes', snr: 0, last_heard_seconds: 300 },
{ pubkey_prefix: 'c', name: 'Hours', snr: 0, last_heard_seconds: 7200 },
];
const result = formatNeighbors(neighbors);
expect(result).toContain('Seconds, +0.0 dB [45s ago]');
expect(result).toContain('Minutes, +0.0 dB [5m ago]');
expect(result).toContain('Hours, +0.0 dB [2h ago]');
});
});
describe('formatAcl', () => {
it('returns "No ACL entries" message for empty list', () => {
const result = formatAcl([]);
expect(result).toBe('ACL\nNo ACL entries');
});
it('formats single ACL entry', () => {
const acl: AclEntry[] = [
{ pubkey_prefix: 'abc123', name: 'Alice', permission: 3, permission_name: 'Admin' },
];
const result = formatAcl(acl);
expect(result).toContain('ACL (1)');
expect(result).toContain('Alice: Admin');
});
it('formats multiple ACL entries', () => {
const acl: AclEntry[] = [
{ pubkey_prefix: 'aaa', name: 'Admin User', permission: 3, permission_name: 'Admin' },
{ pubkey_prefix: 'bbb', name: 'Read Only', permission: 1, permission_name: 'Read-only' },
{ pubkey_prefix: 'ccc', name: null, permission: 0, permission_name: 'Guest' },
];
const result = formatAcl(acl);
expect(result).toContain('ACL (3)');
expect(result).toContain('Admin User: Admin');
expect(result).toContain('Read Only: Read-only');
expect(result).toContain('ccc: Guest');
});
it('uses pubkey_prefix when name is null', () => {
const acl: AclEntry[] = [
{ pubkey_prefix: 'xyz789', name: null, permission: 2, permission_name: 'Read-write' },
];
const result = formatAcl(acl);
expect(result).toContain('xyz789: Read-write');
});
});
+46
View File
@@ -0,0 +1,46 @@
import type { Conversation } from '../types';
export interface ParsedHashConversation {
type: 'channel' | 'contact' | 'raw';
name: string;
}
// Parse URL hash to get conversation (e.g., #channel/Public or #contact/JohnDoe or #raw)
export function parseHashConversation(): ParsedHashConversation | null {
const hash = window.location.hash.slice(1); // Remove leading #
if (!hash) return null;
if (hash === 'raw') {
return { type: 'raw', name: 'raw' };
}
const slashIndex = hash.indexOf('/');
if (slashIndex === -1) return null;
const type = hash.slice(0, slashIndex);
const name = decodeURIComponent(hash.slice(slashIndex + 1));
if ((type === 'channel' || type === 'contact') && name) {
return { type, name };
}
return null;
}
// Generate URL hash from conversation
export function getConversationHash(conv: Conversation | null): string {
if (!conv) return '';
if (conv.type === 'raw') return '#raw';
// Strip leading # from channel names for cleaner URLs
const name = conv.type === 'channel' && conv.name.startsWith('#')
? conv.name.slice(1)
: conv.name;
return `#${conv.type}/${encodeURIComponent(name)}`;
}
// Update URL hash without adding to history
export function updateUrlHash(conv: Conversation | null): void {
const newHash = getConversationHash(conv);
if (newHash !== window.location.hash) {
window.history.replaceState(null, '', newHash || window.location.pathname);
}
}