@pi-chamber/web 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/bin/cli-entry.js +55 -0
- package/bin/cli-output.js +140 -0
- package/bin/cli-output.test.js +36 -0
- package/bin/cli.js +383 -0
- package/bin/cli.test.js +1065 -0
- package/bin/lib/DOCUMENTATION.md +31 -0
- package/bin/lib/cli-api-target.js +74 -0
- package/bin/lib/cli-args.js +846 -0
- package/bin/lib/cli-errors.js +18 -0
- package/bin/lib/cli-http.js +252 -0
- package/bin/lib/cli-lifecycle.js +421 -0
- package/bin/lib/cli-log-files.js +93 -0
- package/bin/lib/cli-network.js +186 -0
- package/bin/lib/cli-paths.js +128 -0
- package/bin/lib/cli-ports.js +51 -0
- package/bin/lib/cli-process.js +342 -0
- package/bin/lib/cli-startup.js +371 -0
- package/bin/lib/cli-tunnel-capabilities.js +9 -0
- package/bin/lib/cli-tunnel-profiles.js +366 -0
- package/bin/lib/cli-tunnel-utils.js +300 -0
- package/bin/lib/commands-connect-url.js +329 -0
- package/bin/lib/commands-lifecycle.js +396 -0
- package/bin/lib/commands-logs.js +110 -0
- package/bin/lib/commands-serve.js +419 -0
- package/bin/lib/commands-startup.js +64 -0
- package/bin/lib/commands-status.js +114 -0
- package/bin/lib/commands-tunnel.js +1761 -0
- package/bin/lib/commands-update.js +184 -0
- package/bin/lib/commands-update.test.js +98 -0
- package/dist/apple-touch-icon-120x120.png +0 -0
- package/dist/apple-touch-icon-152x152.png +0 -0
- package/dist/apple-touch-icon-167x167.png +0 -0
- package/dist/apple-touch-icon-180x180.png +0 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/apple-touch-icon.svg +13 -0
- package/dist/assets/CommitSection-DGGArAb2.js +1 -0
- package/dist/assets/DiagramEditor-ig5nYV6t.js +1 -0
- package/dist/assets/DiagramView-8Qr6pwwI.js +1 -0
- package/dist/assets/DiffView-BHS4m_K7.js +18 -0
- package/dist/assets/FilesView-DmZcdwl-.js +14 -0
- package/dist/assets/GitView-gfHxmc66.js +6 -0
- package/dist/assets/JsonTreeView-DbFqka3P.js +1 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/MarkdownRendererImpl-B07lHhBl.js +12 -0
- package/dist/assets/PatchDiff-DD9pAs47.js +1 -0
- package/dist/assets/PierreDiffViewer-Bo0_BCT7.js +136 -0
- package/dist/assets/SessionSwitcherDropdown-t7WZX24f.js +1 -0
- package/dist/assets/SettingsView-CtsS4oDI.js +3 -0
- package/dist/assets/SettingsWindow-DIRGn3eQ.js +1 -0
- package/dist/assets/TerminalView-BV3KOtdA.css +1 -0
- package/dist/assets/TerminalView-DUwO1CCY.js +46 -0
- package/dist/assets/ToolOutputDialog-iZG2ey4T.js +15 -0
- package/dist/assets/ToolPartDiffPreview-FTexlZSz.js +8 -0
- package/dist/assets/VirtualizedFileDiff-DD5IznIe.js +10 -0
- package/dist/assets/Virtualizer-BxW2CJGB.js +1 -0
- package/dist/assets/abap-BdImnpbu.js +1 -0
- package/dist/assets/abap-Dg923wIy.js +1 -0
- package/dist/assets/actionscript-3-8WTK4YD2.js +1 -0
- package/dist/assets/actionscript-3-CoDkCxhg.js +1 -0
- package/dist/assets/ada-CTi7YQtL.js +1 -0
- package/dist/assets/ada-bCR0ucgS.js +1 -0
- package/dist/assets/andromeeda-C4gqWexZ.js +1 -0
- package/dist/assets/andromeeda-CT0vplkN.js +1 -0
- package/dist/assets/angular-html-CU67Zn6k.js +1 -0
- package/dist/assets/angular-html-D2R1pe45.js +1 -0
- package/dist/assets/angular-ts-BDmYQnhm.js +1 -0
- package/dist/assets/angular-ts-BwZT4LLn.js +1 -0
- package/dist/assets/apache-PcO-DBxQ.js +1 -0
- package/dist/assets/apache-Pmp26Uib.js +1 -0
- package/dist/assets/apex-CnCu5_gb.js +1 -0
- package/dist/assets/apex-D8_7TLub.js +1 -0
- package/dist/assets/apl-B4CMkyY2.js +1 -0
- package/dist/assets/apl-DkfyvC6M.js +1 -0
- package/dist/assets/apl-dKokRX4l.js +1 -0
- package/dist/assets/appThemeRegistry-BK0dHKa6.js +1 -0
- package/dist/assets/applescript-Co6uUVPk.js +1 -0
- package/dist/assets/applescript-Ds_W2jPS.js +1 -0
- package/dist/assets/ara-BRHolxvo.js +1 -0
- package/dist/assets/ara-C1--7J61.js +1 -0
- package/dist/assets/asciiarmor-Df11BRmG.js +1 -0
- package/dist/assets/asciidoc-Ve4PFQV2.js +1 -0
- package/dist/assets/asciidoc-nTfEFGxb.js +1 -0
- package/dist/assets/asm-CTFRnVOM.js +1 -0
- package/dist/assets/asm-D_Q5rh1f.js +1 -0
- package/dist/assets/asn1-EdZsLKOL.js +1 -0
- package/dist/assets/asterisk-B-8jnY81.js +1 -0
- package/dist/assets/astro-CbQHKStN.js +1 -0
- package/dist/assets/astro-DtPAdMss.js +1 -0
- package/dist/assets/aurora-x-C0VKFdlO.js +1 -0
- package/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/dist/assets/awk-CrWgQKhV.js +1 -0
- package/dist/assets/awk-DMzUqQB5.js +1 -0
- package/dist/assets/ayu-dark-DYE7WIF3.js +1 -0
- package/dist/assets/ayu-dark-wZ6ORhBL.js +1 -0
- package/dist/assets/ayu-light-BA47KaF1.js +1 -0
- package/dist/assets/ayu-light-xXLJ_Ch1.js +1 -0
- package/dist/assets/ayu-mirage-32ctXXKs.js +1 -0
- package/dist/assets/ayu-mirage-CQckA_sP.js +1 -0
- package/dist/assets/ballerina-BFfxhgS-.js +1 -0
- package/dist/assets/ballerina-a0yOSyc5.js +1 -0
- package/dist/assets/bat-B0cHNp6s.js +1 -0
- package/dist/assets/bat-BkioyH1T.js +1 -0
- package/dist/assets/beancount-dPJi_gci.js +1 -0
- package/dist/assets/beancount-k_qm7-4y.js +1 -0
- package/dist/assets/berry-CVKoklHx.js +1 -0
- package/dist/assets/berry-uYugtg8r.js +1 -0
- package/dist/assets/bibtex-CHM0blh-.js +1 -0
- package/dist/assets/bibtex-DRcCXNgM.js +1 -0
- package/dist/assets/bicep-Bmn6On1c.js +1 -0
- package/dist/assets/bicep-jWz-hQ8I.js +1 -0
- package/dist/assets/bird2-DPOp833l.js +1 -0
- package/dist/assets/bird2-DaGXIRGu.js +1 -0
- package/dist/assets/blade-D4QpJJKB.js +1 -0
- package/dist/assets/blade-QlWdzBxO.js +1 -0
- package/dist/assets/brainfuck-C4LP7Hcl.js +1 -0
- package/dist/assets/bsl-BO_Y6i37.js +1 -0
- package/dist/assets/bsl-jNujDQK2.js +1 -0
- package/dist/assets/c-BG-tZi7o.js +1 -0
- package/dist/assets/c-BIGW1oBm.js +1 -0
- package/dist/assets/c3-Fcb8_HKm.js +1 -0
- package/dist/assets/c3-eo99z4R2.js +1 -0
- package/dist/assets/cadence-BuaeHeSu.js +1 -0
- package/dist/assets/cadence-Bv_4Rxtq.js +1 -0
- package/dist/assets/cairo-BYiDgOF2.js +1 -0
- package/dist/assets/cairo-KRGpt6FW.js +1 -0
- package/dist/assets/catppuccin-frappe-CfssSZMA.js +1 -0
- package/dist/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- package/dist/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- package/dist/assets/catppuccin-latte-DoBaWTGn.js +1 -0
- package/dist/assets/catppuccin-macchiato-BkAuvdZC.js +1 -0
- package/dist/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- package/dist/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- package/dist/assets/catppuccin-mocha-D9fcC0iT.js +1 -0
- package/dist/assets/clarity-CC_0IFDj.js +1 -0
- package/dist/assets/clarity-D53aC0YG.js +1 -0
- package/dist/assets/clike-B9uivgTg.js +1 -0
- package/dist/assets/clojure-BMjYHr_A.js +1 -0
- package/dist/assets/clojure-KnfDkB_V.js +1 -0
- package/dist/assets/clojure-P80f7IUj.js +1 -0
- package/dist/assets/cmake-BQqOBYOt.js +1 -0
- package/dist/assets/cmake-D1j8_8rp.js +1 -0
- package/dist/assets/cmake-bh2MLnkv.js +1 -0
- package/dist/assets/cobol-CWcv1MsR.js +1 -0
- package/dist/assets/cobol-dIXRVV9Y.js +1 -0
- package/dist/assets/cobol-nwyudZeR.js +1 -0
- package/dist/assets/codeowners-Bp6g37R7.js +1 -0
- package/dist/assets/codeowners-CtwIM3TN.js +1 -0
- package/dist/assets/codeql-D164juXQ.js +1 -0
- package/dist/assets/codeql-DsOJ9woJ.js +1 -0
- package/dist/assets/coffee-Bk-z6oI_.js +1 -0
- package/dist/assets/coffee-Ch7k5sss.js +1 -0
- package/dist/assets/coffeescript-S37ZYGWr.js +1 -0
- package/dist/assets/collapsible-ODWWxk4f.js +1 -0
- package/dist/assets/common-lisp-Cg-RD9OK.js +1 -0
- package/dist/assets/common-lisp-DXc9XcIM.js +1 -0
- package/dist/assets/commonlisp-DBKNyK5s.js +1 -0
- package/dist/assets/computeVirtualFileMetrics-DdXkLFAD.js +3 -0
- package/dist/assets/contextFileOpenGuard-uC4OerzL.js +1 -0
- package/dist/assets/coq-BBL5-mG4.js +1 -0
- package/dist/assets/coq-DkFqJrB1.js +1 -0
- package/dist/assets/cpp-C0lzDePx.js +1 -0
- package/dist/assets/cpp-CofmeUqb.js +1 -0
- package/dist/assets/crystal-Bhade0k2.js +1 -0
- package/dist/assets/crystal-SjHAIU92.js +1 -0
- package/dist/assets/crystal-tKQVLTB8.js +1 -0
- package/dist/assets/csharp-B7nLXyi8.js +1 -0
- package/dist/assets/csharp-COcwbKMJ.js +1 -0
- package/dist/assets/css-BnMrqG3P.js +1 -0
- package/dist/assets/css-DPfMkruS.js +1 -0
- package/dist/assets/css-PZBBrkI0.js +1 -0
- package/dist/assets/csv-D5aBhKLr.js +1 -0
- package/dist/assets/csv-fuZLfV_i.js +1 -0
- package/dist/assets/cue-D82EKSYY.js +1 -0
- package/dist/assets/cue-WCin6O4e.js +1 -0
- package/dist/assets/cypher-COkxafJQ.js +1 -0
- package/dist/assets/cypher-C_CwsFkJ.js +1 -0
- package/dist/assets/cypher-DYQk2G82.js +1 -0
- package/dist/assets/d-85-TOEBH.js +1 -0
- package/dist/assets/d-DVc-yF-l.js +1 -0
- package/dist/assets/d-pRatUO7H.js +1 -0
- package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
- package/dist/assets/dark-plus-DLU5INA3.js +1 -0
- package/dist/assets/dart-CF10PKvl.js +1 -0
- package/dist/assets/dart-YLd5yyRs.js +1 -0
- package/dist/assets/dax-CEL-wOlO.js +1 -0
- package/dist/assets/dax-oldcCaJg.js +1 -0
- package/dist/assets/desktop-BmXAJ9_W.js +1 -0
- package/dist/assets/desktop-DZfyBMgF.js +1 -0
- package/dist/assets/diff-D97Zzqfu.js +1 -0
- package/dist/assets/diff-DbItnlRl.js +1 -0
- package/dist/assets/diff-jW5kmiru.js +1 -0
- package/dist/assets/docker-BcOcwvcX.js +1 -0
- package/dist/assets/docker-DG9neUKd.js +1 -0
- package/dist/assets/dockerfile-BKs6k2Af.js +1 -0
- package/dist/assets/document-attachments-BslE4Ns7.js +30 -0
- package/dist/assets/dotenv-Bea-FkXq.js +1 -0
- package/dist/assets/dotenv-Da5cRb03.js +1 -0
- package/dist/assets/dracula-BzJJZx-M.js +1 -0
- package/dist/assets/dracula-XcYHBZy6.js +1 -0
- package/dist/assets/dracula-soft-BPeX9gxW.js +1 -0
- package/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/dist/assets/dream-maker-BtqSS_iP.js +1 -0
- package/dist/assets/dream-maker-DAHQwpwp.js +1 -0
- package/dist/assets/dtd-DF_7sFjM.js +1 -0
- package/dist/assets/dylan-DwRh75JA.js +1 -0
- package/dist/assets/ebnf-CDyGwa7X.js +1 -0
- package/dist/assets/ecl-Cabwm37j.js +1 -0
- package/dist/assets/edge-BkV0erSs.js +1 -0
- package/dist/assets/edge-zt3dU95k.js +1 -0
- package/dist/assets/eiffel-CnydiIhH.js +1 -0
- package/dist/assets/elixir-CDX3lj18.js +1 -0
- package/dist/assets/elixir-JolcG37e.js +1 -0
- package/dist/assets/elm-BjIDgKzz.js +1 -0
- package/dist/assets/elm-DbKCFpqz.js +1 -0
- package/dist/assets/elm-vLlmbW-K.js +1 -0
- package/dist/assets/emacs-lisp-C9XAeP06.js +1 -0
- package/dist/assets/emacs-lisp-DMBh55R4.js +1 -0
- package/dist/assets/erb-B12qg9BL.js +1 -0
- package/dist/assets/erb-rsjdL4fB.js +1 -0
- package/dist/assets/erlang-BNw1qcRV.js +1 -0
- package/dist/assets/erlang-DNeY46SW.js +1 -0
- package/dist/assets/erlang-DsQrWhSR.js +1 -0
- package/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/dist/assets/everforest-dark-WOub3wle.js +1 -0
- package/dist/assets/everforest-light-C8M2exoo.js +1 -0
- package/dist/assets/everforest-light-DCUg8VH8.js +1 -0
- package/dist/assets/factor-kuTfRLto.js +1 -0
- package/dist/assets/fcl-Kvtd6kyn.js +1 -0
- package/dist/assets/fennel-BYunw83y.js +1 -0
- package/dist/assets/fennel-DBMsxtrh.js +1 -0
- package/dist/assets/fish-BvzEVeQv.js +1 -0
- package/dist/assets/fish-BxskpYPN.js +1 -0
- package/dist/assets/fluent-Bwo2XpEt.js +1 -0
- package/dist/assets/fluent-C4IJs8-o.js +1 -0
- package/dist/assets/forth-Ffai-XNe.js +1 -0
- package/dist/assets/fortran-DYz_wnZ1.js +1 -0
- package/dist/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
- package/dist/assets/fortran-fixed-form-DFFnVrrY.js +1 -0
- package/dist/assets/fortran-free-form-BxgE0vQu.js +1 -0
- package/dist/assets/fortran-free-form-DB5N_DgY.js +1 -0
- package/dist/assets/fsharp-CXgrBDvD.js +1 -0
- package/dist/assets/fsharp-DRAF1jsN.js +1 -0
- package/dist/assets/gas-Bneqetm1.js +1 -0
- package/dist/assets/gdresource-BD-1uzY7.js +1 -0
- package/dist/assets/gdresource-BOOCDP_w.js +1 -0
- package/dist/assets/gdscript-C3mT4Uym.js +1 -0
- package/dist/assets/gdscript-C5YyOfLZ.js +1 -0
- package/dist/assets/gdshader-DkwncUOv.js +1 -0
- package/dist/assets/gdshader-G7X6ysY3.js +1 -0
- package/dist/assets/genie-D0YGMca9.js +1 -0
- package/dist/assets/genie-DSsjfZMX.js +1 -0
- package/dist/assets/gherkin-Cl2Fw-_0.js +1 -0
- package/dist/assets/gherkin-DyxjwDmM.js +1 -0
- package/dist/assets/gherkin-heZmZLOM.js +1 -0
- package/dist/assets/git-commit-CJPsQObV.js +1 -0
- package/dist/assets/git-commit-F4YmCXRG.js +1 -0
- package/dist/assets/git-rebase-DeZqv4fp.js +1 -0
- package/dist/assets/git-rebase-r7XF79zn.js +1 -0
- package/dist/assets/github-dark-BQV5ugrc.js +1 -0
- package/dist/assets/github-dark-DHJKELXO.js +1 -0
- package/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/dist/assets/github-dark-default-DLWDM7FH.js +1 -0
- package/dist/assets/github-dark-dimmed-D1FmiR_p.js +1 -0
- package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/dist/assets/github-dark-high-contrast-BilLoi0o.js +1 -0
- package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/dist/assets/github-light-DAi9KRSo.js +1 -0
- package/dist/assets/github-light-Rcb2q3L5.js +1 -0
- package/dist/assets/github-light-default-CQ75esMw.js +1 -0
- package/dist/assets/github-light-default-D7oLnXFd.js +1 -0
- package/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/dist/assets/github-light-high-contrast-CRF1wyLy.js +1 -0
- package/dist/assets/gleam-BeBqoPwE.js +1 -0
- package/dist/assets/gleam-BspZqrRM.js +1 -0
- package/dist/assets/glimmer-js-BuyKUjfV.js +1 -0
- package/dist/assets/glimmer-js-Rg0-pVw9.js +1 -0
- package/dist/assets/glimmer-ts-D9iniPFN.js +1 -0
- package/dist/assets/glimmer-ts-U6CK756n.js +1 -0
- package/dist/assets/glsl-BOG9l2nV.js +1 -0
- package/dist/assets/glsl-DplSGwfg.js +1 -0
- package/dist/assets/gn-Dxr0sSUf.js +1 -0
- package/dist/assets/gn-n2N0HUVH.js +1 -0
- package/dist/assets/gnuplot-CtW3R_Hz.js +1 -0
- package/dist/assets/gnuplot-DdkO51Og.js +1 -0
- package/dist/assets/go-BBr2UQCn.js +1 -0
- package/dist/assets/go-CxLEBnE3.js +1 -0
- package/dist/assets/graphql-B6XHM4uj.js +1 -0
- package/dist/assets/graphql-ChdNCCLP.js +1 -0
- package/dist/assets/groovy-D9Dt4D0W.js +1 -0
- package/dist/assets/groovy-DFxE31Qs.js +1 -0
- package/dist/assets/groovy-gcz8RCvz.js +1 -0
- package/dist/assets/gruvbox-dark-hard-C37G1gbc.js +1 -0
- package/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- package/dist/assets/gruvbox-dark-medium-C3G9m3TV.js +1 -0
- package/dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- package/dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- package/dist/assets/gruvbox-dark-soft-eZmcmZ9I.js +1 -0
- package/dist/assets/gruvbox-light-hard-BYj-vz_3.js +1 -0
- package/dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- package/dist/assets/gruvbox-light-medium-BdQlDiiF.js +1 -0
- package/dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- package/dist/assets/gruvbox-light-soft-BGNwjiRh.js +1 -0
- package/dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- package/dist/assets/hack-CaT9iCJl.js +1 -0
- package/dist/assets/hack-Cxa-FaZ4.js +1 -0
- package/dist/assets/haml-2e0EI6Is.js +1 -0
- package/dist/assets/haml-B8DHNrY2.js +1 -0
- package/dist/assets/handlebars-BL8al0AC.js +1 -0
- package/dist/assets/handlebars-CojHdNmS.js +1 -0
- package/dist/assets/haskell-BfdhR_WN.js +1 -0
- package/dist/assets/haskell-Cw1EW3IL.js +1 -0
- package/dist/assets/haskell-Df6bDoY_.js +1 -0
- package/dist/assets/haxe-CzTSHFRz.js +1 -0
- package/dist/assets/haxe-DVIybFAS.js +1 -0
- package/dist/assets/haxe-H-WmDvRZ.js +1 -0
- package/dist/assets/hcl-B6OePqme.js +1 -0
- package/dist/assets/hcl-BWvSN4gD.js +1 -0
- package/dist/assets/hjson-D2jn7IRw.js +1 -0
- package/dist/assets/hjson-D5-asLiD.js +1 -0
- package/dist/assets/hlsl-D3lLCCz7.js +1 -0
- package/dist/assets/hlsl-MKeth5V9.js +1 -0
- package/dist/assets/horizon-BUw7H-hv.js +1 -0
- package/dist/assets/horizon-bright-Cn-bp-IR.js +1 -0
- package/dist/assets/horizon-bright-CoQRYDvy.js +1 -0
- package/dist/assets/horizon-cslQss18.js +1 -0
- package/dist/assets/houston-DnULxvSX.js +1 -0
- package/dist/assets/houston-RHIKkNfR.js +1 -0
- package/dist/assets/html-DOf0XnR2.js +1 -0
- package/dist/assets/html-GMplVEZG.js +1 -0
- package/dist/assets/html-derivative-BFtXZ54Q.js +1 -0
- package/dist/assets/html-derivative-DfCZ6Hio.js +1 -0
- package/dist/assets/http-CG3jvXcS.js +1 -0
- package/dist/assets/http-DBlCnlav.js +1 -0
- package/dist/assets/http-jrhK8wxY.js +1 -0
- package/dist/assets/hurl-CxQrh4RI.js +1 -0
- package/dist/assets/hurl-irOxFIW8.js +1 -0
- package/dist/assets/hxml-Bvhsp5Yf.js +1 -0
- package/dist/assets/hxml-CkS3Eie7.js +1 -0
- package/dist/assets/hy-DFXneXwc.js +1 -0
- package/dist/assets/hy-DI6mCUnw.js +1 -0
- package/dist/assets/idl-BEugSyMb.js +1 -0
- package/dist/assets/imba-D-Pk5S0X.js +1 -0
- package/dist/assets/imba-DGztddWO.js +1 -0
- package/dist/assets/index-CRB7i1S2.js +8 -0
- package/dist/assets/index-C_Mo87uS.css +1 -0
- package/dist/assets/index-MSKFS67y.js +1 -0
- package/dist/assets/ini-BEwlwnbL.js +1 -0
- package/dist/assets/ini-Dix3pTmg.js +1 -0
- package/dist/assets/isDiffPlainText-CeCQwHA2.js +1 -0
- package/dist/assets/isFilePlainText-BcfhE9FH.js +1 -0
- package/dist/assets/java-BTDmaRNO.js +1 -0
- package/dist/assets/java-CylS5w8V.js +1 -0
- package/dist/assets/javascript-BO_2ioGC.js +1 -0
- package/dist/assets/javascript-WyGyM1wV.js +1 -0
- package/dist/assets/javascript-wDzz0qaB.js +1 -0
- package/dist/assets/jinja-4LBKfQ-Z.js +1 -0
- package/dist/assets/jinja-D0mZ3q2r.js +1 -0
- package/dist/assets/jison-B9XHrcYJ.js +1 -0
- package/dist/assets/jison-wvAkD_A8.js +1 -0
- package/dist/assets/json-B8JryHcM.js +1 -0
- package/dist/assets/json-Cp-IABpG.js +1 -0
- package/dist/assets/json5-C9tS-k6U.js +1 -0
- package/dist/assets/json5-Cdatmj9n.js +1 -0
- package/dist/assets/jsonc-CvUCa1o5.js +1 -0
- package/dist/assets/jsonc-Des-eS-w.js +1 -0
- package/dist/assets/jsonl-9I_GBd5L.js +1 -0
- package/dist/assets/jsonl-DcaNXYhu.js +1 -0
- package/dist/assets/jsonnet-DFQXde-d.js +1 -0
- package/dist/assets/jsonnet-XoenQ2oc.js +1 -0
- package/dist/assets/jssm-C2t-YnRu.js +1 -0
- package/dist/assets/jssm-CWE2S0Z5.js +1 -0
- package/dist/assets/jsx-8cLv2Six.js +1 -0
- package/dist/assets/jsx-g9-lgVsj.js +1 -0
- package/dist/assets/julia-C1oINnwa.js +1 -0
- package/dist/assets/julia-CxzCAyBv.js +1 -0
- package/dist/assets/julia-DuME0IfC.js +1 -0
- package/dist/assets/just-Bo3fV9RY.js +1 -0
- package/dist/assets/just-Cw27pwNe.js +1 -0
- package/dist/assets/kanagawa-dragon-BOvIHJVT.js +1 -0
- package/dist/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- package/dist/assets/kanagawa-lotus-Bwcd7Bvx.js +1 -0
- package/dist/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- package/dist/assets/kanagawa-wave-0SAlpf0s.js +1 -0
- package/dist/assets/kanagawa-wave-DWedfzmr.js +1 -0
- package/dist/assets/kdl-CMbyIXjQ.js +1 -0
- package/dist/assets/kdl-DV7GczEv.js +1 -0
- package/dist/assets/kimi-for-coding-eVEHug_h.svg +3 -0
- package/dist/assets/kotlin-BQy537ds.js +1 -0
- package/dist/assets/kotlin-BdnUsdx6.js +1 -0
- package/dist/assets/kusto-B_y0uMaZ.js +1 -0
- package/dist/assets/kusto-DZf3V79B.js +1 -0
- package/dist/assets/laserwave-DUszq2jm.js +1 -0
- package/dist/assets/laserwave-si7wuSrt.js +1 -0
- package/dist/assets/latex-CWtU0Tv5.js +1 -0
- package/dist/assets/latex-CZAhoKip.js +1 -0
- package/dist/assets/lean-BZvkOJ9d.js +1 -0
- package/dist/assets/lean-C4RHkN41.js +1 -0
- package/dist/assets/less-B1dDrJ26.js +1 -0
- package/dist/assets/less-XNfgnZC7.js +1 -0
- package/dist/assets/light-plus-B7mTdjB0.js +1 -0
- package/dist/assets/light-plus-g_Fi9vkG.js +1 -0
- package/dist/assets/liquid-DFlqVvG0.js +1 -0
- package/dist/assets/liquid-DYVedYrR.js +1 -0
- package/dist/assets/livescript-BwQOo05w.js +1 -0
- package/dist/assets/llmapi-detailed-Pup_107f.svg +28 -0
- package/dist/assets/llvm-BJRUtCev.js +1 -0
- package/dist/assets/llvm-DjAJT7YJ.js +1 -0
- package/dist/assets/log-2UxHyX5q.js +1 -0
- package/dist/assets/log-CcaF_xNv.js +1 -0
- package/dist/assets/logo-BtOb2qkB.js +1 -0
- package/dist/assets/logo-D9WQLoKQ.js +1 -0
- package/dist/assets/lua-8Av-h-ns.js +1 -0
- package/dist/assets/lua-BaeVxFsk.js +1 -0
- package/dist/assets/lua-BgMRiT3U.js +1 -0
- package/dist/assets/luau-BTSFCcFb.js +1 -0
- package/dist/assets/luau-C-HG3fhB.js +1 -0
- package/dist/assets/main-CPygRDd9.js +134 -0
- package/dist/assets/main-DZg4qzOe.js +2 -0
- package/dist/assets/make-CHLpvVh8.js +1 -0
- package/dist/assets/make-DXVx14cr.js +1 -0
- package/dist/assets/markdown-Cvjx9yec.js +1 -0
- package/dist/assets/markdown-DX-TNHnc.js +1 -0
- package/dist/assets/markdown-shiki.worker-DnvH1S1x.js +12 -0
- package/dist/assets/markdownCore-BULtt6Xa.js +2 -0
- package/dist/assets/marko-CnJfTvn9.js +1 -0
- package/dist/assets/marko-DiIoNBFw.js +1 -0
- package/dist/assets/material-theme-CIJ3cGaW.js +1 -0
- package/dist/assets/material-theme-D5KoaKCx.js +1 -0
- package/dist/assets/material-theme-darker-BWg3m1GB.js +1 -0
- package/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/dist/assets/material-theme-lighter-DR8HWMN7.js +1 -0
- package/dist/assets/material-theme-ocean-CZf9pk28.js +1 -0
- package/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/dist/assets/material-theme-palenight-BF01YeZy.js +1 -0
- package/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/dist/assets/mathematica-DTrFuWx2.js +1 -0
- package/dist/assets/matlab-D7o27uSR.js +1 -0
- package/dist/assets/matlab-DzreyMvm.js +1 -0
- package/dist/assets/mbox-CNhZ1qSd.js +1 -0
- package/dist/assets/mdc-BMNejdWA.js +1 -0
- package/dist/assets/mdc-D_DpLax5.js +1 -0
- package/dist/assets/mdx-C2fqO2XY.js +1 -0
- package/dist/assets/mdx-Cmh6b_Ma.js +1 -0
- package/dist/assets/mermaid-Cwy4Up_p.js +1 -0
- package/dist/assets/mermaid-mWjccvbQ.js +1 -0
- package/dist/assets/min-dark-CafNBF8u.js +1 -0
- package/dist/assets/min-dark-DhfaepVE.js +1 -0
- package/dist/assets/min-light-6xx0uMgm.js +1 -0
- package/dist/assets/min-light-CTRr51gU.js +1 -0
- package/dist/assets/miniChat-BFotDgpO.js +2 -0
- package/dist/assets/mipsasm-CKIfxQSi.js +1 -0
- package/dist/assets/mipsasm-yE_Aqo9y.js +1 -0
- package/dist/assets/mirc-CjQqDB4T.js +1 -0
- package/dist/assets/mllike-BO34x3tv.js +1 -0
- package/dist/assets/mobile-3LLzhN5Q.js +2 -0
- package/dist/assets/modelica-Dc1JOy9r.js +1 -0
- package/dist/assets/mojo-M6pJPbMk.js +1 -0
- package/dist/assets/mojo-rZm6bMo-.js +1 -0
- package/dist/assets/monokai-BodnsaDQ.js +1 -0
- package/dist/assets/monokai-D4h5O-jR.js +1 -0
- package/dist/assets/moonbit-CR8uFLGd.js +1 -0
- package/dist/assets/moonbit-_H4v1dQx.js +1 -0
- package/dist/assets/move-CyYMWVy5.js +1 -0
- package/dist/assets/move-IF9eRakj.js +1 -0
- package/dist/assets/mscgen-BA5vi2Kp.js +1 -0
- package/dist/assets/mumps-BT43cFF4.js +1 -0
- package/dist/assets/narrat-DRg8JJMk.js +1 -0
- package/dist/assets/narrat-DXUZ_ZkW.js +1 -0
- package/dist/assets/nextflow-BPolriEg.js +1 -0
- package/dist/assets/nextflow-Zz6hmt5N.js +1 -0
- package/dist/assets/nextflow-groovy-BeH2EWoN.js +1 -0
- package/dist/assets/nextflow-groovy-pQRwk_Jg.js +1 -0
- package/dist/assets/nginx-BpAMiNFr.js +1 -0
- package/dist/assets/nginx-DSXkacNc.js +1 -0
- package/dist/assets/nginx-DdIZxoE0.js +1 -0
- package/dist/assets/night-owl-C39BiMTA.js +1 -0
- package/dist/assets/night-owl-EdP2Hztp.js +1 -0
- package/dist/assets/night-owl-light-Bk-lYVyk.js +1 -0
- package/dist/assets/night-owl-light-CMTm3GFP.js +1 -0
- package/dist/assets/nim-CVrawwO9.js +1 -0
- package/dist/assets/nim-e2z2kCcq.js +1 -0
- package/dist/assets/nix-Ckjrpg0r.js +1 -0
- package/dist/assets/nix-CwoSXNpI.js +1 -0
- package/dist/assets/nord-BAu0wM6c.js +1 -0
- package/dist/assets/nord-Ddv68eIx.js +1 -0
- package/dist/assets/nsis-LdVXkNf5.js +1 -0
- package/dist/assets/ntriples-BfvgReVJ.js +1 -0
- package/dist/assets/nushell-Cz2AlsmD.js +1 -0
- package/dist/assets/nushell-DtHLcZf1.js +1 -0
- package/dist/assets/objective-c-BoaBTdtz.js +1 -0
- package/dist/assets/objective-c-DXmwc3jG.js +1 -0
- package/dist/assets/objective-cpp-CLxacb5B.js +1 -0
- package/dist/assets/objective-cpp-DstR8hNy.js +1 -0
- package/dist/assets/ocaml-C0hk2d4L.js +1 -0
- package/dist/assets/ocaml-DTbn2Hv7.js +1 -0
- package/dist/assets/octave-CWZ4xMHY.js +1 -0
- package/dist/assets/odin-BBf5iR-q.js +1 -0
- package/dist/assets/odin-Bxc4S6Xn.js +1 -0
- package/dist/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- package/dist/assets/one-dark-pro-utPxbBbJ.js +1 -0
- package/dist/assets/one-light-BuCQ22aO.js +1 -0
- package/dist/assets/one-light-C3Wv6jpd.js +1 -0
- package/dist/assets/openscad-C4EeE6gA.js +1 -0
- package/dist/assets/openscad-CXHpNBsb.js +1 -0
- package/dist/assets/oz-BzwKVEFT.js +1 -0
- package/dist/assets/pascal--L3eBynH.js +1 -0
- package/dist/assets/pascal-D93ZcfNL.js +1 -0
- package/dist/assets/pascal-Sr-XLSE6.js +1 -0
- package/dist/assets/perl-C0TMdlhV.js +1 -0
- package/dist/assets/perl-C95KruZl.js +1 -0
- package/dist/assets/perl-CdXCOZ3F.js +1 -0
- package/dist/assets/permissionStore-BRcQ9Jgt.js +1 -0
- package/dist/assets/php-B4Kqya17.js +1 -0
- package/dist/assets/php-Dhbhpdrm.js +1 -0
- package/dist/assets/pig-CevX1Tat.js +1 -0
- package/dist/assets/pkl-CRusufhw.js +1 -0
- package/dist/assets/pkl-u5AG7uiY.js +1 -0
- package/dist/assets/plastic-3e1v2bzS.js +1 -0
- package/dist/assets/plastic-CXnNNlFZ.js +1 -0
- package/dist/assets/plsql-ChMvpjG-.js +1 -0
- package/dist/assets/plsql-Dbm3tV2N.js +1 -0
- package/dist/assets/po-BTJTHyun.js +1 -0
- package/dist/assets/po-LG0lFwH4.js +1 -0
- package/dist/assets/poimandres-BrOVMvuE.js +1 -0
- package/dist/assets/poimandres-CS3Unz2-.js +1 -0
- package/dist/assets/polar-C0HS_06l.js +1 -0
- package/dist/assets/polar-DjOs3GWb.js +1 -0
- package/dist/assets/postcss-CXtECtnM.js +1 -0
- package/dist/assets/postcss-GRRkWn2F.js +1 -0
- package/dist/assets/powerquery-CEu0bR-o.js +1 -0
- package/dist/assets/powerquery-DHf-dtTS.js +1 -0
- package/dist/assets/powershell-CFHJl5sT.js +1 -0
- package/dist/assets/powershell-DXrKVo3G.js +1 -0
- package/dist/assets/powershell-Dpen1YoG.js +1 -0
- package/dist/assets/prisma-Dd19v3D-.js +1 -0
- package/dist/assets/prisma-Dgf8IN_C.js +1 -0
- package/dist/assets/prolog-9JcfiY6I.js +1 -0
- package/dist/assets/prolog-CbFg5uaA.js +1 -0
- package/dist/assets/properties-C78fOPTZ.js +1 -0
- package/dist/assets/proto-C7zT0LnQ.js +1 -0
- package/dist/assets/proto-dqx_Sj2i.js +1 -0
- package/dist/assets/protobuf-ChK-085T.js +1 -0
- package/dist/assets/pug-CGlum2m_.js +1 -0
- package/dist/assets/pug-CIGw5rlb.js +1 -0
- package/dist/assets/pug-CoOVtr4u.js +1 -0
- package/dist/assets/puppet-BMWR74SV.js +1 -0
- package/dist/assets/puppet-DMA9R1ak.js +1 -0
- package/dist/assets/puppet-Di57VbSS.js +1 -0
- package/dist/assets/purescript-BgpM7rDH.js +1 -0
- package/dist/assets/purescript-CklMAg4u.js +1 -0
- package/dist/assets/python-B6Ihcp-E.js +1 -0
- package/dist/assets/python-B6aJPvgy.js +1 -0
- package/dist/assets/python-Cso5vRIg.js +1 -0
- package/dist/assets/q-pXgVlZs6.js +1 -0
- package/dist/assets/qml-3beO22l8.js +1 -0
- package/dist/assets/qml-DmGQGbca.js +1 -0
- package/dist/assets/qmldir-C8lEn-DE.js +1 -0
- package/dist/assets/qmldir-DXd9s5XC.js +1 -0
- package/dist/assets/qss-B7NnhfIi.js +1 -0
- package/dist/assets/qss-IeuSbFQv.js +1 -0
- package/dist/assets/r-1ts4xG1r.js +1 -0
- package/dist/assets/r-B6wPVr8A.js +1 -0
- package/dist/assets/r-Dspwwk_N.js +1 -0
- package/dist/assets/racket-BqYA7rlc.js +1 -0
- package/dist/assets/racket-CeDx7KO2.js +1 -0
- package/dist/assets/raku-BMJwmrCS.js +1 -0
- package/dist/assets/raku-DXvB9xmW.js +1 -0
- package/dist/assets/razor-BaMsy-R7.js +1 -0
- package/dist/assets/razor-Uh8Bk_45.js +1 -0
- package/dist/assets/red-DR6ZwibK.js +1 -0
- package/dist/assets/red-bN70gL4F.js +1 -0
- package/dist/assets/reg-C-SQnVFl.js +1 -0
- package/dist/assets/reg-Cvyu2Gnx.js +1 -0
- package/dist/assets/regexp-CDVJQ6XC.js +1 -0
- package/dist/assets/regexp-DK-WlHNu.js +1 -0
- package/dist/assets/registerCustomTheme-BBXUCRnG.js +1 -0
- package/dist/assets/rel-BlqNHz8k.js +1 -0
- package/dist/assets/rel-C3B-1QV4.js +1 -0
- package/dist/assets/renderElectronMiniChatApp-DGmmT9Wf.js +1 -0
- package/dist/assets/renderMobileApp-Dd8P9hvM.js +23 -0
- package/dist/assets/riscv-BM1_JUlF.js +1 -0
- package/dist/assets/riscv-C5dpdlr7.js +1 -0
- package/dist/assets/ron-D8l8udqQ.js +1 -0
- package/dist/assets/ron-j5NfrB3z.js +1 -0
- package/dist/assets/rose-pine-CN2F2lJl.js +1 -0
- package/dist/assets/rose-pine-dawn-0S8NEhsA.js +1 -0
- package/dist/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
- package/dist/assets/rose-pine-moon-D4_iv3hh.js +1 -0
- package/dist/assets/rose-pine-moon-DzBDT4rc.js +1 -0
- package/dist/assets/rose-pine-qdsjHGoJ.js +1 -0
- package/dist/assets/rosmsg-BJDFO7_C.js +1 -0
- package/dist/assets/rosmsg-ZkSIzPhc.js +1 -0
- package/dist/assets/rpm-CTu-6PCP.js +1 -0
- package/dist/assets/rst-BrH8l1NY.js +1 -0
- package/dist/assets/rst-C371KPjr.js +1 -0
- package/dist/assets/ruby-B2Rjki9n.js +1 -0
- package/dist/assets/ruby-DusoydGj.js +1 -0
- package/dist/assets/ruby-Dw2BHqvy.js +1 -0
- package/dist/assets/runtimeSurface-Cnqjhb6Y.js +1 -0
- package/dist/assets/rust-B1yitclQ.js +1 -0
- package/dist/assets/rust-CxuBE0J8.js +1 -0
- package/dist/assets/sas-B4kiWyti.js +1 -0
- package/dist/assets/sas-DtSHuIwI.js +1 -0
- package/dist/assets/sas-cz2c8ADy.js +1 -0
- package/dist/assets/sass-Cj5Yp3dK.js +1 -0
- package/dist/assets/sass-bfEVHPJ8.js +1 -0
- package/dist/assets/scala-C151Ov-r.js +1 -0
- package/dist/assets/scala-Dtw19ca_.js +1 -0
- package/dist/assets/scheme-C41bIUwD.js +1 -0
- package/dist/assets/scheme-C98Dy4si.js +1 -0
- package/dist/assets/scheme-OJppFQF3.js +1 -0
- package/dist/assets/scss-DxBkwadj.js +1 -0
- package/dist/assets/scss-OYdSNvt2.js +1 -0
- package/dist/assets/sdbl-CvZs3G9c.js +1 -0
- package/dist/assets/sdbl-DVxCFoDh.js +1 -0
- package/dist/assets/shaderlab-CEJedz68.js +1 -0
- package/dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
- package/dist/assets/shellscript-DgJyDXW6.js +1 -0
- package/dist/assets/shellscript-Yzrsuije.js +1 -0
- package/dist/assets/shellsession-BADoaaVG.js +1 -0
- package/dist/assets/shellsession-DSlzxnCM.js +1 -0
- package/dist/assets/sieve-C3Gn_uJK.js +1 -0
- package/dist/assets/simple-mode-GW_nhZxv.js +1 -0
- package/dist/assets/slack-dark-BthQWCQV.js +1 -0
- package/dist/assets/slack-dark-CWRQmTmo.js +1 -0
- package/dist/assets/slack-ochin-DO_ok7XK.js +1 -0
- package/dist/assets/slack-ochin-DqwNpetd.js +1 -0
- package/dist/assets/smalltalk-B9IHbYt7.js +1 -0
- package/dist/assets/smalltalk-BERRCDM3.js +1 -0
- package/dist/assets/smalltalk-CnHTOXQT.js +1 -0
- package/dist/assets/snazzy-light-B5llO_gK.js +1 -0
- package/dist/assets/snazzy-light-Bw305WKR.js +1 -0
- package/dist/assets/solarized-dark-DXbdFlpD.js +1 -0
- package/dist/assets/solarized-dark-G9yBjKj_.js +1 -0
- package/dist/assets/solarized-light-DeSzljQz.js +1 -0
- package/dist/assets/solarized-light-L9t79GZl.js +1 -0
- package/dist/assets/solidity-B3CjIuAd.js +1 -0
- package/dist/assets/solidity-rGO070M0.js +1 -0
- package/dist/assets/solr-DehyRSwq.js +1 -0
- package/dist/assets/soy-BSb8SaP0.js +1 -0
- package/dist/assets/soy-Brmx7dQM.js +1 -0
- package/dist/assets/sparql-BJh4JEw-.js +1 -0
- package/dist/assets/sparql-DkYu6x3z.js +1 -0
- package/dist/assets/sparql-rVzFXLq3.js +1 -0
- package/dist/assets/splunk-BtCnVYZw.js +1 -0
- package/dist/assets/splunk-h-bNAnbk.js +1 -0
- package/dist/assets/spreadsheet-BCZA_wO0.js +1 -0
- package/dist/assets/sql-BLtJtn59.js +1 -0
- package/dist/assets/sql-BbONfI8K.js +1 -0
- package/dist/assets/sql-D0XecflT.js +1 -0
- package/dist/assets/ssh-config-CywOjoXH.js +1 -0
- package/dist/assets/ssh-config-_ykCGR6B.js +1 -0
- package/dist/assets/stata-BH5u7GGu.js +1 -0
- package/dist/assets/stata-BKjsT8qe.js +1 -0
- package/dist/assets/stex-C3f8Ysf7.js +1 -0
- package/dist/assets/stylus-B533Al4x.js +1 -0
- package/dist/assets/stylus-BEDo0Tqx.js +1 -0
- package/dist/assets/stylus-CIg617SB.js +1 -0
- package/dist/assets/surrealql-2KNEnAt7.js +1 -0
- package/dist/assets/surrealql-Bq5Q-fJD.js +1 -0
- package/dist/assets/svelte-C_ipcX3V.js +1 -0
- package/dist/assets/svelte-xaxjcSSP.js +1 -0
- package/dist/assets/swift-BzpIVaGY.js +1 -0
- package/dist/assets/swift-D82vCrfD.js +1 -0
- package/dist/assets/swift-tEesUtni.js +1 -0
- package/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/dist/assets/synthwave-84-DRVv-_m_.js +1 -0
- package/dist/assets/system-verilog-CnnmHF94.js +1 -0
- package/dist/assets/system-verilog-Qdm_1g2q.js +1 -0
- package/dist/assets/systemd-4A_iFExJ.js +1 -0
- package/dist/assets/systemd-DKMPPZjB.js +1 -0
- package/dist/assets/talonscript-CkByrt1z.js +1 -0
- package/dist/assets/talonscript-eRuyeYHl.js +1 -0
- package/dist/assets/tasl-B9CVvjfG.js +1 -0
- package/dist/assets/tasl-QIJgUcNo.js +1 -0
- package/dist/assets/tcl-Bq9dbVAF.js +1 -0
- package/dist/assets/tcl-DVfN8rqt.js +1 -0
- package/dist/assets/tcl-dwOrl1Do.js +1 -0
- package/dist/assets/templ-BVTOctZ7.js +1 -0
- package/dist/assets/templ-P3uqSqPl.js +1 -0
- package/dist/assets/terraform-BETggiCN.js +1 -0
- package/dist/assets/terraform-CAp711iT.js +1 -0
- package/dist/assets/tex-BHxF5DMe.js +1 -0
- package/dist/assets/tex-idrVyKtj.js +1 -0
- package/dist/assets/textile-CnDTJFAw.js +1 -0
- package/dist/assets/themeResolver-BmJyTmjJ.js +1 -0
- package/dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
- package/dist/assets/tiki-DGYXhP31.js +1 -0
- package/dist/assets/tokyo-night-BvwC8l2v.js +1 -0
- package/dist/assets/tokyo-night-hegEt444.js +1 -0
- package/dist/assets/toml-Bm5Em-hy.js +1 -0
- package/dist/assets/toml-Ms459DBw.js +1 -0
- package/dist/assets/toml-vGWfd6FD.js +1 -0
- package/dist/assets/troff-wAsdV37c.js +1 -0
- package/dist/assets/ts-tags-C-gylFal.js +1 -0
- package/dist/assets/ts-tags-zn1MmPIZ.js +1 -0
- package/dist/assets/tsv-BFtm7NXx.js +1 -0
- package/dist/assets/tsv-B_m7g4N7.js +1 -0
- package/dist/assets/tsx-COt5Ahok.js +1 -0
- package/dist/assets/tsx-D6116Ycy.js +1 -0
- package/dist/assets/ttcn-CfJYG6tj.js +1 -0
- package/dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
- package/dist/assets/turtle-B1tBg_DP.js +1 -0
- package/dist/assets/turtle-BsS91CYL.js +1 -0
- package/dist/assets/turtle-DpRhbQZY.js +1 -0
- package/dist/assets/twig-DNn4PbVi.js +1 -0
- package/dist/assets/twig-DuP5mDZ4.js +1 -0
- package/dist/assets/typescript-BPQ3VLAy.js +1 -0
- package/dist/assets/typescript-BqvgUolh.js +1 -0
- package/dist/assets/typespec-BGHnOYBU.js +1 -0
- package/dist/assets/typespec-DhK7u29_.js +1 -0
- package/dist/assets/typst-DHCkPAjA.js +1 -0
- package/dist/assets/typst-Dfd71gQj.js +1 -0
- package/dist/assets/useAppFontEffects-DXASDavv.js +7043 -0
- package/dist/assets/useInlineCommentController-KJFe98HG.js +2 -0
- package/dist/assets/useOpenInAppsStore-DHAeT9zx.js +1 -0
- package/dist/assets/useWindowTitle-BcwmWC0M.js +10 -0
- package/dist/assets/v-BH76v7br.js +1 -0
- package/dist/assets/v-BcVCzyr7.js +1 -0
- package/dist/assets/vala-CsfeWuGM.js +1 -0
- package/dist/assets/vala-z59IxkFd.js +1 -0
- package/dist/assets/vb-4AxSLKIU.js +1 -0
- package/dist/assets/vb-CmGdzxic.js +1 -0
- package/dist/assets/vb-D17OF-Vu.js +1 -0
- package/dist/assets/vbscript-BuJXcnF6.js +1 -0
- package/dist/assets/velocity-D8B20fx6.js +1 -0
- package/dist/assets/vendor-aparajita-capacitor-secure-storage-5IShChL0.js +1 -0
- package/dist/assets/vendor-aria-hidden-DvXkyWUv.js +1 -0
- package/dist/assets/vendor-base-ui-DwfTg5wD.js +1 -0
- package/dist/assets/vendor-beautiful-mermaid-r4GK_usS.js +119 -0
- package/dist/assets/vendor-capacitor-app-BrDJj9I_.js +1 -0
- package/dist/assets/vendor-capacitor-core-B4EWRrHz.js +1 -0
- package/dist/assets/vendor-capacitor-keyboard-CYccoJjr.js +1 -0
- package/dist/assets/vendor-capacitor-push-notifications-KNe1zSBF.js +1 -0
- package/dist/assets/vendor-capacitor-status-bar-B-N0EOVk.js +1 -0
- package/dist/assets/vendor-ccount-c2V3InAJ.js +1 -0
- package/dist/assets/vendor-character-entities-html4-D7taRCQ7.js +1 -0
- package/dist/assets/vendor-character-entities-legacy-BpoLgcEt.js +1 -0
- package/dist/assets/vendor-class-variance-authority-5VPnzWs2.js +1 -0
- package/dist/assets/vendor-clsx-B-dksMZM.js +1 -0
- package/dist/assets/vendor-cmdk-BCEUzcHE.js +1 -0
- package/dist/assets/vendor-codemirror-autocomplete-3xMyV7a2.js +1 -0
- package/dist/assets/vendor-codemirror-commands-Bzf12ODH.js +1 -0
- package/dist/assets/vendor-codemirror-lang-angular-0K6s6cvb.js +1 -0
- package/dist/assets/vendor-codemirror-lang-cpp-B0aL03z-.js +1 -0
- package/dist/assets/vendor-codemirror-lang-css-C4jEScp7.js +1 -0
- package/dist/assets/vendor-codemirror-lang-go-CgrfVBPr.js +7 -0
- package/dist/assets/vendor-codemirror-lang-html-C1Svvdey.js +1 -0
- package/dist/assets/vendor-codemirror-lang-java-DNPz7_Bx.js +1 -0
- package/dist/assets/vendor-codemirror-lang-javascript-CrbeaEAU.js +13 -0
- package/dist/assets/vendor-codemirror-lang-jinja-DL8BR_RG.js +1 -0
- package/dist/assets/vendor-codemirror-lang-json-BTvAjwkJ.js +1 -0
- package/dist/assets/vendor-codemirror-lang-less-CPABt6NZ.js +1 -0
- package/dist/assets/vendor-codemirror-lang-liquid-BLOLOBMg.js +1 -0
- package/dist/assets/vendor-codemirror-lang-markdown-BloD9GN-.js +1 -0
- package/dist/assets/vendor-codemirror-lang-php-FIZMTuWM.js +1 -0
- package/dist/assets/vendor-codemirror-lang-python-CR2g6ukK.js +3 -0
- package/dist/assets/vendor-codemirror-lang-rust-lsPqn4k4.js +1 -0
- package/dist/assets/vendor-codemirror-lang-sass-CtECMwzH.js +1 -0
- package/dist/assets/vendor-codemirror-lang-sql-BtmZrrZH.js +2 -0
- package/dist/assets/vendor-codemirror-lang-vue-Br6DrntY.js +1 -0
- package/dist/assets/vendor-codemirror-lang-wast-DT7IlCnb.js +1 -0
- package/dist/assets/vendor-codemirror-lang-xml-wNr-LZOf.js +1 -0
- package/dist/assets/vendor-codemirror-lang-yaml-BTKFwfc2.js +1 -0
- package/dist/assets/vendor-codemirror-language-BuDklWi8.js +3 -0
- package/dist/assets/vendor-codemirror-language-data-CFVH0pe8.js +2 -0
- package/dist/assets/vendor-codemirror-search-BzyfPoRb.js +2 -0
- package/dist/assets/vendor-codemirror-state-Wdz6Qsow.js +5 -0
- package/dist/assets/vendor-codemirror-view-pXtfpqbT.js +4 -0
- package/dist/assets/vendor-comma-separated-tokens-D8TqSuIK.js +1 -0
- package/dist/assets/vendor-crelt-C8TCjufn.js +1 -0
- package/dist/assets/vendor-detect-node-es-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-diff-C3JhlQdm.js +13 -0
- package/dist/assets/vendor-dijkstrajs-COg3n3zL.js +1 -0
- package/dist/assets/vendor-dnd-kit-accessibility-Cv52zwWT.js +1 -0
- package/dist/assets/vendor-dnd-kit-core-GipZW2tY.js +5 -0
- package/dist/assets/vendor-dnd-kit-sortable-Ct2QxIHb.js +1 -0
- package/dist/assets/vendor-dnd-kit-utilities-CrOcp0Bp.js +1 -0
- package/dist/assets/vendor-dompurify-r8glNMJW.js +2 -0
- package/dist/assets/vendor-elkjs-CRCLNXjf.js +24 -0
- package/dist/assets/vendor-entities-C3BA0AJP.js +1 -0
- package/dist/assets/vendor-fflate-C7GOKjkv.js +1 -0
- package/dist/assets/vendor-floating-ui-core-CXmKXOXc.js +1 -0
- package/dist/assets/vendor-floating-ui-dom-Bkw82kL9.js +1 -0
- package/dist/assets/vendor-floating-ui-react-dom-sVFnuruh.js +1 -0
- package/dist/assets/vendor-floating-ui-utils-BzXdqYpP.js +1 -0
- package/dist/assets/vendor-framer-motion-DJASDzNt.js +1 -0
- package/dist/assets/vendor-fuse.js-Dw8P0qyA.js +1 -0
- package/dist/assets/vendor-get-nonce-C-Z93AgS.js +1 -0
- package/dist/assets/vendor-ghostty-web-CrxinGGH.js +13 -0
- package/dist/assets/vendor-hast-util-to-html-BKBhbTDb.js +9 -0
- package/dist/assets/vendor-hast-util-whitespace-D4Wp6AEg.js +1 -0
- package/dist/assets/vendor-heic2any-L8hyfc24.js +66 -0
- package/dist/assets/vendor-html-to-image-BeoRn2gJ.js +2 -0
- package/dist/assets/vendor-html-void-elements-CkdplsNl.js +1 -0
- package/dist/assets/vendor-katex-CHD9BdDO.js +257 -0
- package/dist/assets/vendor-katex-Cp579UoJ.css +1 -0
- package/dist/assets/vendor-lezer-common-B3-cUCR_.js +1 -0
- package/dist/assets/vendor-lezer-cpp-BleAU9o-.js +1 -0
- package/dist/assets/vendor-lezer-css-C5xB6jxd.js +1 -0
- package/dist/assets/vendor-lezer-go-CXK1UKk8.js +1 -0
- package/dist/assets/vendor-lezer-highlight-CDMHUZ74.js +1 -0
- package/dist/assets/vendor-lezer-html-C0I1p52t.js +1 -0
- package/dist/assets/vendor-lezer-java-D_zzj-NV.js +1 -0
- package/dist/assets/vendor-lezer-javascript-D7Tj24ot.js +1 -0
- package/dist/assets/vendor-lezer-json-Iaex0UN-.js +1 -0
- package/dist/assets/vendor-lezer-lr-bhwg1HMg.js +1 -0
- package/dist/assets/vendor-lezer-markdown-C6g-d6P3.js +6 -0
- package/dist/assets/vendor-lezer-php-_DHrbsZG.js +1 -0
- package/dist/assets/vendor-lezer-python-CvuFxeZl.js +1 -0
- package/dist/assets/vendor-lezer-rust-Lqgd5SAo.js +1 -0
- package/dist/assets/vendor-lezer-sass-Dnm2lY4t.js +1 -0
- package/dist/assets/vendor-lezer-xml-hUDuAyS5.js +1 -0
- package/dist/assets/vendor-lezer-yaml-BNIl6S_O.js +1 -0
- package/dist/assets/vendor-lru_map-B-cPXHXW.js +1 -0
- package/dist/assets/vendor-marijn-find-cluster-break-DXwl3gUT.js +1 -0
- package/dist/assets/vendor-marked-i_gIkIP3.js +59 -0
- package/dist/assets/vendor-morphdom-B7dS_VPB.js +1 -0
- package/dist/assets/vendor-motion-dom-DJLgZh4Q.js +1 -0
- package/dist/assets/vendor-motion-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-motion-utils-BGh8QBgx.js +1 -0
- package/dist/assets/vendor-oniguruma-parser-C0D4_9rT.js +33 -0
- package/dist/assets/vendor-oniguruma-to-es-DLBnFDCE.js +94 -0
- package/dist/assets/vendor-pierre-theme-Bp7SdUL6.js +1 -0
- package/dist/assets/vendor-pierre-theming-C6Z4M5_u.js +1 -0
- package/dist/assets/vendor-property-information-rJCbAzvX.js +1 -0
- package/dist/assets/vendor-qrcode-COsmt6jn.js +8 -0
- package/dist/assets/vendor-radix-ui-primitive-Dc_FVRD7.js +1 -0
- package/dist/assets/vendor-radix-ui-react-compose-refs-D1GnwnP9.js +1 -0
- package/dist/assets/vendor-radix-ui-react-context-CM4yFQ2I.js +1 -0
- package/dist/assets/vendor-radix-ui-react-dialog-jX3Dld8c.js +5 -0
- package/dist/assets/vendor-radix-ui-react-dismissable-layer-M7LsH3yL.js +1 -0
- package/dist/assets/vendor-radix-ui-react-focus-guards-D5MZ0nQs.js +1 -0
- package/dist/assets/vendor-radix-ui-react-focus-scope-C2K7lNPv.js +1 -0
- package/dist/assets/vendor-radix-ui-react-id-BcxRjnbX.js +1 -0
- package/dist/assets/vendor-radix-ui-react-portal-xlJWBfDg.js +1 -0
- package/dist/assets/vendor-radix-ui-react-presence-Btn-Dp7L.js +1 -0
- package/dist/assets/vendor-radix-ui-react-primitive-CfpW6_IT.js +1 -0
- package/dist/assets/vendor-radix-ui-react-slot-BxUc3Pvz.js +1 -0
- package/dist/assets/vendor-radix-ui-react-use-callback-ref-UKEWHtoP.js +1 -0
- package/dist/assets/vendor-radix-ui-react-use-controllable-state-C_ecoh35.js +1 -0
- package/dist/assets/vendor-radix-ui-react-use-effect-event-DkJl_Rn_.js +1 -0
- package/dist/assets/vendor-radix-ui-react-use-escape-keydown-Co13lHGj.js +1 -0
- package/dist/assets/vendor-radix-ui-react-use-layout-effect-0fyGwS2_.js +1 -0
- package/dist/assets/vendor-react-DYyjh7Q0.js +9 -0
- package/dist/assets/vendor-react-drawio-Boqanm0j.js +1 -0
- package/dist/assets/vendor-react-remove-scroll-CfeMe0DL.js +4 -0
- package/dist/assets/vendor-react-remove-scroll-bar-C9YRs2nU.js +38 -0
- package/dist/assets/vendor-react-style-singleton-B1H4bWSG.js +1 -0
- package/dist/assets/vendor-regex-BBWcyTkg.js +16 -0
- package/dist/assets/vendor-regex-recursion-JU4mF-dD.js +1 -0
- package/dist/assets/vendor-regex-utilities-2BQyy0xI.js +1 -0
- package/dist/assets/vendor-remend-BWYYytN3.js +22 -0
- package/dist/assets/vendor-remixicon-react-CzPq62YO.js +1 -0
- package/dist/assets/vendor-replit-codemirror-vim-BUqe2Slv.js +39 -0
- package/dist/assets/vendor-reselect-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-scheduler-7OC5HNn7.js +1 -0
- package/dist/assets/vendor-shiki-Bbf2GEcE.js +2 -0
- package/dist/assets/vendor-shikijs-core-CGM_jqPK.js +2 -0
- package/dist/assets/vendor-shikijs-engine-javascript-Cme4830A.js +1 -0
- package/dist/assets/vendor-shikijs-engine-oniguruma-DPWuIF1G.js +1 -0
- package/dist/assets/vendor-shikijs-transformers-CZWsLLKm.js +1 -0
- package/dist/assets/vendor-shikijs-types-CPSbSeK6.js +1 -0
- package/dist/assets/vendor-shikijs-vscode-textmate-BgLrS_2D.js +3 -0
- package/dist/assets/vendor-simplewebauthn-browser-VDA66rT_.js +2 -0
- package/dist/assets/vendor-sonner-dWA7qvjr.js +1 -0
- package/dist/assets/vendor-space-separated-tokens-DD3iYX1K.js +1 -0
- package/dist/assets/vendor-stringify-entities-CsH1VEn9.js +1 -0
- package/dist/assets/vendor-style-mod-NZ5MEPvj.js +3 -0
- package/dist/assets/vendor-tailwind-merge-CPcsbTWB.js +1 -0
- package/dist/assets/vendor-tanstack-react-virtual-IrbCBGJ4.js +1 -0
- package/dist/assets/vendor-tanstack-virtual-core-Dubg10g5.js +1 -0
- package/dist/assets/vendor-tslib-De9GV7Vy.js +1 -0
- package/dist/assets/vendor-use-callback-ref-CJB0R85I.js +1 -0
- package/dist/assets/vendor-use-sidecar-C-AuTczP.js +1 -0
- package/dist/assets/vendor-use-sync-external-store-B7OScDIw.js +1 -0
- package/dist/assets/vendor-vite-runtime-DD1OZmLC.js +1 -0
- package/dist/assets/vendor-w3c-keyname-Vcq4gwWv.js +1 -0
- package/dist/assets/vendor-workbox-window-BIl4cyR9.js +2 -0
- package/dist/assets/vendor-zumer-snapdom-Btyu_Xi2.js +8 -0
- package/dist/assets/vendor-zustand-CnsBKjP5.js +6 -0
- package/dist/assets/vendor-zwitch-C2o2j-tx.js +1 -0
- package/dist/assets/verilog-BQ8w6xss.js +1 -0
- package/dist/assets/verilog-C6RDOZhf.js +1 -0
- package/dist/assets/verilog-LfQUkHDc.js +1 -0
- package/dist/assets/vesper--t9w6MWz.js +1 -0
- package/dist/assets/vesper-DU1UobuO.js +1 -0
- package/dist/assets/vhdl-CeAyd5Ju.js +1 -0
- package/dist/assets/vhdl-DJCsFYgn.js +1 -0
- package/dist/assets/vhdl-lSbBsy5d.js +1 -0
- package/dist/assets/viml-CJc9bBzg.js +1 -0
- package/dist/assets/viml-pbKHW3Ia.js +1 -0
- package/dist/assets/vitesse-black-B_BYPdvp.js +1 -0
- package/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/dist/assets/vitesse-dark-DOSOf2Sz.js +1 -0
- package/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/dist/assets/vitesse-light-gUX51GpB.js +1 -0
- package/dist/assets/vue-D2k0BAl5.js +1 -0
- package/dist/assets/vue-DN_0RTcg.js +1 -0
- package/dist/assets/vue-html-AaS7Mt5G.js +1 -0
- package/dist/assets/vue-html-zEfNEgpj.js +1 -0
- package/dist/assets/vue-vine-CQOfvN7w.js +1 -0
- package/dist/assets/vue-vine-D9eCaTqi.js +1 -0
- package/dist/assets/vyper-CDx5xZoG.js +1 -0
- package/dist/assets/vyper-DpazWNpx.js +1 -0
- package/dist/assets/wasm-CFi-k9--.js +1 -0
- package/dist/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/assets/wasm-MzD3tlZU.js +1 -0
- package/dist/assets/web-components-CNPQSzpa.js +1390 -0
- package/dist/assets/webidl-ZXfAyPTL.js +1 -0
- package/dist/assets/wenyan-BV7otONQ.js +1 -0
- package/dist/assets/wenyan-CYFFG9DK.js +1 -0
- package/dist/assets/wgsl-CxjTZs-b.js +1 -0
- package/dist/assets/wgsl-Dx-B1_4e.js +1 -0
- package/dist/assets/wikitext-BhOHFoWU.js +1 -0
- package/dist/assets/wikitext-Bx2qct8O.js +1 -0
- package/dist/assets/wit-5i3qLPDT.js +1 -0
- package/dist/assets/wit-DDW3hD69.js +1 -0
- package/dist/assets/wolfram-lXgVvXCa.js +1 -0
- package/dist/assets/wolfram-pu21K997.js +1 -0
- package/dist/assets/worker-BBk7yuNN.js +153 -0
- package/dist/assets/workerFactory-tw7cIdmV.js +1 -0
- package/dist/assets/xml-BkAJuBCc.js +1 -0
- package/dist/assets/xml-sdJ4AIDG.js +1 -0
- package/dist/assets/xquery-DzFWVndE.js +1 -0
- package/dist/assets/xsl-C6UUWSil.js +1 -0
- package/dist/assets/xsl-CtQFsRM5.js +1 -0
- package/dist/assets/yacas-BJ4BC0dw.js +1 -0
- package/dist/assets/yaml-Bopit2L3.js +1 -0
- package/dist/assets/yaml-Buea-lGh.js +1 -0
- package/dist/assets/z80-Hz9HOZM7.js +1 -0
- package/dist/assets/zenscript-BIAKq-aR.js +1 -0
- package/dist/assets/zenscript-DVFEvuxE.js +1 -0
- package/dist/assets/zig-VOosw3JB.js +1 -0
- package/dist/assets/zig-bSFrHQ7n.js +1 -0
- package/dist/favicon-16.png +0 -0
- package/dist/favicon-32.png +0 -0
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +13 -0
- package/dist/index.html +658 -0
- package/dist/logo-dark-192x192.png +0 -0
- package/dist/logo-dark-512x512.svg +13 -0
- package/dist/logo-light-192x192.png +0 -0
- package/dist/logo-light-512x512.svg +13 -0
- package/dist/mini-chat.html +121 -0
- package/dist/mobile.html +79 -0
- package/dist/pwa-192.png +0 -0
- package/dist/pwa-512.png +0 -0
- package/dist/pwa-maskable-192.png +0 -0
- package/dist/pwa-maskable-512.png +0 -0
- package/dist/site.webmanifest +21 -0
- package/dist/sw.js +1 -0
- package/package.json +115 -0
- package/public/apple-touch-icon-120x120.png +0 -0
- package/public/apple-touch-icon-152x152.png +0 -0
- package/public/apple-touch-icon-167x167.png +0 -0
- package/public/apple-touch-icon-180x180.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/apple-touch-icon.svg +13 -0
- package/public/favicon-16.png +0 -0
- package/public/favicon-32.png +0 -0
- package/public/favicon.png +0 -0
- package/public/favicon.svg +13 -0
- package/public/logo-dark-192x192.png +0 -0
- package/public/logo-dark-512x512.svg +13 -0
- package/public/logo-light-192x192.png +0 -0
- package/public/logo-light-512x512.svg +13 -0
- package/public/pwa-192.png +0 -0
- package/public/pwa-512.png +0 -0
- package/public/pwa-maskable-192.png +0 -0
- package/public/pwa-maskable-512.png +0 -0
- package/public/site.webmanifest +21 -0
- package/server/index.d.ts +35 -0
- package/server/index.js +206 -0
- package/server/lib/client-auth/pairing.js +308 -0
- package/server/lib/client-auth/pairing.test.js +142 -0
- package/server/lib/client-auth/remote-clients.js +280 -0
- package/server/lib/client-auth/remote-clients.test.js +158 -0
- package/server/lib/cloudflare-tunnel.js +606 -0
- package/server/lib/fs/DOCUMENTATION.md +39 -0
- package/server/lib/fs/pick-directory.js +236 -0
- package/server/lib/fs/pick-directory.test.js +134 -0
- package/server/lib/fs/routes.js +1543 -0
- package/server/lib/fs/routes.test.js +920 -0
- package/server/lib/fs/search.js +238 -0
- package/server/lib/git/DOCUMENTATION.md +180 -0
- package/server/lib/git/credentials.js +41 -0
- package/server/lib/git/identity-storage.js +113 -0
- package/server/lib/git/index.js +6 -0
- package/server/lib/git/issue-2746-longpaths.test.js +206 -0
- package/server/lib/git/routes.js +1089 -0
- package/server/lib/git/routes.test.js +210 -0
- package/server/lib/git/service.js +4927 -0
- package/server/lib/git/service.test.js +1216 -0
- package/server/lib/inherited-env.js +74 -0
- package/server/lib/inherited-env.test.js +65 -0
- package/server/lib/ngrok-tunnel.js +343 -0
- package/server/lib/ngrok-tunnel.test.js +50 -0
- package/server/lib/package-manager.js +850 -0
- package/server/lib/package-manager.test.js +370 -0
- package/server/lib/path-realpath-cache.js +85 -0
- package/server/lib/path-realpath-cache.test.js +75 -0
- package/server/lib/pi/archive-store.js +52 -0
- package/server/lib/pi/archive-store.test.js +32 -0
- package/server/lib/pi/attachment-store.js +98 -0
- package/server/lib/pi/attachment-store.test.js +40 -0
- package/server/lib/pi/custom-themes.js +33 -0
- package/server/lib/pi/custom-themes.test.js +21 -0
- package/server/lib/pi/legacy-ui-model-defaults.js +57 -0
- package/server/lib/pi/legacy-ui-model-defaults.test.js +65 -0
- package/server/lib/pi/model-config-store.js +173 -0
- package/server/lib/pi/model-config-store.test.js +42 -0
- package/server/lib/pi/routes.js +956 -0
- package/server/lib/pi/routes.test.js +733 -0
- package/server/lib/pi/session-daemon/DOCUMENTATION.md +41 -0
- package/server/lib/pi/session-daemon/daemon-process.js +113 -0
- package/server/lib/pi/session-daemon/ipc-client.js +144 -0
- package/server/lib/pi/session-daemon/runtime-registry.js +149 -0
- package/server/lib/pi/session-daemon/runtime-registry.test.js +109 -0
- package/server/lib/pi/session-daemon/session-daemon.js +1903 -0
- package/server/lib/pi/session-daemon/session-daemon.test.js +1306 -0
- package/server/lib/pi/session-daemon/session-jsonl.js +94 -0
- package/server/lib/pi/session-daemon/session-jsonl.test.js +73 -0
- package/server/lib/pi/session-daemon/supervisor.js +458 -0
- package/server/lib/pi/session-daemon/supervisor.test.js +212 -0
- package/server/lib/pi/session-folders-store.js +74 -0
- package/server/lib/pi/session-folders-store.test.js +45 -0
- package/server/lib/pi/session-lifecycle.integration.test.js +197 -0
- package/server/lib/pi/settings-store.js +133 -0
- package/server/lib/pi/settings-store.test.js +113 -0
- package/server/lib/pi/thinking-levels.js +39 -0
- package/server/lib/pi/thinking-levels.test.js +34 -0
- package/server/lib/pi/ui-settings-store.js +68 -0
- package/server/lib/pi/ui-settings-store.test.js +40 -0
- package/server/lib/pichamber-data-dir.js +78 -0
- package/server/lib/pichamber-data-dir.test.js +105 -0
- package/server/lib/pichamber-data-root-authority.test.js +81 -0
- package/server/lib/preview/proxy-runtime.js +1604 -0
- package/server/lib/preview/proxy-runtime.test.js +528 -0
- package/server/lib/pwa-keys-contract.test.ts +62 -0
- package/server/lib/relay/DOCUMENTATION.md +106 -0
- package/server/lib/relay/cross-compat.test.js +135 -0
- package/server/lib/relay/e2ee.js +341 -0
- package/server/lib/relay/e2ee.test.js +124 -0
- package/server/lib/relay/host-client.js +389 -0
- package/server/lib/relay/host-client.test.js +292 -0
- package/server/lib/relay/identity.js +93 -0
- package/server/lib/relay/identity.test.js +78 -0
- package/server/lib/relay/service.js +9 -0
- package/server/lib/relay/signing-key.js +73 -0
- package/server/lib/relay/tunnel-codec.js +373 -0
- package/server/lib/relay/tunnel-codec.test.js +58 -0
- package/server/lib/relay/tunnel-host.js +465 -0
- package/server/lib/security/bind-host.js +40 -0
- package/server/lib/security/bind-host.test.js +36 -0
- package/server/lib/security/packaged-client-origins.js +11 -0
- package/server/lib/security/request-security.js +122 -0
- package/server/lib/security/request-security.test.js +57 -0
- package/server/lib/server/cli-entry-runtime.js +44 -0
- package/server/lib/server/cli-options.js +132 -0
- package/server/lib/server/core-routes.js +1041 -0
- package/server/lib/server/cors.js +51 -0
- package/server/lib/server/cors.test.js +37 -0
- package/server/lib/server/lan-addresses.js +41 -0
- package/server/lib/server/lan-addresses.test.js +42 -0
- package/server/lib/server/path-utils.js +27 -0
- package/server/lib/server/tunnel-auth.js +586 -0
- package/server/lib/terminal/DOCUMENTATION.md +48 -0
- package/server/lib/terminal/history.js +83 -0
- package/server/lib/terminal/history.test.js +19 -0
- package/server/lib/terminal/runtime.js +391 -0
- package/server/lib/terminal/runtime.test.js +566 -0
- package/server/lib/terminal/shells.js +100 -0
- package/server/lib/terminal/shells.test.js +73 -0
- package/server/lib/terminal/terminal-ws-protocol.js +68 -0
- package/server/lib/terminal/terminal-ws-protocol.test.js +145 -0
- package/server/lib/terminal/theme-response.js +98 -0
- package/server/lib/terminal/theme-response.test.js +70 -0
- package/server/lib/tunnels/DOCUMENTATION.md +33 -0
- package/server/lib/tunnels/executable-search.js +140 -0
- package/server/lib/tunnels/executable-search.test.js +96 -0
- package/server/lib/tunnels/install-help.js +56 -0
- package/server/lib/tunnels/install-help.test.js +31 -0
- package/server/lib/tunnels/providers/cloudflare.js +54 -0
- package/server/lib/tunnels/providers/ngrok.js +32 -0
- package/server/lib/tunnels/types.js +16 -0
- package/server/lib/ui-auth/DOCUMENTATION.md +44 -0
- package/server/lib/ui-auth/ui-auth.js +972 -0
- package/server/lib/ui-auth/ui-auth.test.js +250 -0
- package/server/lib/ui-auth/ui-passkeys.js +543 -0
- package/server/lib/workspace/host.js +91 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Relay Module Documentation
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
The private relay lets an PiChamber client (mobile app, browser, or another desktop) reach a user's PiChamber instance through PiChamber-hosted infrastructure when the instance is not directly reachable (behind NAT, no public URL, no tunnel). The instance dials **outbound** to the relay; nothing needs to be exposed inbound.
|
|
6
|
+
|
|
7
|
+
Traffic is **end-to-end encrypted between the two endpoints** (client and host instance). The relay infrastructure forwards opaque ciphertext and cannot read application traffic — it is an untrusted transport, not a trusted middlebox.
|
|
8
|
+
|
|
9
|
+
This module (`packages/web/server/lib/relay/`) is the **host side**: it runs inside the PiChamber web server (so it works for Electron desktop, headless server, and CLI installs alike). The **client side** lives in `packages/ui/src/lib/relay/`. The **relay service itself** is a separate Cloudflare Worker in the `pichamber-website` repo and only brokers connections.
|
|
10
|
+
|
|
11
|
+
> **Wiring status:** the relay host runtime is not currently started by any
|
|
12
|
+
> runtime entrypoint. The host entrypoint (`createRelayService` — settings
|
|
13
|
+
> persistence, the `/api/pichamber/relay/*` management routes, and lifecycle
|
|
14
|
+
> wiring) and `host-lock.js` (the per-machine host claim) were removed as dead
|
|
15
|
+
> code. The remaining host files below are kept as the implementation and are
|
|
16
|
+
> exercised by their own tests and the TS↔JS cross-compat test; restoring relay
|
|
17
|
+
> host mode means wiring an entrypoint that starts `host-client.js` with the
|
|
18
|
+
> identity and local port from `identity.js`.
|
|
19
|
+
|
|
20
|
+
## The three layers
|
|
21
|
+
|
|
22
|
+
Traffic is modeled as three stacked layers. The relay understands only Layer 1; Layers 2–3 exist solely between the client and the host.
|
|
23
|
+
|
|
24
|
+
1. **Relay routing (Layer 1)** — outbound WebSocket connections to the relay, connection brokering, and host authentication to the relay. The relay routes each client to the correct host and forwards frames verbatim.
|
|
25
|
+
2. **End-to-end encryption (Layer 2)** — an authenticated encrypted channel established directly between client and host, keyed so the relay cannot participate. Built on standard WebCrypto primitives (ECDH key agreement + AEAD framing). The host's encryption public key is distributed to the client out-of-band via the pairing payload and is the client's trust anchor.
|
|
26
|
+
3. **Tunnel multiplexing (Layer 3)** — because an PiChamber client speaks many concurrent HTTP requests, an event stream (SSE), and WebSockets to one origin, the encrypted channel carries a small multiplexing protocol. It frames HTTP request/response (including streamed bodies) and WebSocket sub-streams so the whole app works over one encrypted connection.
|
|
27
|
+
|
|
28
|
+
## Entrypoints and structure
|
|
29
|
+
|
|
30
|
+
Host side (`packages/web/server/lib/relay/`):
|
|
31
|
+
- `service.js` — relay endpoint configuration only: exports `DEFAULT_RELAY_URL`. The host entrypoint (`createRelayService`: settings persistence, the `GET/POST /api/pichamber/relay/{status,enable,disable}` management routes, the `getPairingCandidate()` accessor, and host lifecycle wiring) was removed as dead code — no runtime wired it in. The CLI connect-url command resolves the relay endpoint (`DEFAULT_RELAY_URL`, the `PICHAMBER_RELAY_URL` env override, stored `settings.privateRelay.relayUrl`) the same way a host would.
|
|
32
|
+
- `identity.js` — the host's stable identity: the long-lived signing keypair (shared with the push relay, defines the routing id) plus a long-lived encryption keypair (the E2EE trust anchor). Reused across restarts; never rotated implicitly.
|
|
33
|
+
- `signing-key.js` — storage/derivation of the signing keypair and the routing id, shared with the notifications runtime.
|
|
34
|
+
- `host-client.js` — the long-lived connection manager: one outbound control connection to the relay, a per-client data connection for each connected device, reconnect/backoff, and the E2EE responder handshake per connection.
|
|
35
|
+
- `tunnel-host.js` — the per-connection dispatcher: decrypts tunnel frames and forwards HTTP/SSE/WS to the local server over loopback, then streams responses back. Enforces a path allowlist and never injects credentials.
|
|
36
|
+
- `e2ee.js`, `tunnel-codec.js` — host-side (JS) mirrors of the shared crypto and framing (see "Two implementations" below).
|
|
37
|
+
|
|
38
|
+
Client side (`packages/ui/src/lib/relay/`):
|
|
39
|
+
- `protocol.ts` — the shared contract: constants, frame types, message shapes. The normative source both implementations follow.
|
|
40
|
+
- `crypto.ts`, `handshake.ts` — the E2EE primitives and handshake state machines (initiator + responder).
|
|
41
|
+
- `tunnel-codec.ts` — Layer 3 frame codec, fragmentation, and outbound frame batching.
|
|
42
|
+
- `tunnel-client.ts` — the client tunnel: exposes a `fetch()`-compatible and a WebSocket-compatible surface backed by the encrypted tunnel.
|
|
43
|
+
- `tunnel-payloads.ts`, `runtime-tunnel.ts`, `runtime-socket.ts` — payload helpers, the active-tunnel singleton, and the shared "open a runtime WebSocket the right way" helper.
|
|
44
|
+
|
|
45
|
+
Relay is not a separate link format: it is one transport candidate inside the unified **pairing v2** payload (`packages/ui/src/lib/connectionPayload.ts`). A relay candidate is `{ type: 'relay', relayUrl, serverId, hostEncPubJwk }` — no embedded token; the client redeems the one-time pairing secret over the tunnel like any other candidate.
|
|
46
|
+
|
|
47
|
+
## What travels the tunnel
|
|
48
|
+
|
|
49
|
+
Everything a client normally sends to the single PiChamber origin:
|
|
50
|
+
- **HTTP** — authenticated PiChamber API endpoints under `/api/*`, plus `/auth/*` and `/health`.
|
|
51
|
+
- **SSE** — long-lived streamed responses (the event stream and notifications). These are just HTTP responses whose body streams; the tunnel needs no special SSE handling.
|
|
52
|
+
- **WebSocket** — the endpoints that use a real socket (the global event stream on platforms that support WS, terminal I/O, dictation).
|
|
53
|
+
|
|
54
|
+
The host dispatcher restricts tunneled traffic to explicit path allowlists (one for HTTP, one for WS).
|
|
55
|
+
|
|
56
|
+
## Authentication model
|
|
57
|
+
|
|
58
|
+
- The tunnel is **transport only**. The PiChamber server still authenticates every tunneled request exactly as it authenticates a direct remote client. The relay path grants reachability, not authorization.
|
|
59
|
+
- Clients carry their normal credential. HTTP and SSE requests authenticate with the client's bearer token (a header). **WebSocket upgrades cannot send headers**, so they authenticate with a short-lived URL-scoped token minted beforehand and passed as a query parameter. This asymmetry is important when adding new WebSocket features (see the skill).
|
|
60
|
+
- The host authenticates itself to the relay with a signed handshake using its long-lived signing key.
|
|
61
|
+
- Enabling the relay is explicit opt-in and disabled by default; disabling it severs all relay reachability immediately.
|
|
62
|
+
|
|
63
|
+
## End-to-end flow (overview)
|
|
64
|
+
|
|
65
|
+
1. **Pairing.** The host issues a pairing-v2 link (QR / deep link) carrying a one-time secret and a list of transport candidates. When the relay is enabled, one candidate is the relay transport (its endpoint, routing id, and encryption public key — the E2EE trust anchor). The client redeems the secret over the first reachable candidate; over the relay candidate it opens the E2EE tunnel first, then redeems through it, and stores the connection.
|
|
66
|
+
2. **Presence.** When the relay is enabled, the host opens one outbound control connection and waits.
|
|
67
|
+
3. **Connect.** The client connects for a given routing id; the relay notifies the host over the control connection; the host opens a matching per-client data connection.
|
|
68
|
+
4. **Handshake.** Over that connection pair, client and host run the E2EE handshake and derive a shared encrypted channel the relay cannot read.
|
|
69
|
+
5. **Traffic.** All normal app traffic is multiplexed and encrypted through that channel. On the host, decrypted requests are dispatched to the local server over loopback with the actual loopback origin, so normal origin checks still apply without trusting client-supplied origin metadata; responses stream back encrypted. Reconnects re-establish a fresh channel and the app's existing retry machinery recovers.
|
|
70
|
+
|
|
71
|
+
## Candidate refresh (staying off the relay when direct works)
|
|
72
|
+
|
|
73
|
+
Pairing-payload transport candidates are a snapshot: when DHCP hands the host
|
|
74
|
+
machine a new LAN address, a device's saved direct candidate goes stale and the
|
|
75
|
+
device silently degrades to relay-only. To recover, an already-paired client can
|
|
76
|
+
call `GET /api/client-auth/connection/candidates` (UI session or client bearer;
|
|
77
|
+
registered with the auth/access routes) over any live transport — including
|
|
78
|
+
through the tunnel — to learn the server's **current** LAN URLs plus the relay
|
|
79
|
+
candidate, and update its saved candidate set (mobile: `mobileConnections.ts`;
|
|
80
|
+
desktop: `desktopRelayRestore.ts`).
|
|
81
|
+
|
|
82
|
+
Identity gating: the response carries the stable `serverId` (base64url SHA-256 of
|
|
83
|
+
the public signing JWK — the same identity the relay routes by, exposed by the
|
|
84
|
+
relay service's `getServerId()` and echoed unauthenticated on `/health` and
|
|
85
|
+
`/api/version`). Clients ignore a refresh whose `serverId` does not match their
|
|
86
|
+
pinned relay identity, and verify `/health`'s `serverId` on a learned address
|
|
87
|
+
**before** sending their bearer token to it — a re-assigned LAN address may now
|
|
88
|
+
belong to a different machine.
|
|
89
|
+
|
|
90
|
+
## Two implementations, kept in sync
|
|
91
|
+
|
|
92
|
+
The E2EE and framing logic exists twice: TypeScript in `packages/ui/src/lib/relay/` (shared by the client and the normative reference) and a JavaScript mirror in this module (the host, which is plain JS ESM). They **must stay byte-compatible** — a client encrypted by one must decrypt on the other. A cross-compatibility test (`cross-compat.test.js`) imports the TS modules directly and exercises a full TS-client ↔ JS-host exchange. Any change to the wire format, frame codec, handshake, or batching must update both sides and keep that test green.
|
|
93
|
+
|
|
94
|
+
## Runtime integration (client)
|
|
95
|
+
|
|
96
|
+
Relay mode plugs into the existing client transport layer rather than a parallel path: `runtime-switch` activates the tunnel singleton, `runtime-fetch` routes runtime requests through it, `runtime-url`/`runtime-socket` yield tunnel-backed URLs and sockets, and `runtime-auth` mints the URL-scoped token through the tunnel. Direct-URL connections are unaffected.
|
|
97
|
+
|
|
98
|
+
## Design invariants (do not regress)
|
|
99
|
+
|
|
100
|
+
- The relay never sees plaintext application traffic; it sees only routing metadata (routing id, connection identifiers, timestamps, coarse counts).
|
|
101
|
+
- Pairing secrets travel in URL fragments only, never in query strings, never logged.
|
|
102
|
+
- The host dispatcher never injects credentials; the server authenticates each tunneled request.
|
|
103
|
+
- The tunnel is transparent to the app: adding relay support to a feature should not require the feature to know the relay exists — it goes through the shared runtime transport helpers.
|
|
104
|
+
- The two implementations stay byte-compatible and the wire format is versioned/negotiated so mixed client/host app versions degrade gracefully rather than break.
|
|
105
|
+
|
|
106
|
+
For the operational rules that keep future changes (new WebSocket endpoints, transport refactors, terminal/voice porting) from breaking this, load the `relay-transport` skill.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Cross-compatibility: the JS host e2ee must interoperate with the normative TS
|
|
2
|
+
// modules in packages/ui/src/lib/relay. bun runs TS directly, so import the TS
|
|
3
|
+
// client handshake and drive a full TS-client <-> JS-host exchange both ways.
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from 'bun:test';
|
|
6
|
+
|
|
7
|
+
import { createHostHandshake, exportPublicKeyJwk, generateEcdhKeyPair } from './e2ee.js';
|
|
8
|
+
import { createClientHandshake } from '../../../../ui/src/lib/relay/handshake.ts';
|
|
9
|
+
import {
|
|
10
|
+
TunnelFrameType as JsFrameType,
|
|
11
|
+
decodeFrameBatch as jsDecodeBatch,
|
|
12
|
+
decodeTunnelFrame as jsDecode,
|
|
13
|
+
encodeFrameBatch as jsEncodeBatch,
|
|
14
|
+
encodeTunnelFrame as jsEncode,
|
|
15
|
+
} from './tunnel-codec.js';
|
|
16
|
+
import {
|
|
17
|
+
decodeFrameBatch as tsDecodeBatch,
|
|
18
|
+
decodeTunnelFrame as tsDecode,
|
|
19
|
+
encodeFrameBatch as tsEncodeBatch,
|
|
20
|
+
encodeTunnelFrame as tsEncode,
|
|
21
|
+
} from '../../../../ui/src/lib/relay/tunnel-codec.ts';
|
|
22
|
+
import { TunnelFrameType as TsFrameType } from '../../../../ui/src/lib/relay/protocol.ts';
|
|
23
|
+
|
|
24
|
+
describe('relay JS-host <-> TS-client cross compatibility', () => {
|
|
25
|
+
it('completes a handshake and exchanges frames both ways', async () => {
|
|
26
|
+
const hostKeys = await generateEcdhKeyPair();
|
|
27
|
+
const hostPubJwk = await exportPublicKeyJwk(hostKeys.publicKey);
|
|
28
|
+
|
|
29
|
+
const jsHost = createHostHandshake(hostKeys.privateKey);
|
|
30
|
+
const tsClient = await createClientHandshake(hostPubJwk);
|
|
31
|
+
|
|
32
|
+
// TS client hello -> JS host establishes and replies ready.
|
|
33
|
+
const hostAction = await jsHost.handleText(tsClient.helloText);
|
|
34
|
+
expect(hostAction.type).toBe('established');
|
|
35
|
+
const hostChannel = hostAction.channel;
|
|
36
|
+
|
|
37
|
+
// JS host ready -> TS client establishes.
|
|
38
|
+
const clientAction = await tsClient.handleText(hostAction.replyText);
|
|
39
|
+
expect(clientAction.type).toBe('established');
|
|
40
|
+
const clientChannel = clientAction.channel;
|
|
41
|
+
|
|
42
|
+
// TS client -> JS host.
|
|
43
|
+
const up = new TextEncoder().encode('ts client speaking');
|
|
44
|
+
const upPlain = await hostChannel.decryptor.decrypt(await clientChannel.encryptor.encrypt(up));
|
|
45
|
+
expect(new TextDecoder().decode(upPlain)).toBe('ts client speaking');
|
|
46
|
+
|
|
47
|
+
// JS host -> TS client.
|
|
48
|
+
const down = new TextEncoder().encode('js host replying');
|
|
49
|
+
const downPlain = await clientChannel.decryptor.decrypt(await hostChannel.encryptor.encrypt(down));
|
|
50
|
+
expect(new TextDecoder().decode(downPlain)).toBe('js host replying');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('tunnel frames are byte-compatible across TS and JS codecs', () => {
|
|
54
|
+
const payload = new TextEncoder().encode('{"method":"GET"}');
|
|
55
|
+
const tsFrame = tsEncode(TsFrameType.HttpRequest, 5, payload);
|
|
56
|
+
const jsFrame = jsEncode(JsFrameType.HttpRequest, 5, payload);
|
|
57
|
+
expect(Array.from(jsFrame)).toEqual(Array.from(tsFrame));
|
|
58
|
+
|
|
59
|
+
const decodedByJs = jsDecode(tsFrame);
|
|
60
|
+
const decodedByTs = tsDecode(jsFrame);
|
|
61
|
+
expect(decodedByJs.streamId).toBe(5);
|
|
62
|
+
expect(decodedByTs.streamId).toBe(5);
|
|
63
|
+
expect(decodedByJs.frameType).toBe(TsFrameType.HttpRequest);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('negotiates batching between a TS client and a JS host, then exchanges a batch', async () => {
|
|
67
|
+
const hostKeys = await generateEcdhKeyPair();
|
|
68
|
+
const hostPubJwk = await exportPublicKeyJwk(hostKeys.publicKey);
|
|
69
|
+
|
|
70
|
+
const jsHost = createHostHandshake(hostKeys.privateKey);
|
|
71
|
+
const tsClient = await createClientHandshake(hostPubJwk);
|
|
72
|
+
|
|
73
|
+
const hostAction = await jsHost.handleText(tsClient.helloText);
|
|
74
|
+
expect(hostAction.type).toBe('established');
|
|
75
|
+
expect(hostAction.batch).toBe(true);
|
|
76
|
+
const clientAction = await tsClient.handleText(hostAction.replyText);
|
|
77
|
+
expect(clientAction.type).toBe('established');
|
|
78
|
+
expect(clientAction.batch).toBe(true);
|
|
79
|
+
|
|
80
|
+
// TS client encodes a multi-frame batch -> JS host decodes it byte-identically.
|
|
81
|
+
const frames = [
|
|
82
|
+
tsEncode(TsFrameType.HttpBody, 1, new TextEncoder().encode('alpha')),
|
|
83
|
+
tsEncode(TsFrameType.HttpBody, 1, new TextEncoder().encode('beta')),
|
|
84
|
+
tsEncode(TsFrameType.HttpBody, 1, new TextEncoder().encode('gamma')),
|
|
85
|
+
];
|
|
86
|
+
const overWire = await hostAction.channel.decryptor.decrypt(
|
|
87
|
+
await clientAction.channel.encryptor.encrypt(tsEncodeBatch(frames)),
|
|
88
|
+
);
|
|
89
|
+
const jsFrames = jsDecodeBatch(overWire);
|
|
90
|
+
expect(jsFrames.length).toBe(3);
|
|
91
|
+
jsFrames.forEach((frame, index) => expect(Array.from(frame)).toEqual(Array.from(frames[index])));
|
|
92
|
+
|
|
93
|
+
// JS host encodes a batch -> TS client decodes it.
|
|
94
|
+
const downFrames = [
|
|
95
|
+
jsEncode(JsFrameType.HttpBody, 1, new TextEncoder().encode('down-1')),
|
|
96
|
+
jsEncode(JsFrameType.HttpBody, 1, new TextEncoder().encode('down-2')),
|
|
97
|
+
];
|
|
98
|
+
const downWire = await clientAction.channel.decryptor.decrypt(
|
|
99
|
+
await hostAction.channel.encryptor.encrypt(jsEncodeBatch(downFrames)),
|
|
100
|
+
);
|
|
101
|
+
const tsFrames = tsDecodeBatch(downWire);
|
|
102
|
+
expect(tsFrames.length).toBe(2);
|
|
103
|
+
tsFrames.forEach((frame, index) => expect(Array.from(frame)).toEqual(Array.from(downFrames[index])));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('falls back to legacy (no batch) when either peer does not advertise batching', async () => {
|
|
107
|
+
const hostKeys = await generateEcdhKeyPair();
|
|
108
|
+
const hostPubJwk = await exportPublicKeyJwk(hostKeys.publicKey);
|
|
109
|
+
|
|
110
|
+
// Legacy JS host (batch:false) vs batch-capable TS client -> batching off.
|
|
111
|
+
const legacyHost = createHostHandshake(hostKeys.privateKey, { batch: false });
|
|
112
|
+
const tsClient = await createClientHandshake(hostPubJwk);
|
|
113
|
+
const hostAction = await legacyHost.handleText(tsClient.helloText);
|
|
114
|
+
expect(hostAction.type).toBe('established');
|
|
115
|
+
expect(hostAction.batch).toBe(false);
|
|
116
|
+
const clientAction = await tsClient.handleText(hostAction.replyText);
|
|
117
|
+
expect(clientAction.type).toBe('established');
|
|
118
|
+
expect(clientAction.batch).toBe(false);
|
|
119
|
+
|
|
120
|
+
// Legacy wire: plaintext is a single raw tunnel frame (no container tag).
|
|
121
|
+
const frame = tsEncode(TsFrameType.HttpBody, 1, new TextEncoder().encode('legacy'));
|
|
122
|
+
const overWire = await hostAction.channel.decryptor.decrypt(
|
|
123
|
+
await clientAction.channel.encryptor.encrypt(frame),
|
|
124
|
+
);
|
|
125
|
+
expect(jsDecode(overWire).frameType).toBe(JsFrameType.HttpBody);
|
|
126
|
+
|
|
127
|
+
// Batch-capable JS host vs legacy TS client (batch:false) -> also off.
|
|
128
|
+
const host2 = createHostHandshake(hostKeys.privateKey);
|
|
129
|
+
const legacyClient = await createClientHandshake(hostPubJwk, { batch: false });
|
|
130
|
+
const host2Action = await host2.handleText(legacyClient.helloText);
|
|
131
|
+
expect(host2Action.batch).toBe(false);
|
|
132
|
+
const client2Action = await legacyClient.handleText(host2Action.replyText);
|
|
133
|
+
expect(client2Action.batch).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
// E2EE primitives + responder handshake for the private relay (Layer 2).
|
|
2
|
+
// JS mirror of the normative TS implementation in
|
|
3
|
+
// packages/ui/src/lib/relay/{protocol,crypto,handshake}.ts — the web server is
|
|
4
|
+
// plain JS and cannot import from packages/ui, so the logic is copied verbatim
|
|
5
|
+
// (converted to JSDoc'd JS) and MUST stay byte-compatible with those modules.
|
|
6
|
+
// WebCrypto only: `globalThis.crypto.subtle` (Node >= 22).
|
|
7
|
+
// Protocol: PiChamber private relay (Layer 2).
|
|
8
|
+
|
|
9
|
+
const subtle = globalThis.crypto.subtle;
|
|
10
|
+
|
|
11
|
+
export const RELAY_PROTOCOL_VERSION = 1;
|
|
12
|
+
export const RELAY_HKDF_INFO = 'pichamber-relay-v1';
|
|
13
|
+
|
|
14
|
+
// Encrypted frame layout: [1 byte version][12 byte IV][ciphertext + 16 byte GCM tag].
|
|
15
|
+
export const ENCRYPTED_FRAME_VERSION = 1;
|
|
16
|
+
export const ENCRYPTED_FRAME_IV_BYTES = 12;
|
|
17
|
+
export const ENCRYPTED_FRAME_HEADER_BYTES = 1 + ENCRYPTED_FRAME_IV_BYTES;
|
|
18
|
+
export const MAX_PLAINTEXT_FRAME_BYTES = 64 * 1024;
|
|
19
|
+
|
|
20
|
+
// Relay-assigned WebSocket close codes (subset the host needs).
|
|
21
|
+
export const RelayCloseCode = {
|
|
22
|
+
RekeyMismatch: 1008,
|
|
23
|
+
ChannelFailure: 1011,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const ECDH_PARAMS = { name: 'ECDH', namedCurve: 'P-256' };
|
|
27
|
+
const HANDSHAKE_NONCE_BYTES = 16;
|
|
28
|
+
const SESSION_KEY_BYTES = 32;
|
|
29
|
+
const GCM_TAG_BYTES = 16;
|
|
30
|
+
// IV = 4-byte random per-direction prefix || 8-byte big-endian frame counter.
|
|
31
|
+
const IV_PREFIX_BYTES = 4;
|
|
32
|
+
const IV_COUNTER_BYTES = 8;
|
|
33
|
+
|
|
34
|
+
export class RelayCryptoError extends Error {
|
|
35
|
+
constructor(message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = 'RelayCryptoError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @returns {Promise<CryptoKeyPair>} */
|
|
42
|
+
export const generateEcdhKeyPair = () => subtle.generateKey(ECDH_PARAMS, true, ['deriveBits']);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {CryptoKey} key
|
|
46
|
+
* @returns {Promise<JsonWebKey>} public JWK reduced to the fields that define the point
|
|
47
|
+
*/
|
|
48
|
+
export const exportPublicKeyJwk = async (key) => {
|
|
49
|
+
const jwk = await subtle.exportKey('jwk', key);
|
|
50
|
+
return { kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y };
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** @param {JsonWebKey} jwk */
|
|
54
|
+
export const importEcdhPublicKey = async (jwk) => {
|
|
55
|
+
if (jwk.kty !== 'EC' || jwk.crv !== 'P-256' || typeof jwk.x !== 'string' || typeof jwk.y !== 'string') {
|
|
56
|
+
throw new RelayCryptoError('invalid ECDH public key JWK');
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
return await subtle.importKey(
|
|
60
|
+
'jwk',
|
|
61
|
+
{ kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y, ext: true },
|
|
62
|
+
ECDH_PARAMS,
|
|
63
|
+
true,
|
|
64
|
+
[],
|
|
65
|
+
);
|
|
66
|
+
} catch {
|
|
67
|
+
throw new RelayCryptoError('invalid ECDH public key JWK');
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** @param {JsonWebKey} jwk private ECDH JWK (d + point) */
|
|
72
|
+
export const importEcdhPrivateKey = async (jwk) => {
|
|
73
|
+
try {
|
|
74
|
+
return await subtle.importKey('jwk', jwk, ECDH_PARAMS, false, ['deriveBits']);
|
|
75
|
+
} catch {
|
|
76
|
+
throw new RelayCryptoError('invalid ECDH private key JWK');
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Stable fingerprint of a public key, used to detect rekey attempts on re-hello.
|
|
81
|
+
/** @param {JsonWebKey} jwk */
|
|
82
|
+
export const publicKeyJwkFingerprint = (jwk) =>
|
|
83
|
+
JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y });
|
|
84
|
+
|
|
85
|
+
export const generateHandshakeNonce = () => {
|
|
86
|
+
const nonce = new Uint8Array(HANDSHAKE_NONCE_BYTES);
|
|
87
|
+
globalThis.crypto.getRandomValues(nonce);
|
|
88
|
+
return nonce;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Both sides call this with their own private key and the peer's public key;
|
|
93
|
+
* ECDH yields the same shared secret, so the derived key pair matches.
|
|
94
|
+
* @param {CryptoKey} ownPrivateKey
|
|
95
|
+
* @param {CryptoKey} peerPublicKey
|
|
96
|
+
* @param {Uint8Array} handshakeNonce
|
|
97
|
+
* @returns {Promise<{ clientToHost: CryptoKey, hostToClient: CryptoKey }>}
|
|
98
|
+
*/
|
|
99
|
+
export const deriveSessionKeys = async (ownPrivateKey, peerPublicKey, handshakeNonce) => {
|
|
100
|
+
if (handshakeNonce.length !== HANDSHAKE_NONCE_BYTES) {
|
|
101
|
+
throw new RelayCryptoError('invalid handshake nonce length');
|
|
102
|
+
}
|
|
103
|
+
const sharedSecret = await subtle.deriveBits({ name: 'ECDH', public: peerPublicKey }, ownPrivateKey, 256);
|
|
104
|
+
const hkdfKey = await subtle.importKey('raw', sharedSecret, 'HKDF', false, ['deriveBits']);
|
|
105
|
+
const keyMaterial = new Uint8Array(
|
|
106
|
+
await subtle.deriveBits(
|
|
107
|
+
{
|
|
108
|
+
name: 'HKDF',
|
|
109
|
+
hash: 'SHA-256',
|
|
110
|
+
salt: handshakeNonce,
|
|
111
|
+
info: new TextEncoder().encode(RELAY_HKDF_INFO),
|
|
112
|
+
},
|
|
113
|
+
hkdfKey,
|
|
114
|
+
SESSION_KEY_BYTES * 2 * 8,
|
|
115
|
+
),
|
|
116
|
+
);
|
|
117
|
+
const importAesKey = (bytes, usage) => subtle.importKey('raw', bytes, { name: 'AES-GCM' }, false, usage);
|
|
118
|
+
return {
|
|
119
|
+
clientToHost: await importAesKey(keyMaterial.slice(0, SESSION_KEY_BYTES), ['encrypt', 'decrypt']),
|
|
120
|
+
hostToClient: await importAesKey(keyMaterial.slice(SESSION_KEY_BYTES), ['encrypt', 'decrypt']),
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const writeCounter = (target, offset, counter) => {
|
|
125
|
+
for (let i = IV_COUNTER_BYTES - 1; i >= 0; i -= 1) {
|
|
126
|
+
target[offset + i] = Number(counter & 0xffn);
|
|
127
|
+
counter >>= 8n;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const readCounter = (source, offset) => {
|
|
132
|
+
let value = 0n;
|
|
133
|
+
for (let i = 0; i < IV_COUNTER_BYTES; i += 1) {
|
|
134
|
+
value = (value << 8n) | BigInt(source[offset + i]);
|
|
135
|
+
}
|
|
136
|
+
return value;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/** @param {CryptoKey} key AES-256-GCM key for this direction */
|
|
140
|
+
export const createFrameEncryptor = (key) => {
|
|
141
|
+
const ivPrefix = new Uint8Array(IV_PREFIX_BYTES);
|
|
142
|
+
globalThis.crypto.getRandomValues(ivPrefix);
|
|
143
|
+
let counter = 0n;
|
|
144
|
+
return {
|
|
145
|
+
/** @param {Uint8Array} plaintext */
|
|
146
|
+
async encrypt(plaintext) {
|
|
147
|
+
if (plaintext.length > MAX_PLAINTEXT_FRAME_BYTES) {
|
|
148
|
+
throw new RelayCryptoError('plaintext frame exceeds maximum size');
|
|
149
|
+
}
|
|
150
|
+
counter += 1n;
|
|
151
|
+
const iv = new Uint8Array(ENCRYPTED_FRAME_IV_BYTES);
|
|
152
|
+
iv.set(ivPrefix, 0);
|
|
153
|
+
writeCounter(iv, IV_PREFIX_BYTES, counter);
|
|
154
|
+
const ciphertext = new Uint8Array(await subtle.encrypt({ name: 'AES-GCM', iv }, key, plaintext));
|
|
155
|
+
const frame = new Uint8Array(ENCRYPTED_FRAME_HEADER_BYTES + ciphertext.length);
|
|
156
|
+
frame[0] = ENCRYPTED_FRAME_VERSION;
|
|
157
|
+
frame.set(iv, 1);
|
|
158
|
+
frame.set(ciphertext, ENCRYPTED_FRAME_HEADER_BYTES);
|
|
159
|
+
return frame;
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Enforces strictly increasing per-direction counters: the relay WS preserves
|
|
165
|
+
// ordering, so any regression or replay means tampering and must fail closed.
|
|
166
|
+
/** @param {CryptoKey} key AES-256-GCM key for this direction */
|
|
167
|
+
export const createFrameDecryptor = (key) => {
|
|
168
|
+
let lastCounter = 0n;
|
|
169
|
+
return {
|
|
170
|
+
/** @param {Uint8Array} frame */
|
|
171
|
+
async decrypt(frame) {
|
|
172
|
+
if (frame.length < ENCRYPTED_FRAME_HEADER_BYTES + GCM_TAG_BYTES) {
|
|
173
|
+
throw new RelayCryptoError('encrypted frame too short');
|
|
174
|
+
}
|
|
175
|
+
if (frame[0] !== ENCRYPTED_FRAME_VERSION) {
|
|
176
|
+
throw new RelayCryptoError('unsupported encrypted frame version');
|
|
177
|
+
}
|
|
178
|
+
const iv = frame.slice(1, ENCRYPTED_FRAME_HEADER_BYTES);
|
|
179
|
+
const counter = readCounter(iv, IV_PREFIX_BYTES);
|
|
180
|
+
if (counter <= lastCounter) {
|
|
181
|
+
throw new RelayCryptoError('frame counter regression');
|
|
182
|
+
}
|
|
183
|
+
let plaintext;
|
|
184
|
+
try {
|
|
185
|
+
plaintext = await subtle.decrypt({ name: 'AES-GCM', iv }, key, frame.slice(ENCRYPTED_FRAME_HEADER_BYTES));
|
|
186
|
+
} catch {
|
|
187
|
+
throw new RelayCryptoError('frame decryption failed');
|
|
188
|
+
}
|
|
189
|
+
lastCounter = counter;
|
|
190
|
+
return new Uint8Array(plaintext);
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const BASE64URL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
|
196
|
+
|
|
197
|
+
/** @param {Uint8Array} bytes */
|
|
198
|
+
export const bytesToBase64Url = (bytes) => {
|
|
199
|
+
let out = '';
|
|
200
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
201
|
+
const b0 = bytes[i];
|
|
202
|
+
const b1 = i + 1 < bytes.length ? bytes[i + 1] : undefined;
|
|
203
|
+
const b2 = i + 2 < bytes.length ? bytes[i + 2] : undefined;
|
|
204
|
+
out += BASE64URL_ALPHABET[b0 >> 2];
|
|
205
|
+
out += BASE64URL_ALPHABET[((b0 & 0x03) << 4) | ((b1 ?? 0) >> 4)];
|
|
206
|
+
if (b1 !== undefined) out += BASE64URL_ALPHABET[((b1 & 0x0f) << 2) | ((b2 ?? 0) >> 6)];
|
|
207
|
+
if (b2 !== undefined) out += BASE64URL_ALPHABET[b2 & 0x3f];
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/** @param {string} value */
|
|
213
|
+
export const base64UrlToBytes = (value) => {
|
|
214
|
+
if (!/^[A-Za-z0-9_-]*$/.test(value) || value.length % 4 === 1) {
|
|
215
|
+
throw new RelayCryptoError('invalid base64url input');
|
|
216
|
+
}
|
|
217
|
+
const out = new Uint8Array(Math.floor((value.length * 3) / 4));
|
|
218
|
+
let outIndex = 0;
|
|
219
|
+
let buffer = 0;
|
|
220
|
+
let bits = 0;
|
|
221
|
+
for (const char of value) {
|
|
222
|
+
buffer = (buffer << 6) | BASE64URL_ALPHABET.indexOf(char);
|
|
223
|
+
bits += 6;
|
|
224
|
+
if (bits >= 8) {
|
|
225
|
+
bits -= 8;
|
|
226
|
+
out[outIndex] = (buffer >> bits) & 0xff;
|
|
227
|
+
outIndex += 1;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
// Responder handshake state machine (host side). Mirror of createHostHandshake
|
|
235
|
+
// in packages/ui/src/lib/relay/handshake.ts.
|
|
236
|
+
//
|
|
237
|
+
// Fail-closed rules (from the spec):
|
|
238
|
+
// - a repeated identical `hello` re-sends `ready` (client retry race);
|
|
239
|
+
// - a `hello` with a DIFFERENT key on an established channel is a rekey
|
|
240
|
+
// attack -> close 1008, never rekey in place;
|
|
241
|
+
// - plaintext after `ready`, or any decrypt failure -> close 1011.
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
const parseHandshakeMessage = (raw) => {
|
|
245
|
+
let parsed;
|
|
246
|
+
try {
|
|
247
|
+
parsed = JSON.parse(raw);
|
|
248
|
+
} catch {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
if (typeof parsed !== 'object' || parsed === null) return null;
|
|
252
|
+
if (parsed.v !== RELAY_PROTOCOL_VERSION) return null;
|
|
253
|
+
// Unknown/missing capability flag = false = legacy behavior.
|
|
254
|
+
const batch = parsed.batch === true;
|
|
255
|
+
if (parsed.t === 'ready') {
|
|
256
|
+
return { t: 'ready', v: RELAY_PROTOCOL_VERSION, batch };
|
|
257
|
+
}
|
|
258
|
+
if (parsed.t === 'hello' && typeof parsed.nonce === 'string' && typeof parsed.clientPubJwk === 'object' && parsed.clientPubJwk !== null) {
|
|
259
|
+
return { t: 'hello', v: RELAY_PROTOCOL_VERSION, clientPubJwk: parsed.clientPubJwk, nonce: parsed.nonce, batch };
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const failClosed = (reason) => ({
|
|
265
|
+
type: 'fail',
|
|
266
|
+
closeCode: RelayCloseCode.ChannelFailure,
|
|
267
|
+
reason,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Host (responder) handshake. Feed every inbound text frame to `handleText`;
|
|
272
|
+
* it returns one of:
|
|
273
|
+
* { type: 'send-text', text } — send this plaintext frame
|
|
274
|
+
* { type: 'established', channel, replyText } — send replyText first, then switch to encrypted frames
|
|
275
|
+
* { type: 'ignore' } — drop the frame
|
|
276
|
+
* { type: 'fail', closeCode, reason } — close the socket with closeCode
|
|
277
|
+
* @param {CryptoKey} hostEncPrivateKey long-lived ECDH private key
|
|
278
|
+
* @param {{ batch?: boolean }} [options] `batch` defaults true; set false to force legacy behavior
|
|
279
|
+
*/
|
|
280
|
+
export const createHostHandshake = (hostEncPrivateKey, options = {}) => {
|
|
281
|
+
const localBatch = options.batch !== false;
|
|
282
|
+
let established = false;
|
|
283
|
+
let acceptedClientKeyFingerprint = null;
|
|
284
|
+
let readyText = null;
|
|
285
|
+
let negotiatedBatch = false;
|
|
286
|
+
return {
|
|
287
|
+
get established() {
|
|
288
|
+
return established;
|
|
289
|
+
},
|
|
290
|
+
/** @param {string} raw */
|
|
291
|
+
async handleText(raw) {
|
|
292
|
+
const message = parseHandshakeMessage(raw);
|
|
293
|
+
if (message?.t !== 'hello') {
|
|
294
|
+
if (established) {
|
|
295
|
+
return failClosed('plaintext frame on established channel');
|
|
296
|
+
}
|
|
297
|
+
return { type: 'ignore' };
|
|
298
|
+
}
|
|
299
|
+
const fingerprint = publicKeyJwkFingerprint(message.clientPubJwk);
|
|
300
|
+
if (acceptedClientKeyFingerprint !== null) {
|
|
301
|
+
if (fingerprint === acceptedClientKeyFingerprint && readyText !== null) {
|
|
302
|
+
// Client retried `hello` before our `ready` arrived — answer again.
|
|
303
|
+
return { type: 'send-text', text: readyText };
|
|
304
|
+
}
|
|
305
|
+
return { type: 'fail', closeCode: RelayCloseCode.RekeyMismatch, reason: 'rekey mismatch' };
|
|
306
|
+
}
|
|
307
|
+
let clientPublicKey;
|
|
308
|
+
let nonce;
|
|
309
|
+
try {
|
|
310
|
+
clientPublicKey = await importEcdhPublicKey(message.clientPubJwk);
|
|
311
|
+
nonce = base64UrlToBytes(message.nonce);
|
|
312
|
+
} catch {
|
|
313
|
+
return failClosed('malformed hello');
|
|
314
|
+
}
|
|
315
|
+
let keys;
|
|
316
|
+
try {
|
|
317
|
+
keys = await deriveSessionKeys(hostEncPrivateKey, clientPublicKey, nonce);
|
|
318
|
+
} catch {
|
|
319
|
+
return failClosed('key derivation failed');
|
|
320
|
+
}
|
|
321
|
+
acceptedClientKeyFingerprint = fingerprint;
|
|
322
|
+
// Batching runs only if both peers advertised it.
|
|
323
|
+
negotiatedBatch = localBatch && message.batch === true;
|
|
324
|
+
readyText = JSON.stringify(
|
|
325
|
+
negotiatedBatch
|
|
326
|
+
? { t: 'ready', v: RELAY_PROTOCOL_VERSION, batch: true }
|
|
327
|
+
: { t: 'ready', v: RELAY_PROTOCOL_VERSION },
|
|
328
|
+
);
|
|
329
|
+
established = true;
|
|
330
|
+
return {
|
|
331
|
+
type: 'established',
|
|
332
|
+
batch: negotiatedBatch,
|
|
333
|
+
replyText: readyText,
|
|
334
|
+
channel: {
|
|
335
|
+
encryptor: createFrameEncryptor(keys.hostToClient),
|
|
336
|
+
decryptor: createFrameDecryptor(keys.clientToHost),
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
};
|