9remote 2.5.5 → 3.0.0
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 +91 -172
- package/dist/assets/trayIcon.ico +0 -0
- package/dist/cli.cjs +102 -101
- package/dist/ptyDaemon.cjs +10 -9
- package/dist/server.cjs +307 -138
- package/dist/ui/agents/codex.svg +1 -1
- package/dist/ui/assets/index-CAvf0VPf.css +1 -0
- package/dist/ui/assets/index-EXcI2V_p.js +8 -0
- package/dist/ui/favicon.ico +0 -0
- package/dist/ui/index.html +12 -4
- package/dist/vendor/scrcpy-server-v3.1 +0 -0
- package/dist/web/404.html +1 -0
- package/dist/web/__next.__PAGE__.txt +9 -0
- package/dist/web/__next._full.txt +25 -0
- package/dist/web/__next._head.txt +6 -0
- package/dist/web/__next._index.txt +10 -0
- package/dist/web/__next._tree.txt +5 -0
- package/dist/web/_next/static/AUHtJgkYALYyNwHop9pgT/_buildManifest.js +1 -0
- package/dist/web/_next/static/AUHtJgkYALYyNwHop9pgT/_ssgManifest.js +1 -0
- package/dist/web/_next/static/chunks/0a7753a9.4cb02a98e2e453b9.js +1 -0
- package/dist/web/_next/static/chunks/1222.aa4f7f7f397533e9.js +1 -0
- package/dist/web/_next/static/chunks/1228.d291278320bca9ca.js +1 -0
- package/dist/web/_next/static/chunks/1243.f35c2e6494079e05.js +1 -0
- package/dist/web/_next/static/chunks/1311.8c6b8930e22ceea0.js +1 -0
- package/dist/web/_next/static/chunks/132.6032826021079bce.js +1 -0
- package/dist/web/_next/static/chunks/133.1106654a28b5afda.js +1 -0
- package/dist/web/_next/static/chunks/1385.086b560acb0d7a68.js +115 -0
- package/dist/web/_next/static/chunks/1642.cac26d30ac331cb4.js +1 -0
- package/dist/web/_next/static/chunks/1733.f06c240b3ef8a428.js +77 -0
- package/dist/web/_next/static/chunks/1750.f7ba3332b56a8e68.js +1 -0
- package/dist/web/_next/static/chunks/1763.28beedca593b5e19.js +138 -0
- package/dist/web/_next/static/chunks/179.b9e8f385bd414d1c.js +62 -0
- package/dist/web/_next/static/chunks/1838.7c8990f903e3524a.js +215 -0
- package/dist/web/_next/static/chunks/20154aaa.868648f838e03bf1.js +1 -0
- package/dist/web/_next/static/chunks/2038.640b672702093b24.js +1 -0
- package/dist/web/_next/static/chunks/2112.d72eefbdf81659b3.js +23 -0
- package/dist/web/_next/static/chunks/2125.61c6e4a3541e30ad.js +1 -0
- package/dist/web/_next/static/chunks/2129.61afb2f4ff59e46a.js +24 -0
- package/dist/web/_next/static/chunks/213.68ce1361aee3281a.js +1 -0
- package/dist/web/_next/static/chunks/2179.3f55be0b79e3cca7.js +1 -0
- package/dist/web/_next/static/chunks/2180.e6bad8186f52ff89.js +1 -0
- package/dist/web/_next/static/chunks/2264.0fc1474d26067a01.js +1 -0
- package/dist/web/_next/static/chunks/2376.ed6302a91a9de1ae.js +36 -0
- package/dist/web/_next/static/chunks/2545.3ff192a933baf11e.js +1 -0
- package/dist/web/_next/static/chunks/2620.d17f4fac0eec88a5.js +1 -0
- package/dist/web/_next/static/chunks/2847.d3124853ca727b78.js +1 -0
- package/dist/web/_next/static/chunks/2849.f2bcad51c68e44b5.js +1 -0
- package/dist/web/_next/static/chunks/3189.64c6546d621eec0b.js +1 -0
- package/dist/web/_next/static/chunks/3348-9ef29f9cedf6c0df.js +1 -0
- package/dist/web/_next/static/chunks/3429.436b229808b33e81.js +1 -0
- package/dist/web/_next/static/chunks/3514.9643e815d690f2fb.js +1 -0
- package/dist/web/_next/static/chunks/3552.8dd9a3d2c3bcef65.js +86 -0
- package/dist/web/_next/static/chunks/3606.b59e695fd05b4eb3.js +74 -0
- package/dist/web/_next/static/chunks/3692.8c6b8930e22ceea0.js +1 -0
- package/dist/web/_next/static/chunks/3736.4a5662ceeecc6acb.js +94 -0
- package/dist/web/_next/static/chunks/392.842b338137824bb8.js +1 -0
- package/dist/web/_next/static/chunks/3973-9b18bcd9afc0bcb4.js +1 -0
- package/dist/web/_next/static/chunks/3977.8cfd8dec4cb8e893.js +1 -0
- package/dist/web/_next/static/chunks/4090.441fa61655da9b38.js +1 -0
- package/dist/web/_next/static/chunks/4303.54c4aa0b89834fc6.js +1 -0
- package/dist/web/_next/static/chunks/4314.6bbd89507909cad1.js +1 -0
- package/dist/web/_next/static/chunks/4551.d0d6723cacfb90b9.js +226 -0
- package/dist/web/_next/static/chunks/4566.d8e009823b8647d1.js +90 -0
- package/dist/web/_next/static/chunks/4643.dbb152682d076617.js +1 -0
- package/dist/web/_next/static/chunks/4732-76a574510722b2c3.js +11 -0
- package/dist/web/_next/static/chunks/4924.3f5c77a103ac69a9.js +8 -0
- package/dist/web/_next/static/chunks/4930.42e564644c94a93b.js +1 -0
- package/dist/web/_next/static/chunks/5155.d7e8db52ab4c95e0.js +27 -0
- package/dist/web/_next/static/chunks/5158-267908d069475cdc.js +32 -0
- package/dist/web/_next/static/chunks/543.5636e97aa68eb9c8.js +41 -0
- package/dist/web/_next/static/chunks/5506.98304a1821200439.js +1 -0
- package/dist/web/_next/static/chunks/5622.5ead5e748ecedbd1.js +1 -0
- package/dist/web/_next/static/chunks/565f33a6-fd3410bd29f39fdf.js +1 -0
- package/dist/web/_next/static/chunks/5708.22b4531ceebf3eca.js +1 -0
- package/dist/web/_next/static/chunks/5804.ff72a927135eb74c.js +93 -0
- package/dist/web/_next/static/chunks/588.78e2528ff24df85c.js +287 -0
- package/dist/web/_next/static/chunks/6001.e5bcb28bb9ce6571.js +1 -0
- package/dist/web/_next/static/chunks/6075.a6b8434732056526.js +1 -0
- package/dist/web/_next/static/chunks/6180.82fa683aa16f21df.js +1 -0
- package/dist/web/_next/static/chunks/6274.a3795ca6dacb3d62.js +32 -0
- package/dist/web/_next/static/chunks/635-d6ae421935055337.js +1 -0
- package/dist/web/_next/static/chunks/6419.842467d9a877e770.js +1 -0
- package/dist/web/_next/static/chunks/6420.bfa7bc86d03dcefc.js +1 -0
- package/dist/web/_next/static/chunks/6552-145e634541b5b66c.js +1 -0
- package/dist/web/_next/static/chunks/6647.740ec32a32201eab.js +64 -0
- package/dist/web/_next/static/chunks/6730.88fc6f2ccc49496c.js +1 -0
- package/dist/web/_next/static/chunks/6739.82570eba0a02cb26.js +1 -0
- package/dist/web/_next/static/chunks/674.2b5ffad0676aedbe.js +1 -0
- package/dist/web/_next/static/chunks/6742.8e137138a5d1c9e1.js +1 -0
- package/dist/web/_next/static/chunks/6987.743a63813c6288ce.js +79 -0
- package/dist/web/_next/static/chunks/7202.dad90ce4a1d78f67.js +1 -0
- package/dist/web/_next/static/chunks/7233.0f252f6c1cce8058.js +1 -0
- package/dist/web/_next/static/chunks/7248.d89bf328a0dc1b4c.js +1 -0
- package/dist/web/_next/static/chunks/7263.6f224c3924155294.js +1 -0
- package/dist/web/_next/static/chunks/7341.6958eac888a96823.js +34 -0
- package/dist/web/_next/static/chunks/7424.c8c9f85c8ea65885.js +1 -0
- package/dist/web/_next/static/chunks/7507.1204ae29f46e3f01.js +1 -0
- package/dist/web/_next/static/chunks/7629-16de8d4154923623.js +1 -0
- package/dist/web/_next/static/chunks/7811.19082afa63eb73ba.js +1 -0
- package/dist/web/_next/static/chunks/7853.d01982fa0afc1ee2.js +24 -0
- package/dist/web/_next/static/chunks/7908.432ab9feeb9ae756.js +1 -0
- package/dist/web/_next/static/chunks/7960.3b1c77b882ba4bb1.js +1 -0
- package/dist/web/_next/static/chunks/7967.6c62be01fad1603c.js +1 -0
- package/dist/web/_next/static/chunks/7c89255b.f727562586fa21cc.js +153 -0
- package/dist/web/_next/static/chunks/8044.5f87392585c9f3e4.js +38 -0
- package/dist/web/_next/static/chunks/8054.feeb01b8acdb31d4.js +1 -0
- package/dist/web/_next/static/chunks/8115.68d46fb961c724e0.js +172 -0
- package/dist/web/_next/static/chunks/817.cd0f4d221eca4c8b.js +104 -0
- package/dist/web/_next/static/chunks/8334-0e9493cfa1547244.js +1 -0
- package/dist/web/_next/static/chunks/8467.f34d938a32d40325.js +1 -0
- package/dist/web/_next/static/chunks/8495.d6cea1be03635fa8.js +148 -0
- package/dist/web/_next/static/chunks/8516-b3694c9156666b12.js +152 -0
- package/dist/web/_next/static/chunks/8638.03c6480d4aaeb5b3.js +1 -0
- package/dist/web/_next/static/chunks/8716.1ad6febb58e8560e.js +1 -0
- package/dist/web/_next/static/chunks/8839.1ad64faf06fb4a33.js +1 -0
- package/dist/web/_next/static/chunks/8922-a9cee4a104876005.js +1 -0
- package/dist/web/_next/static/chunks/910.43be9c3be85c7fa0.js +21 -0
- package/dist/web/_next/static/chunks/925.7ef504ee39f11840.js +1 -0
- package/dist/web/_next/static/chunks/9259.f4337cbb56e2e0b6.js +1 -0
- package/dist/web/_next/static/chunks/9299.af2a95f6ece07482.js +1 -0
- package/dist/web/_next/static/chunks/9387.96263a2d1602a078.js +1 -0
- package/dist/web/_next/static/chunks/9568-0bfadca4fcb105aa.js +1 -0
- package/dist/web/_next/static/chunks/9641.f6f2982fc4b4735f.js +1 -0
- package/dist/web/_next/static/chunks/9675.be4a175f92aa2f26.js +1 -0
- package/dist/web/_next/static/chunks/9860.05fa134ae44699e0.js +1 -0
- package/dist/web/_next/static/chunks/9b87412a.d2cfae4121762dec.js +122 -0
- package/dist/web/_next/static/chunks/a012e9f0.7dbb2fe05ec0b816.js +82 -0
- package/dist/web/_next/static/chunks/a096f98a-5a93977f5f5de413.js +1 -0
- package/dist/web/_next/static/chunks/ab1da478.d051e56375dab6d8.js +1 -0
- package/dist/web/_next/static/chunks/adcd17ec-9a2791632136b67b.js +18 -0
- package/dist/web/_next/static/chunks/app/_global-error/page-5bd6526483106080.js +1 -0
- package/dist/web/_next/static/chunks/app/_not-found/page-877523282abd5a7c.js +1 -0
- package/dist/web/_next/static/chunks/app/admin/admins/page-0f20b93a1fbe900c.js +1 -0
- package/dist/web/_next/static/chunks/app/admin/login/page-dbcfb21ebbd0bb00.js +1 -0
- package/dist/web/_next/static/chunks/app/admin/modes/page-8f292692166b56fc.js +1 -0
- package/dist/web/_next/static/chunks/app/admin/ota/page-c9fe76f24ac08288.js +1 -0
- package/dist/web/_next/static/chunks/app/admin/page-622381f5dac39ddb.js +1 -0
- package/dist/web/_next/static/chunks/app/admin/turn/page-8f6b835a319f3fe3.js +1 -0
- package/dist/web/_next/static/chunks/app/dev/question/page-5fd80ae293617365.js +1 -0
- package/dist/web/_next/static/chunks/app/layout-341400810c81f67e.js +1 -0
- package/dist/web/_next/static/chunks/app/login/page-31786ee656da76b1.js +1 -0
- package/dist/web/_next/static/chunks/app/page-1cf39061b48db156.js +22 -0
- package/dist/web/_next/static/chunks/app/workspace/browse/page-5bd6526483106080.js +1 -0
- package/dist/web/_next/static/chunks/app/workspace/editor/page-5bd6526483106080.js +1 -0
- package/dist/web/_next/static/chunks/app/workspace/files/page-5bd6526483106080.js +1 -0
- package/dist/web/_next/static/chunks/app/workspace/git/page-5bd6526483106080.js +1 -0
- package/dist/web/_next/static/chunks/app/workspace/layout-5e8f95fbc4ebaa6b.js +14 -0
- package/dist/web/_next/static/chunks/app/workspace/mobile/page-5bd6526483106080.js +1 -0
- package/dist/web/_next/static/chunks/app/workspace/page-5bd6526483106080.js +1 -0
- package/dist/web/_next/static/chunks/app/workspace/remote/page-5bd6526483106080.js +1 -0
- package/dist/web/_next/static/chunks/app/workspace/terminal/[sessionId]/page-5bd6526483106080.js +1 -0
- package/dist/web/_next/static/chunks/app/workspace/workspaces/page-5bd6526483106080.js +1 -0
- package/dist/web/_next/static/chunks/bbfbfb6c.069db3c4cc981ed3.js +1 -0
- package/dist/web/_next/static/chunks/c7879cf7-0d16fce1674078f9.js +1 -0
- package/dist/web/_next/static/chunks/c7ea4e13.6153499d4c2fb1f2.js +1 -0
- package/dist/web/_next/static/chunks/cdcce140.7a440644c3dd52b8.js +1 -0
- package/dist/web/_next/static/chunks/da9a53d6.df25e193f1f5bcae.js +62 -0
- package/dist/web/_next/static/chunks/framework-42053c69543a55f9.js +1 -0
- package/dist/web/_next/static/chunks/main-255c95c36766ce86.js +5 -0
- package/dist/web/_next/static/chunks/main-app-1831d4f902c3fcb9.js +1 -0
- package/dist/web/_next/static/chunks/next/dist/client/components/builtin/app-error-5bd6526483106080.js +1 -0
- package/dist/web/_next/static/chunks/next/dist/client/components/builtin/forbidden-5bd6526483106080.js +1 -0
- package/dist/web/_next/static/chunks/next/dist/client/components/builtin/global-error-76800531999827bd.js +1 -0
- package/dist/web/_next/static/chunks/next/dist/client/components/builtin/not-found-5bd6526483106080.js +1 -0
- package/dist/web/_next/static/chunks/next/dist/client/components/builtin/unauthorized-5bd6526483106080.js +1 -0
- package/dist/web/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dist/web/_next/static/chunks/webpack-c971919003362f96.js +1 -0
- package/dist/web/_next/static/css/56adcd4d72bf2d84.css +1 -0
- package/dist/web/_next/static/css/5c40e58137a11306.css +1 -0
- package/dist/web/_next/static/css/b496025c9cd768cc.css +1 -0
- package/dist/web/_next/static/media/013b72fa676f92e0-s.woff2 +0 -0
- package/dist/web/_next/static/media/22a5144ee8d83bca-s.p.woff2 +0 -0
- package/dist/web/_next/static/media/2b5b02fc7e511755-s.woff2 +0 -0
- package/dist/web/_next/static/media/65f03d54ccadf4a8-s.woff2 +0 -0
- package/dist/web/_next/static/media/7d4881bb7e1bf84d-s.p.woff2 +0 -0
- package/dist/web/_next/static/media/9766a7e9e2e0ad5a-s.woff2 +0 -0
- package/dist/web/_next/static/media/aa016aab0e6d1295-s.woff2 +0 -0
- package/dist/web/_next/static/media/b66cf8e69499582a-s.woff2 +0 -0
- package/dist/web/_next/static/media/b9408752a0c24fb9-s.woff2 +0 -0
- package/dist/web/_next/static/media/e038a29029a234f2-s.woff2 +0 -0
- package/dist/web/_next/static/media/f639721981034f88-s.woff2 +0 -0
- package/dist/web/_not-found/__next._full.txt +23 -0
- package/dist/web/_not-found/__next._head.txt +6 -0
- package/dist/web/_not-found/__next._index.txt +10 -0
- package/dist/web/_not-found/__next._not-found.__PAGE__.txt +5 -0
- package/dist/web/_not-found/__next._not-found.txt +5 -0
- package/dist/web/_not-found/__next._tree.txt +5 -0
- package/dist/web/_not-found.html +1 -0
- package/dist/web/_not-found.txt +23 -0
- package/dist/web/admin/__next._full.txt +27 -0
- package/dist/web/admin/__next._head.txt +6 -0
- package/dist/web/admin/__next._index.txt +10 -0
- package/dist/web/admin/__next._tree.txt +5 -0
- package/dist/web/admin/__next.admin.__PAGE__.txt +9 -0
- package/dist/web/admin/__next.admin.txt +5 -0
- package/dist/web/admin/admins/__next._full.txt +27 -0
- package/dist/web/admin/admins/__next._head.txt +6 -0
- package/dist/web/admin/admins/__next._index.txt +10 -0
- package/dist/web/admin/admins/__next._tree.txt +5 -0
- package/dist/web/admin/admins/__next.admin.admins.__PAGE__.txt +9 -0
- package/dist/web/admin/admins/__next.admin.admins.txt +5 -0
- package/dist/web/admin/admins/__next.admin.txt +5 -0
- package/dist/web/admin/admins.html +1 -0
- package/dist/web/admin/admins.txt +27 -0
- package/dist/web/admin/login/__next._full.txt +27 -0
- package/dist/web/admin/login/__next._head.txt +6 -0
- package/dist/web/admin/login/__next._index.txt +10 -0
- package/dist/web/admin/login/__next._tree.txt +5 -0
- package/dist/web/admin/login/__next.admin.login.__PAGE__.txt +9 -0
- package/dist/web/admin/login/__next.admin.login.txt +5 -0
- package/dist/web/admin/login/__next.admin.txt +5 -0
- package/dist/web/admin/login.html +1 -0
- package/dist/web/admin/login.txt +27 -0
- package/dist/web/admin/modes/__next._full.txt +27 -0
- package/dist/web/admin/modes/__next._head.txt +6 -0
- package/dist/web/admin/modes/__next._index.txt +10 -0
- package/dist/web/admin/modes/__next._tree.txt +5 -0
- package/dist/web/admin/modes/__next.admin.modes.__PAGE__.txt +9 -0
- package/dist/web/admin/modes/__next.admin.modes.txt +5 -0
- package/dist/web/admin/modes/__next.admin.txt +5 -0
- package/dist/web/admin/modes.html +1 -0
- package/dist/web/admin/modes.txt +27 -0
- package/dist/web/admin/ota/__next._full.txt +27 -0
- package/dist/web/admin/ota/__next._head.txt +6 -0
- package/dist/web/admin/ota/__next._index.txt +10 -0
- package/dist/web/admin/ota/__next._tree.txt +5 -0
- package/dist/web/admin/ota/__next.admin.ota.__PAGE__.txt +9 -0
- package/dist/web/admin/ota/__next.admin.ota.txt +5 -0
- package/dist/web/admin/ota/__next.admin.txt +5 -0
- package/dist/web/admin/ota.html +1 -0
- package/dist/web/admin/ota.txt +27 -0
- package/dist/web/admin/turn/__next._full.txt +27 -0
- package/dist/web/admin/turn/__next._head.txt +6 -0
- package/dist/web/admin/turn/__next._index.txt +10 -0
- package/dist/web/admin/turn/__next._tree.txt +5 -0
- package/dist/web/admin/turn/__next.admin.turn.__PAGE__.txt +9 -0
- package/dist/web/admin/turn/__next.admin.turn.txt +5 -0
- package/dist/web/admin/turn/__next.admin.txt +5 -0
- package/dist/web/admin/turn.html +1 -0
- package/dist/web/admin/turn.txt +27 -0
- package/dist/web/admin.html +1 -0
- package/dist/web/admin.txt +27 -0
- package/dist/web/agent-icons/aider.svg +1 -0
- package/dist/web/agent-icons/amp.png +0 -0
- package/dist/web/agent-icons/ante.png +0 -0
- package/dist/web/agent-icons/antigravity.png +0 -0
- package/dist/web/agent-icons/auggie.png +0 -0
- package/dist/web/agent-icons/autohand.png +0 -0
- package/dist/web/agent-icons/claude-ui.png +0 -0
- package/dist/web/agent-icons/claude.svg +1 -0
- package/dist/web/agent-icons/cline.png +0 -0
- package/dist/web/agent-icons/codebuff.png +0 -0
- package/dist/web/agent-icons/codex-ui.png +0 -0
- package/dist/web/agent-icons/codex.svg +1 -0
- package/dist/web/agent-icons/command-code.png +0 -0
- package/dist/web/agent-icons/continue.png +0 -0
- package/dist/web/agent-icons/copilot.png +0 -0
- package/dist/web/agent-icons/crush.png +0 -0
- package/dist/web/agent-icons/cursor.png +0 -0
- package/dist/web/agent-icons/devin.png +0 -0
- package/dist/web/agent-icons/droid.png +0 -0
- package/dist/web/agent-icons/gemini.png +0 -0
- package/dist/web/agent-icons/goose.png +0 -0
- package/dist/web/agent-icons/grok.png +0 -0
- package/dist/web/agent-icons/hermes.png +0 -0
- package/dist/web/agent-icons/kilo.png +0 -0
- package/dist/web/agent-icons/kimi.png +0 -0
- package/dist/web/agent-icons/kiro.png +0 -0
- package/dist/web/agent-icons/mimo-code.png +0 -0
- package/dist/web/agent-icons/mistral-vibe.png +0 -0
- package/dist/web/agent-icons/omp.svg +1 -0
- package/dist/web/agent-icons/openclaude.png +0 -0
- package/dist/web/agent-icons/openclaw.png +0 -0
- package/dist/web/agent-icons/opencode.png +0 -0
- package/dist/web/agent-icons/pi.svg +1 -0
- package/dist/web/agent-icons/prime-agent.png +0 -0
- package/dist/web/agent-icons/qwen-code.png +0 -0
- package/dist/web/agent-icons/rovo.png +0 -0
- package/dist/web/agent-icons/trae.png +0 -0
- package/dist/web/agents/claude.svg +1 -0
- package/dist/web/agents/codex.svg +1 -0
- package/dist/web/agents/gemini.svg +1 -0
- package/dist/web/agents/opencode.svg +1 -0
- package/dist/web/backgrounds/bg10.jpg +0 -0
- package/dist/web/backgrounds/bg11.jpg +0 -0
- package/dist/web/backgrounds/bg12.jpg +0 -0
- package/dist/web/backgrounds/bg13.jpg +0 -0
- package/dist/web/backgrounds/bg2.jpg +0 -0
- package/dist/web/backgrounds/bg3.jpg +0 -0
- package/dist/web/backgrounds/bg4.jpg +0 -0
- package/dist/web/backgrounds/bg5.jpg +0 -0
- package/dist/web/backgrounds/bg6.jpg +0 -0
- package/dist/web/backgrounds/bg7.jpg +0 -0
- package/dist/web/backgrounds/bg8.jpg +0 -0
- package/dist/web/backgrounds/bg9.jpg +0 -0
- package/dist/web/dev/question/__next._full.txt +27 -0
- package/dist/web/dev/question/__next._head.txt +6 -0
- package/dist/web/dev/question/__next._index.txt +10 -0
- package/dist/web/dev/question/__next._tree.txt +5 -0
- package/dist/web/dev/question/__next.dev.question.__PAGE__.txt +9 -0
- package/dist/web/dev/question/__next.dev.question.txt +5 -0
- package/dist/web/dev/question/__next.dev.txt +5 -0
- package/dist/web/dev/question.html +1 -0
- package/dist/web/dev/question.txt +27 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/file.svg +1 -0
- package/dist/web/globe.svg +1 -0
- package/dist/web/icon-192.png +0 -0
- package/dist/web/icon-192.svg +3 -0
- package/dist/web/icon-512.png +0 -0
- package/dist/web/icon-512.svg +3 -0
- package/dist/web/icons/file-types/astro.svg +1 -0
- package/dist/web/icons/file-types/audio.svg +1 -0
- package/dist/web/icons/file-types/avif.svg +1 -0
- package/dist/web/icons/file-types/bat.svg +1 -0
- package/dist/web/icons/file-types/c.svg +1 -0
- package/dist/web/icons/file-types/cheader.svg +1 -0
- package/dist/web/icons/file-types/clojure.svg +1 -0
- package/dist/web/icons/file-types/clojurescript.svg +1 -0
- package/dist/web/icons/file-types/cloudfoundry.svg +1 -0
- package/dist/web/icons/file-types/cpp.svg +1 -0
- package/dist/web/icons/file-types/cppheader.svg +1 -0
- package/dist/web/icons/file-types/csharp.svg +1 -0
- package/dist/web/icons/file-types/css.svg +1 -0
- package/dist/web/icons/file-types/dartlang.svg +1 -0
- package/dist/web/icons/file-types/datadog.svg +1 -0
- package/dist/web/icons/file-types/default.svg +1 -0
- package/dist/web/icons/file-types/diff.svg +1 -0
- package/dist/web/icons/file-types/django.svg +1 -0
- package/dist/web/icons/file-types/docker.svg +1 -0
- package/dist/web/icons/file-types/dotenv.svg +1 -0
- package/dist/web/icons/file-types/editorconfig.svg +1 -0
- package/dist/web/icons/file-types/elixir.svg +1 -0
- package/dist/web/icons/file-types/erlang.svg +1 -0
- package/dist/web/icons/file-types/esphome.svg +1 -0
- package/dist/web/icons/file-types/font.svg +1 -0
- package/dist/web/icons/file-types/git.svg +1 -0
- package/dist/web/icons/file-types/gnu.svg +1 -0
- package/dist/web/icons/file-types/go.svg +1 -0
- package/dist/web/icons/file-types/graphql.svg +1 -0
- package/dist/web/icons/file-types/haskell.svg +1 -0
- package/dist/web/icons/file-types/html.svg +1 -0
- package/dist/web/icons/file-types/image.svg +1 -0
- package/dist/web/icons/file-types/ini.svg +1 -0
- package/dist/web/icons/file-types/janet.svg +1 -0
- package/dist/web/icons/file-types/jar.svg +1 -0
- package/dist/web/icons/file-types/java.svg +1 -0
- package/dist/web/icons/file-types/js.svg +1 -0
- package/dist/web/icons/file-types/json.svg +1 -0
- package/dist/web/icons/file-types/json5.svg +1 -0
- package/dist/web/icons/file-types/kotlin.svg +1 -0
- package/dist/web/icons/file-types/less.svg +1 -0
- package/dist/web/icons/file-types/license.svg +1 -0
- package/dist/web/icons/file-types/locale.svg +1 -0
- package/dist/web/icons/file-types/log.svg +1 -0
- package/dist/web/icons/file-types/lua.svg +1 -0
- package/dist/web/icons/file-types/markdown.svg +1 -0
- package/dist/web/icons/file-types/mdx.svg +1 -0
- package/dist/web/icons/file-types/npm.svg +1 -0
- package/dist/web/icons/file-types/objectivec.svg +1 -0
- package/dist/web/icons/file-types/ocaml.svg +1 -0
- package/dist/web/icons/file-types/parcel.svg +1 -0
- package/dist/web/icons/file-types/pddl_happenings.svg +1 -0
- package/dist/web/icons/file-types/pdf.svg +1 -0
- package/dist/web/icons/file-types/php.svg +1 -0
- package/dist/web/icons/file-types/powershell.svg +1 -0
- package/dist/web/icons/file-types/protobuf.svg +1 -0
- package/dist/web/icons/file-types/python.svg +1 -0
- package/dist/web/icons/file-types/r.svg +1 -0
- package/dist/web/icons/file-types/reactjs.svg +1 -0
- package/dist/web/icons/file-types/reactts.svg +1 -0
- package/dist/web/icons/file-types/ruby.svg +1 -0
- package/dist/web/icons/file-types/rust.svg +1 -0
- package/dist/web/icons/file-types/sass.svg +1 -0
- package/dist/web/icons/file-types/scala.svg +1 -0
- package/dist/web/icons/file-types/scss.svg +1 -0
- package/dist/web/icons/file-types/shell.svg +1 -0
- package/dist/web/icons/file-types/sql.svg +1 -0
- package/dist/web/icons/file-types/sqlite.svg +1 -0
- package/dist/web/icons/file-types/stylus.svg +1 -0
- package/dist/web/icons/file-types/svelte.svg +1 -0
- package/dist/web/icons/file-types/svg.svg +1 -0
- package/dist/web/icons/file-types/swift.svg +1 -0
- package/dist/web/icons/file-types/text.svg +1 -0
- package/dist/web/icons/file-types/toml.svg +1 -0
- package/dist/web/icons/file-types/typescript.svg +1 -0
- package/dist/web/icons/file-types/video.svg +1 -0
- package/dist/web/icons/file-types/vue.svg +1 -0
- package/dist/web/icons/file-types/xml.svg +1 -0
- package/dist/web/icons/file-types/yaml.svg +1 -0
- package/dist/web/icons/file-types/zip.svg +1 -0
- package/dist/web/icons/folder-types/android-open.svg +1 -0
- package/dist/web/icons/folder-types/android.svg +1 -0
- package/dist/web/icons/folder-types/api-open.svg +1 -0
- package/dist/web/icons/folder-types/api.svg +1 -0
- package/dist/web/icons/folder-types/app-open.svg +1 -0
- package/dist/web/icons/folder-types/app.svg +1 -0
- package/dist/web/icons/folder-types/asset-open.svg +1 -0
- package/dist/web/icons/folder-types/asset.svg +1 -0
- package/dist/web/icons/folder-types/azurepipelines-open.svg +1 -0
- package/dist/web/icons/folder-types/azurepipelines.svg +1 -0
- package/dist/web/icons/folder-types/blueprint-open.svg +1 -0
- package/dist/web/icons/folder-types/blueprint.svg +1 -0
- package/dist/web/icons/folder-types/bower-open.svg +1 -0
- package/dist/web/icons/folder-types/bower.svg +1 -0
- package/dist/web/icons/folder-types/component-open.svg +1 -0
- package/dist/web/icons/folder-types/component.svg +1 -0
- package/dist/web/icons/folder-types/config-open.svg +1 -0
- package/dist/web/icons/folder-types/config.svg +1 -0
- package/dist/web/icons/folder-types/controller-open.svg +1 -0
- package/dist/web/icons/folder-types/controller.svg +1 -0
- package/dist/web/icons/folder-types/css-open.svg +1 -0
- package/dist/web/icons/folder-types/css.svg +1 -0
- package/dist/web/icons/folder-types/db-open.svg +1 -0
- package/dist/web/icons/folder-types/db.svg +1 -0
- package/dist/web/icons/folder-types/default-open.svg +1 -0
- package/dist/web/icons/folder-types/default.svg +1 -0
- package/dist/web/icons/folder-types/dist-open.svg +1 -0
- package/dist/web/icons/folder-types/dist.svg +1 -0
- package/dist/web/icons/folder-types/docs-open.svg +1 -0
- package/dist/web/icons/folder-types/docs.svg +1 -0
- package/dist/web/icons/folder-types/fonts-open.svg +1 -0
- package/dist/web/icons/folder-types/fonts.svg +1 -0
- package/dist/web/icons/folder-types/git-open.svg +1 -0
- package/dist/web/icons/folder-types/git.svg +1 -0
- package/dist/web/icons/folder-types/github-open.svg +1 -0
- package/dist/web/icons/folder-types/github.svg +1 -0
- package/dist/web/icons/folder-types/helper-open.svg +1 -0
- package/dist/web/icons/folder-types/helper.svg +1 -0
- package/dist/web/icons/folder-types/hook-open.svg +1 -0
- package/dist/web/icons/folder-types/hook.svg +1 -0
- package/dist/web/icons/folder-types/husky-open.svg +1 -0
- package/dist/web/icons/folder-types/husky.svg +1 -0
- package/dist/web/icons/folder-types/idea-open.svg +1 -0
- package/dist/web/icons/folder-types/idea.svg +1 -0
- package/dist/web/icons/folder-types/images-open.svg +1 -0
- package/dist/web/icons/folder-types/images.svg +1 -0
- package/dist/web/icons/folder-types/interfaces-open.svg +1 -0
- package/dist/web/icons/folder-types/interfaces.svg +1 -0
- package/dist/web/icons/folder-types/ios-open.svg +1 -0
- package/dist/web/icons/folder-types/ios.svg +1 -0
- package/dist/web/icons/folder-types/library-open.svg +1 -0
- package/dist/web/icons/folder-types/library.svg +1 -0
- package/dist/web/icons/folder-types/locale-open.svg +1 -0
- package/dist/web/icons/folder-types/locale.svg +1 -0
- package/dist/web/icons/folder-types/middleware-open.svg +1 -0
- package/dist/web/icons/folder-types/middleware.svg +1 -0
- package/dist/web/icons/folder-types/model-open.svg +1 -0
- package/dist/web/icons/folder-types/model.svg +1 -0
- package/dist/web/icons/folder-types/node-open.svg +1 -0
- package/dist/web/icons/folder-types/node.svg +1 -0
- package/dist/web/icons/folder-types/public-open.svg +1 -0
- package/dist/web/icons/folder-types/public.svg +1 -0
- package/dist/web/icons/folder-types/redux-open.svg +1 -0
- package/dist/web/icons/folder-types/redux.svg +1 -0
- package/dist/web/icons/folder-types/route-open.svg +1 -0
- package/dist/web/icons/folder-types/route.svg +1 -0
- package/dist/web/icons/folder-types/script-open.svg +1 -0
- package/dist/web/icons/folder-types/script.svg +1 -0
- package/dist/web/icons/folder-types/seedkit-open.svg +1 -0
- package/dist/web/icons/folder-types/seedkit.svg +1 -0
- package/dist/web/icons/folder-types/server-open.svg +1 -0
- package/dist/web/icons/folder-types/server.svg +1 -0
- package/dist/web/icons/folder-types/services-open.svg +1 -0
- package/dist/web/icons/folder-types/services.svg +1 -0
- package/dist/web/icons/folder-types/src-open.svg +1 -0
- package/dist/web/icons/folder-types/src.svg +1 -0
- package/dist/web/icons/folder-types/story-open.svg +1 -0
- package/dist/web/icons/folder-types/story.svg +1 -0
- package/dist/web/icons/folder-types/style-open.svg +1 -0
- package/dist/web/icons/folder-types/style.svg +1 -0
- package/dist/web/icons/folder-types/template-open.svg +1 -0
- package/dist/web/icons/folder-types/template.svg +1 -0
- package/dist/web/icons/folder-types/test-open.svg +1 -0
- package/dist/web/icons/folder-types/test.svg +1 -0
- package/dist/web/icons/folder-types/theme-open.svg +1 -0
- package/dist/web/icons/folder-types/theme.svg +1 -0
- package/dist/web/icons/folder-types/tools-open.svg +1 -0
- package/dist/web/icons/folder-types/tools.svg +1 -0
- package/dist/web/icons/folder-types/typescript-open.svg +1 -0
- package/dist/web/icons/folder-types/typescript.svg +1 -0
- package/dist/web/icons/folder-types/typings-open.svg +1 -0
- package/dist/web/icons/folder-types/typings.svg +1 -0
- package/dist/web/icons/folder-types/view-open.svg +1 -0
- package/dist/web/icons/folder-types/view.svg +1 -0
- package/dist/web/icons/folder-types/vscode-open.svg +1 -0
- package/dist/web/icons/folder-types/vscode.svg +1 -0
- package/dist/web/index.html +1 -0
- package/dist/web/index.txt +25 -0
- package/dist/web/login/__next._full.txt +27 -0
- package/dist/web/login/__next._head.txt +6 -0
- package/dist/web/login/__next._index.txt +10 -0
- package/dist/web/login/__next._tree.txt +5 -0
- package/dist/web/login/__next.login.__PAGE__.txt +9 -0
- package/dist/web/login/__next.login.txt +5 -0
- package/dist/web/login.html +1 -0
- package/dist/web/login.txt +27 -0
- package/dist/web/manifest.json +53 -0
- package/dist/web/maskable-192.png +0 -0
- package/dist/web/maskable-512.png +0 -0
- package/dist/web/next.svg +1 -0
- package/dist/web/proxy.html +179 -0
- package/dist/web/screenshot-desktop.png +0 -0
- package/dist/web/screenshot-mobile.png +0 -0
- package/dist/web/screenshots/desktop-ide.webp +0 -0
- package/dist/web/screenshots/mobile-1.webp +0 -0
- package/dist/web/screenshots/mobile-2.webp +0 -0
- package/dist/web/screenshots/mobile-3.webp +0 -0
- package/dist/web/sw-site.js +283 -0
- package/dist/web/sw.js +88 -0
- package/dist/web/swBridgeClaim.js +57 -0
- package/dist/web/vercel.svg +1 -0
- package/dist/web/window.svg +1 -0
- package/dist/web/workspace/__next._full.txt +25 -0
- package/dist/web/workspace/__next._head.txt +6 -0
- package/dist/web/workspace/__next._index.txt +10 -0
- package/dist/web/workspace/__next._tree.txt +6 -0
- package/dist/web/workspace/__next.workspace.__PAGE__.txt +5 -0
- package/dist/web/workspace/__next.workspace.txt +8 -0
- package/dist/web/workspace/browse/__next._full.txt +27 -0
- package/dist/web/workspace/browse/__next._head.txt +6 -0
- package/dist/web/workspace/browse/__next._index.txt +10 -0
- package/dist/web/workspace/browse/__next._tree.txt +6 -0
- package/dist/web/workspace/browse/__next.workspace.browse.__PAGE__.txt +5 -0
- package/dist/web/workspace/browse/__next.workspace.browse.txt +5 -0
- package/dist/web/workspace/browse/__next.workspace.txt +8 -0
- package/dist/web/workspace/browse.html +1 -0
- package/dist/web/workspace/browse.txt +27 -0
- package/dist/web/workspace/editor/__next._full.txt +27 -0
- package/dist/web/workspace/editor/__next._head.txt +6 -0
- package/dist/web/workspace/editor/__next._index.txt +10 -0
- package/dist/web/workspace/editor/__next._tree.txt +6 -0
- package/dist/web/workspace/editor/__next.workspace.editor.__PAGE__.txt +5 -0
- package/dist/web/workspace/editor/__next.workspace.editor.txt +5 -0
- package/dist/web/workspace/editor/__next.workspace.txt +8 -0
- package/dist/web/workspace/editor.html +1 -0
- package/dist/web/workspace/editor.txt +27 -0
- package/dist/web/workspace/files/__next._full.txt +27 -0
- package/dist/web/workspace/files/__next._head.txt +6 -0
- package/dist/web/workspace/files/__next._index.txt +10 -0
- package/dist/web/workspace/files/__next._tree.txt +6 -0
- package/dist/web/workspace/files/__next.workspace.files.__PAGE__.txt +5 -0
- package/dist/web/workspace/files/__next.workspace.files.txt +5 -0
- package/dist/web/workspace/files/__next.workspace.txt +8 -0
- package/dist/web/workspace/files.html +1 -0
- package/dist/web/workspace/files.txt +27 -0
- package/dist/web/workspace/git/__next._full.txt +27 -0
- package/dist/web/workspace/git/__next._head.txt +6 -0
- package/dist/web/workspace/git/__next._index.txt +10 -0
- package/dist/web/workspace/git/__next._tree.txt +6 -0
- package/dist/web/workspace/git/__next.workspace.git.__PAGE__.txt +5 -0
- package/dist/web/workspace/git/__next.workspace.git.txt +5 -0
- package/dist/web/workspace/git/__next.workspace.txt +8 -0
- package/dist/web/workspace/git.html +1 -0
- package/dist/web/workspace/git.txt +27 -0
- package/dist/web/workspace/mobile/__next._full.txt +27 -0
- package/dist/web/workspace/mobile/__next._head.txt +6 -0
- package/dist/web/workspace/mobile/__next._index.txt +10 -0
- package/dist/web/workspace/mobile/__next._tree.txt +6 -0
- package/dist/web/workspace/mobile/__next.workspace.mobile.__PAGE__.txt +5 -0
- package/dist/web/workspace/mobile/__next.workspace.mobile.txt +5 -0
- package/dist/web/workspace/mobile/__next.workspace.txt +8 -0
- package/dist/web/workspace/mobile.html +1 -0
- package/dist/web/workspace/mobile.txt +27 -0
- package/dist/web/workspace/remote/__next._full.txt +27 -0
- package/dist/web/workspace/remote/__next._head.txt +6 -0
- package/dist/web/workspace/remote/__next._index.txt +10 -0
- package/dist/web/workspace/remote/__next._tree.txt +6 -0
- package/dist/web/workspace/remote/__next.workspace.remote.__PAGE__.txt +5 -0
- package/dist/web/workspace/remote/__next.workspace.remote.txt +5 -0
- package/dist/web/workspace/remote/__next.workspace.txt +8 -0
- package/dist/web/workspace/remote.html +1 -0
- package/dist/web/workspace/remote.txt +27 -0
- package/dist/web/workspace/terminal/default/__next._full.txt +27 -0
- package/dist/web/workspace/terminal/default/__next._head.txt +6 -0
- package/dist/web/workspace/terminal/default/__next._index.txt +10 -0
- package/dist/web/workspace/terminal/default/__next._tree.txt +6 -0
- package/dist/web/workspace/terminal/default/__next.workspace.terminal.$d$sessionId.__PAGE__.txt +5 -0
- package/dist/web/workspace/terminal/default/__next.workspace.terminal.$d$sessionId.txt +5 -0
- package/dist/web/workspace/terminal/default/__next.workspace.terminal.txt +5 -0
- package/dist/web/workspace/terminal/default/__next.workspace.txt +8 -0
- package/dist/web/workspace/terminal/default.html +1 -0
- package/dist/web/workspace/terminal/default.txt +27 -0
- package/dist/web/workspace/workspaces/__next._full.txt +27 -0
- package/dist/web/workspace/workspaces/__next._head.txt +6 -0
- package/dist/web/workspace/workspaces/__next._index.txt +10 -0
- package/dist/web/workspace/workspaces/__next._tree.txt +6 -0
- package/dist/web/workspace/workspaces/__next.workspace.txt +8 -0
- package/dist/web/workspace/workspaces/__next.workspace.workspaces.__PAGE__.txt +5 -0
- package/dist/web/workspace/workspaces/__next.workspace.workspaces.txt +5 -0
- package/dist/web/workspace/workspaces.html +1 -0
- package/dist/web/workspace/workspaces.txt +27 -0
- package/dist/web/workspace.html +1 -0
- package/dist/web/workspace.txt +25 -0
- package/package.json +4 -4
- package/dist/ui/assets/index-CLABbREo.css +0 -1
- package/dist/ui/assets/index-MX08IV5-.js +0 -58
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "9Remote Terminal",
|
|
3
|
+
"short_name": "9Remote",
|
|
4
|
+
"id": "/?source=pwa",
|
|
5
|
+
"description": "Access your terminal from anywhere - secure remote terminal access",
|
|
6
|
+
"start_url": "/login",
|
|
7
|
+
"display": "standalone",
|
|
8
|
+
"background_color": "#0f0f0f",
|
|
9
|
+
"theme_color": "#121212",
|
|
10
|
+
"icons": [
|
|
11
|
+
{
|
|
12
|
+
"src": "/icon-192.png",
|
|
13
|
+
"sizes": "192x192",
|
|
14
|
+
"type": "image/png",
|
|
15
|
+
"purpose": "any"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"src": "/icon-512.png",
|
|
19
|
+
"sizes": "512x512",
|
|
20
|
+
"type": "image/png",
|
|
21
|
+
"purpose": "any"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"src": "/maskable-192.png",
|
|
25
|
+
"sizes": "192x192",
|
|
26
|
+
"type": "image/png",
|
|
27
|
+
"purpose": "maskable"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"src": "/maskable-512.png",
|
|
31
|
+
"sizes": "512x512",
|
|
32
|
+
"type": "image/png",
|
|
33
|
+
"purpose": "maskable"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"categories": ["utilities", "productivity"],
|
|
37
|
+
"screenshots": [
|
|
38
|
+
{
|
|
39
|
+
"src": "/screenshot-desktop.png",
|
|
40
|
+
"sizes": "1280x720",
|
|
41
|
+
"type": "image/png",
|
|
42
|
+
"form_factor": "wide",
|
|
43
|
+
"label": "9Remote on desktop"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"src": "/screenshot-mobile.png",
|
|
47
|
+
"sizes": "390x844",
|
|
48
|
+
"type": "image/png",
|
|
49
|
+
"form_factor": "narrow",
|
|
50
|
+
"label": "9Remote on mobile"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<meta charset="utf-8">
|
|
3
|
+
<title>9Remote site proxy</title>
|
|
4
|
+
<style>
|
|
5
|
+
html, body { margin: 0; height: 100%; background: #0b1220; }
|
|
6
|
+
iframe { width: 100%; height: 100%; border: 0; display: block; }
|
|
7
|
+
p { font: 14px system-ui; color: #94a3b8; text-align: center; padding-top: 3rem; margin: 0; }
|
|
8
|
+
</style>
|
|
9
|
+
<p id="status">Connecting to local site…</p>
|
|
10
|
+
<script>
|
|
11
|
+
// Proxy shell for the sites origin.
|
|
12
|
+
//
|
|
13
|
+
// This page is ours, the site inside it is not. It lives here so the browsed
|
|
14
|
+
// server gets an origin with nothing worth stealing: the app's storage — the
|
|
15
|
+
// device-trust tail, the saved keys — belongs to the app's own origin and is
|
|
16
|
+
// unreachable from this one.
|
|
17
|
+
//
|
|
18
|
+
// It sits between two parties that cannot address each other. The service
|
|
19
|
+
// worker can only postMessage its own clients, and the transport socket lives
|
|
20
|
+
// in the app on the other origin, so this page carries each request across:
|
|
21
|
+
// SW --(MessageChannel port)--> proxy --(postMessage)--> app --> agent
|
|
22
|
+
// and the reply back along the same path.
|
|
23
|
+
(function () {
|
|
24
|
+
"use strict";
|
|
25
|
+
|
|
26
|
+
// Only the app may drive this page, and only this page's opener is told
|
|
27
|
+
// anything. An opaque origin sends the literal "null" — never a match.
|
|
28
|
+
//
|
|
29
|
+
// The agent serves its workspace on every loopback name, so this has to accept
|
|
30
|
+
// the app whichever one it was opened on — a single hardcoded "localhost"
|
|
31
|
+
// leaves the frame unable to talk to a page opened on 127.0.0.1, and it shows
|
|
32
|
+
// as a shell that never loads anything.
|
|
33
|
+
var APP_ORIGINS = (function () {
|
|
34
|
+
var h = location.hostname;
|
|
35
|
+
var port = location.port ? ":" + location.port : "";
|
|
36
|
+
if (h === "sites.9remote.cc") return ["https://9remote.cc"];
|
|
37
|
+
if (h === "sites-dev.9remote.cc") return ["https://dev.9remote.cc"];
|
|
38
|
+
if (h === "sites.localhost") return ["http://localhost" + port, "http://127.0.0.1" + port, "http://[::1]" + port];
|
|
39
|
+
return [];
|
|
40
|
+
})();
|
|
41
|
+
|
|
42
|
+
function isAppOrigin(origin) {
|
|
43
|
+
return APP_ORIGINS.indexOf(origin) !== -1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
var status = document.getElementById("status");
|
|
47
|
+
var frame = null;
|
|
48
|
+
var pending = new Map(); // reqId -> MessagePort back to the SW
|
|
49
|
+
var reqSeq = 0;
|
|
50
|
+
|
|
51
|
+
function fail(text) {
|
|
52
|
+
if (status) status.textContent = text;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseHash() {
|
|
56
|
+
var h = new URLSearchParams((location.hash || "").replace(/^#/, ""));
|
|
57
|
+
var port = Number(h.get("port"));
|
|
58
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) return null;
|
|
59
|
+
return { port: port, path: h.get("path") || "/" };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── SW side ───────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
navigator.serviceWorker.addEventListener("message", function (event) {
|
|
65
|
+
var msg = event.data;
|
|
66
|
+
if (!msg || typeof msg !== "object") return;
|
|
67
|
+
|
|
68
|
+
if (msg.type === "http-request") {
|
|
69
|
+
// The SW handed us a port for this one request; remember it, ask the app,
|
|
70
|
+
// and answer on that port when the app replies.
|
|
71
|
+
var id = "px-" + (++reqSeq);
|
|
72
|
+
var replyPort = event.ports && event.ports[0];
|
|
73
|
+
if (!replyPort) return;
|
|
74
|
+
pending.set(id, replyPort);
|
|
75
|
+
post({ type: "site:request", id: id, payload: msg });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (msg.type === "site-nav") {
|
|
80
|
+
post({ type: "site:nav", port: msg.port, path: msg.path });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// The worker could not serve a page at all. Its own reply is plain text that
|
|
85
|
+
// the frame renders as a bare white page, so relay the reason upward.
|
|
86
|
+
if (msg.type === "site-error") {
|
|
87
|
+
post({ type: "site-error", message: msg.message, port: msg.port });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// The worker restarted and lost its bridge pointer — re-announce.
|
|
92
|
+
if (msg.type === "sw-hello") {
|
|
93
|
+
helloTo(event.source);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
function helloTo(worker) {
|
|
98
|
+
try { worker && worker.postMessage({ type: "bridge-hello" }); } catch (e) { /* gone */ }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── App side ──────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
// The shell speaks first (site:ready, and requests relayed from the worker),
|
|
104
|
+
// so it cannot wait to learn the parent's origin from a message. Posting to
|
|
105
|
+
// every allowed origin is enough: the browser delivers to the parent only for
|
|
106
|
+
// the one that matches it, and a parent outside the list receives nothing.
|
|
107
|
+
function post(message) {
|
|
108
|
+
for (var i = 0; i < APP_ORIGINS.length; i++) {
|
|
109
|
+
try { parent.postMessage(message, APP_ORIGINS[i]); } catch (e) { /* shell gone */ }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
window.addEventListener("message", function (event) {
|
|
114
|
+
// Everything below acts on the app's word; an unchecked origin here would
|
|
115
|
+
// let any page that frames us speak for it.
|
|
116
|
+
if (!isAppOrigin(event.origin) || event.source !== parent) return;
|
|
117
|
+
var msg = event.data;
|
|
118
|
+
if (!msg || typeof msg !== "object") return;
|
|
119
|
+
|
|
120
|
+
if (msg.type === "site:reply") {
|
|
121
|
+
var port = pending.get(msg.id);
|
|
122
|
+
if (!port) return;
|
|
123
|
+
pending.delete(msg.id);
|
|
124
|
+
try { port.postMessage(msg.payload); } catch (e) { /* SW gone */ }
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// ── Boot ──────────────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
function show(port, path) {
|
|
133
|
+
var src = "/browse/" + Number(port) + (path || "/");
|
|
134
|
+
if (!frame) {
|
|
135
|
+
frame = document.createElement("iframe");
|
|
136
|
+
frame.title = "local site";
|
|
137
|
+
document.body.appendChild(frame);
|
|
138
|
+
if (status) status.remove();
|
|
139
|
+
}
|
|
140
|
+
frame.src = src;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!APP_ORIGINS.length) {
|
|
144
|
+
fail("9Remote: unknown host");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (!("serviceWorker" in navigator)) {
|
|
148
|
+
fail("Cannot connect: service worker unavailable");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
navigator.serviceWorker.register("/sw-site.js", { scope: "/" }).then(function (reg) {
|
|
153
|
+
var sayHello = function () { helloTo(reg.active || reg.waiting || reg.installing); };
|
|
154
|
+
sayHello();
|
|
155
|
+
reg.addEventListener("updatefound", function () {
|
|
156
|
+
var installing = reg.installing;
|
|
157
|
+
installing && installing.addEventListener("statechange", function () {
|
|
158
|
+
if (installing.state === "activated") sayHello();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// The frame must not load before the worker controls this origin, or the
|
|
163
|
+
// request goes to the edge and comes back as this same page.
|
|
164
|
+
var start = function () {
|
|
165
|
+
var target = parseHash();
|
|
166
|
+
if (!target) return fail("9Remote: no site address");
|
|
167
|
+
show(target.port, target.path);
|
|
168
|
+
post({ type: "site:ready" });
|
|
169
|
+
};
|
|
170
|
+
if (navigator.serviceWorker.controller) return start();
|
|
171
|
+
navigator.serviceWorker.addEventListener("controllerchange", start, { once: true });
|
|
172
|
+
// Activation without a controllerchange happens; don't hang forever on it.
|
|
173
|
+
setTimeout(function () { if (!frame) start(); }, 4000);
|
|
174
|
+
}).catch(function () {
|
|
175
|
+
fail("Cannot connect: service worker blocked");
|
|
176
|
+
post({ type: "site:swBlocked" });
|
|
177
|
+
});
|
|
178
|
+
})();
|
|
179
|
+
</script>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// Site proxy service worker: serves /browse/<port>/... documents via transport bridge.
|
|
2
|
+
|
|
3
|
+
importScripts("/swBridgeClaim.js");
|
|
4
|
+
|
|
5
|
+
const BROWSE_RE = /^\/browse\/(\d{1,5})(\/.*)?$/;
|
|
6
|
+
const BRIDGE_TIMEOUT_MS = 60000;
|
|
7
|
+
const BRIDGE_DISCOVER_MS = 3000;
|
|
8
|
+
const MAX_REQUEST_BODY_BYTES = 512 * 1024;
|
|
9
|
+
const REDIRECT_STATUSES = [301, 302, 303, 307, 308];
|
|
10
|
+
|
|
11
|
+
// Restrict framing and origin communication via Content Security Policy.
|
|
12
|
+
const SITE_CSP = [
|
|
13
|
+
"connect-src 'self'",
|
|
14
|
+
"form-action 'self'",
|
|
15
|
+
`frame-ancestors 'self' ${appOriginsFor(self.location.hostname, self.location.port)}`.trim(),
|
|
16
|
+
"base-uri 'self'"
|
|
17
|
+
].join("; ");
|
|
18
|
+
|
|
19
|
+
let bridgeClient = null;
|
|
20
|
+
let bridgeDiscover = null;
|
|
21
|
+
let bridgeResolve = null;
|
|
22
|
+
let reqCounter = 0;
|
|
23
|
+
|
|
24
|
+
self.addEventListener("install", (event) => event.waitUntil(self.skipWaiting()));
|
|
25
|
+
self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim()));
|
|
26
|
+
|
|
27
|
+
self.addEventListener("message", (event) => {
|
|
28
|
+
const msg = event.data;
|
|
29
|
+
if (!msg || typeof msg !== "object") return;
|
|
30
|
+
if (msg.type === "bridge-hello") {
|
|
31
|
+
// Verify source is shell to prevent cross-site privilege escalation.
|
|
32
|
+
if (!canBeBridge(event.source?.url, self.location.origin)) return;
|
|
33
|
+
bridgeClient = event.source;
|
|
34
|
+
if (bridgeResolve) {
|
|
35
|
+
const resolve = bridgeResolve;
|
|
36
|
+
bridgeDiscover = null;
|
|
37
|
+
bridgeResolve = null;
|
|
38
|
+
resolve();
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function base64ToBytes(b64) {
|
|
45
|
+
const bin = atob(b64 || "");
|
|
46
|
+
const out = new Uint8Array(bin.length);
|
|
47
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function bytesToBase64(buffer) {
|
|
52
|
+
const bytes = new Uint8Array(buffer);
|
|
53
|
+
let bin = "";
|
|
54
|
+
const CHUNK = 0x8000;
|
|
55
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
56
|
+
bin += String.fromCharCode.apply(null, bytes.subarray(i, i + CHUNK));
|
|
57
|
+
}
|
|
58
|
+
return btoa(bin);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function discoverBridge() {
|
|
62
|
+
if (bridgeClient) return Promise.resolve();
|
|
63
|
+
if (!bridgeDiscover) {
|
|
64
|
+
bridgeDiscover = new Promise((resolve) => {
|
|
65
|
+
bridgeResolve = resolve;
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
bridgeDiscover = null;
|
|
68
|
+
bridgeResolve = null;
|
|
69
|
+
resolve();
|
|
70
|
+
}, BRIDGE_DISCOVER_MS);
|
|
71
|
+
});
|
|
72
|
+
self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clients) => {
|
|
73
|
+
for (const client of clients) {
|
|
74
|
+
if (canBeBridge(client.url, self.location.origin)) client.postMessage({ type: "sw-hello" });
|
|
75
|
+
}
|
|
76
|
+
}).catch(() => {});
|
|
77
|
+
}
|
|
78
|
+
return bridgeDiscover;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Injected script rewriting absolute links/forms into /browse/<port> scope.
|
|
82
|
+
function inScopeScript(port) {
|
|
83
|
+
return `<script data-9remote="site-proxy">
|
|
84
|
+
(function () {
|
|
85
|
+
if (window.__nineRemoteSiteProxy) return;
|
|
86
|
+
window.__nineRemoteSiteProxy = true;
|
|
87
|
+
var BASE = "/browse/${port}";
|
|
88
|
+
function scoped(path) {
|
|
89
|
+
if (!path || path.indexOf(BASE + "/") === 0 || path === BASE) return null;
|
|
90
|
+
return path.charAt(0) === "/" ? BASE + path : null;
|
|
91
|
+
}
|
|
92
|
+
// Bubble phase allows SPA routers to handle owned routes first.
|
|
93
|
+
document.addEventListener("click", function (e) {
|
|
94
|
+
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
95
|
+
var a = e.target && e.target.closest && e.target.closest("a[href]");
|
|
96
|
+
if (!a || a.target && a.target !== "_self" || a.hasAttribute("download")) return;
|
|
97
|
+
var url;
|
|
98
|
+
try { url = new URL(a.getAttribute("href"), location.href); } catch (err) { return; }
|
|
99
|
+
if (url.origin !== location.origin) return;
|
|
100
|
+
var next = scoped(url.pathname);
|
|
101
|
+
if (!next) return;
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
location.replace(next + url.search + url.hash);
|
|
104
|
+
});
|
|
105
|
+
document.addEventListener("submit", function (e) {
|
|
106
|
+
if (e.defaultPrevented) return;
|
|
107
|
+
var form = e.target;
|
|
108
|
+
if (!form || form.tagName !== "FORM" || form.target && form.target !== "_self") return;
|
|
109
|
+
var action = form.getAttribute("action");
|
|
110
|
+
if (action === null) return;
|
|
111
|
+
var url;
|
|
112
|
+
try { url = new URL(action, location.href); } catch (err) { return; }
|
|
113
|
+
if (url.origin !== location.origin) return;
|
|
114
|
+
var next = scoped(url.pathname);
|
|
115
|
+
if (next) form.setAttribute("action", next + url.search);
|
|
116
|
+
}, true);
|
|
117
|
+
// Downgrade pushState to replaceState so iframe history doesn't swallow parent Back.
|
|
118
|
+
var nativeReplace = history.replaceState.bind(history);
|
|
119
|
+
["pushState", "replaceState"].forEach(function (name) {
|
|
120
|
+
history[name] = function (state, title, path) {
|
|
121
|
+
if (typeof path === "string") {
|
|
122
|
+
try {
|
|
123
|
+
var url = new URL(path, location.href);
|
|
124
|
+
if (url.origin === location.origin) {
|
|
125
|
+
var next = scoped(url.pathname);
|
|
126
|
+
if (next) path = next + url.search + url.hash;
|
|
127
|
+
}
|
|
128
|
+
} catch (err) { /* leave path as-is */ }
|
|
129
|
+
}
|
|
130
|
+
return nativeReplace(state, title, path);
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
})();
|
|
134
|
+
</script>`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function injectIntoHtml(html, port) {
|
|
138
|
+
const tag = inScopeScript(port);
|
|
139
|
+
const head = html.match(/<head[^>]*>/i);
|
|
140
|
+
if (head) return html.replace(head[0], () => head[0] + tag);
|
|
141
|
+
return tag + html;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function portFromReferrer(request) {
|
|
145
|
+
try {
|
|
146
|
+
if (request.referrer) {
|
|
147
|
+
const match = new URL(request.referrer).pathname.match(BROWSE_RE);
|
|
148
|
+
if (match) return Number(match[1]);
|
|
149
|
+
}
|
|
150
|
+
} catch { /* bad referrer — fall through */ }
|
|
151
|
+
const clients = await self.clients.matchAll({ type: "window", includeUncontrolled: false });
|
|
152
|
+
for (const client of clients) {
|
|
153
|
+
const match = new URL(client.url).pathname.match(BROWSE_RE);
|
|
154
|
+
if (match) return Number(match[1]);
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// MessageChannel per request to keep concurrent requests isolated.
|
|
160
|
+
function requestViaBridge(reqId, port, method, target, headers, bodyB64) {
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
const channel = new MessageChannel();
|
|
163
|
+
const timer = setTimeout(() => {
|
|
164
|
+
channel.port1.close();
|
|
165
|
+
reject(new Error("bridge-timeout"));
|
|
166
|
+
}, BRIDGE_TIMEOUT_MS);
|
|
167
|
+
channel.port1.onmessage = (event) => {
|
|
168
|
+
clearTimeout(timer);
|
|
169
|
+
channel.port1.close();
|
|
170
|
+
resolve(event.data);
|
|
171
|
+
};
|
|
172
|
+
bridgeClient.postMessage(
|
|
173
|
+
{ type: "http-request", reqId, port, method, target, headers, bodyB64 },
|
|
174
|
+
[channel.port2]
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function fail(message, status, port) {
|
|
180
|
+
if (port) {
|
|
181
|
+
self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clients) => {
|
|
182
|
+
for (const client of clients) {
|
|
183
|
+
if (canBeBridge(client.url, self.location.origin)) client.postMessage({ type: "site-error", message, port });
|
|
184
|
+
}
|
|
185
|
+
}).catch(() => {});
|
|
186
|
+
}
|
|
187
|
+
return new Response(`9Remote: ${message}`, { status });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function handle(request, url, inScope) {
|
|
191
|
+
const port = inScope ? Number(inScope[1]) : await portFromReferrer(request);
|
|
192
|
+
if (!port) return fail("no site context for this request", 503, null);
|
|
193
|
+
|
|
194
|
+
// Prevent cross-site subresource access within shared origin.
|
|
195
|
+
if (inScope && request.mode !== "navigate") {
|
|
196
|
+
const from = await portFromReferrer(request);
|
|
197
|
+
if (from && from !== port) {
|
|
198
|
+
return new Response("9Remote: cross-site request refused", { status: 403 });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
await discoverBridge();
|
|
203
|
+
if (!bridgeClient) return fail("bridge not connected — open the 9Remote app", 503, port);
|
|
204
|
+
|
|
205
|
+
url.searchParams.delete("r");
|
|
206
|
+
const path = inScope ? (inScope[2] || "/") : url.pathname;
|
|
207
|
+
const target = path + (url.search || "");
|
|
208
|
+
|
|
209
|
+
if (request.mode === "navigate") {
|
|
210
|
+
bridgeClient.postMessage({ type: "site-nav", port, path });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const headers = {};
|
|
214
|
+
request.headers.forEach((value, key) => {
|
|
215
|
+
if (key.toLowerCase() !== "cookie") headers[key] = value;
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
let bodyB64 = null;
|
|
219
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
220
|
+
const buf = await request.arrayBuffer();
|
|
221
|
+
if (buf.byteLength > MAX_REQUEST_BODY_BYTES) {
|
|
222
|
+
return new Response("9Remote: request body too large", { status: 413 });
|
|
223
|
+
}
|
|
224
|
+
if (buf.byteLength > 0) bodyB64 = bytesToBase64(buf);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const reqId = `sw-${Date.now()}-${++reqCounter}`;
|
|
228
|
+
let msg;
|
|
229
|
+
try {
|
|
230
|
+
msg = await requestViaBridge(reqId, port, request.method, target, headers, bodyB64);
|
|
231
|
+
} catch {
|
|
232
|
+
return fail("request timed out", 504, port);
|
|
233
|
+
}
|
|
234
|
+
if (!msg || msg.error) return fail(msg?.error || "no reply from the agent", 502, port);
|
|
235
|
+
|
|
236
|
+
const resHeaders = new Headers(msg.headers || {});
|
|
237
|
+
|
|
238
|
+
const location = resHeaders.get("location");
|
|
239
|
+
if (location && REDIRECT_STATUSES.includes(msg.status)) {
|
|
240
|
+
let next = null;
|
|
241
|
+
try {
|
|
242
|
+
const abs = new URL(location, `http://localhost:${port}`);
|
|
243
|
+
const sameSite = location.startsWith("/") || abs.host === `localhost:${port}`;
|
|
244
|
+
if (sameSite) next = abs.pathname + abs.search;
|
|
245
|
+
} catch { /* keep null */ }
|
|
246
|
+
if (next) return Response.redirect(`/browse/${port}${next}`, msg.status);
|
|
247
|
+
return Response.redirect(location, msg.status);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (msg.status === 204 || msg.status === 304) {
|
|
251
|
+
return new Response(null, { status: msg.status, headers: resHeaders });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Enforce CSP on proxied site content.
|
|
255
|
+
resHeaders.set("Content-Security-Policy", SITE_CSP);
|
|
256
|
+
|
|
257
|
+
const bytes = base64ToBytes(msg.bodyB64);
|
|
258
|
+
if ((resHeaders.get("content-type") || "").includes("text/html")) {
|
|
259
|
+
const html = new TextDecoder("utf-8").decode(bytes);
|
|
260
|
+
return new Response(injectIntoHtml(html, port), { status: msg.status, headers: resHeaders });
|
|
261
|
+
}
|
|
262
|
+
return new Response(bytes, { status: msg.status, headers: resHeaders });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
self.addEventListener("fetch", (event) => {
|
|
266
|
+
const request = event.request;
|
|
267
|
+
let url;
|
|
268
|
+
try { url = new URL(request.url); } catch { return; }
|
|
269
|
+
|
|
270
|
+
if (url.origin !== self.location.origin) return;
|
|
271
|
+
if (url.pathname === "/sw-site.js") return;
|
|
272
|
+
if (url.pathname.startsWith("/cdn-cgi/")) return;
|
|
273
|
+
|
|
274
|
+
const inScope = url.pathname.match(BROWSE_RE);
|
|
275
|
+
if (inScope) {
|
|
276
|
+
event.respondWith(handle(request, url, inScope));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Handle root-relative subresource requests from in-scope pages.
|
|
281
|
+
if (request.mode === "navigate") return;
|
|
282
|
+
event.respondWith(handle(request, url, null));
|
|
283
|
+
});
|
package/dist/web/sw.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Service Worker - NO CACHE + WebPush Notifications
|
|
2
|
+
// Handles PWA installation and push notifications
|
|
3
|
+
|
|
4
|
+
self.addEventListener("install", () => {
|
|
5
|
+
self.skipWaiting();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
self.addEventListener("activate", (event) => {
|
|
9
|
+
event.waitUntil(
|
|
10
|
+
caches.keys().then((cacheNames) => {
|
|
11
|
+
return Promise.all(
|
|
12
|
+
cacheNames.map((cacheName) => caches.delete(cacheName))
|
|
13
|
+
);
|
|
14
|
+
})
|
|
15
|
+
);
|
|
16
|
+
return self.clients.claim();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Fetch event - Always network, no cache
|
|
20
|
+
self.addEventListener("fetch", (event) => {
|
|
21
|
+
event.respondWith(
|
|
22
|
+
fetch(event.request, {
|
|
23
|
+
cache: "no-store"
|
|
24
|
+
})
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Push notification received
|
|
29
|
+
self.addEventListener("push", (event) => {
|
|
30
|
+
if (!event.data) return;
|
|
31
|
+
|
|
32
|
+
event.waitUntil(
|
|
33
|
+
(async () => {
|
|
34
|
+
// Skip when a PWA window is focused — backstop against stale server focus state
|
|
35
|
+
const clientList = await self.clients.matchAll({ type: "window", includeUncontrolled: true });
|
|
36
|
+
if (clientList.some((c) => c.visibilityState === "visible")) return;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const data = event.data.json();
|
|
40
|
+
const title = data.title || "9Remote";
|
|
41
|
+
const options = {
|
|
42
|
+
body: data.body || "Notification",
|
|
43
|
+
icon: "/icon-192.png",
|
|
44
|
+
badge: "/icon-192.png",
|
|
45
|
+
data: data.data || { url: "/workspace" },
|
|
46
|
+
vibrate: [200, 100, 200],
|
|
47
|
+
tag: "9remote-notification",
|
|
48
|
+
renotify: true
|
|
49
|
+
};
|
|
50
|
+
await self.registration.showNotification(title, options);
|
|
51
|
+
|
|
52
|
+
// PWA icon badge (Android/desktop Chrome/Edge/Brave) — iOS silently ignores
|
|
53
|
+
if (self.registration.setAppBadge) {
|
|
54
|
+
const badgeCount = typeof data.badge === "number" ? data.badge : 1;
|
|
55
|
+
await self.registration.setAppBadge(badgeCount);
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error("Push event error:", error);
|
|
59
|
+
}
|
|
60
|
+
})()
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Notification click - focus any same-origin client and deep-link into it
|
|
65
|
+
self.addEventListener("notificationclick", (event) => {
|
|
66
|
+
event.notification.close();
|
|
67
|
+
|
|
68
|
+
// User acknowledged → clear icon badge
|
|
69
|
+
if (self.registration.clearAppBadge) {
|
|
70
|
+
self.registration.clearAppBadge().catch(() => {});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const url = event.notification.data?.url || "/workspace";
|
|
74
|
+
|
|
75
|
+
event.waitUntil(
|
|
76
|
+
self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clientList) => {
|
|
77
|
+
// Focus any existing same-origin client to preserve auth/state
|
|
78
|
+
for (const client of clientList) {
|
|
79
|
+
if ("focus" in client) {
|
|
80
|
+
client.postMessage({ type: "NOTIFICATION_CLICK", url });
|
|
81
|
+
return client.focus();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// No existing client — open a new one
|
|
85
|
+
return self.clients.openWindow(url);
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Which client may act as the bridge.
|
|
2
|
+
//
|
|
3
|
+
// The worker forwards every browsed request to whichever client announced
|
|
4
|
+
// itself with "bridge-hello", and every browsed site lives on this same origin.
|
|
5
|
+
// So a dev server rendering something the developer did not write can claim the
|
|
6
|
+
// role, then see every other tab's requests — paths, headers, bodies — and
|
|
7
|
+
// answer them with content of its own.
|
|
8
|
+
//
|
|
9
|
+
// The shell is the one page entitled to it, and the worker can tell: the shell
|
|
10
|
+
// is served at /proxy.html, while sites are always under /browse/. Parsing the
|
|
11
|
+
// URL rather than matching on the string is what makes /browse/3000/proxy.html
|
|
12
|
+
// and /browse/3000/../proxy.html answer correctly.
|
|
13
|
+
//
|
|
14
|
+
// Loaded by sw-site.js through importScripts, so this is a classic script and
|
|
15
|
+
// defines a global rather than exporting. The test reads and evaluates it.
|
|
16
|
+
function canBeBridge(clientUrl, expectedOrigin) {
|
|
17
|
+
if (typeof clientUrl !== "string" || !clientUrl) return false;
|
|
18
|
+
let url;
|
|
19
|
+
try {
|
|
20
|
+
url = new URL(clientUrl);
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (url.origin !== expectedOrigin) return false;
|
|
25
|
+
// pathname is already normalised by the URL parser, so ../ is resolved and a
|
|
26
|
+
// query or fragment cannot smuggle the shell's name into it.
|
|
27
|
+
return url.pathname === "/proxy.html";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// The app origins that may sit above a browsed site. frame-ancestors is checked
|
|
31
|
+
// against EVERY ancestor, not just the parent, so a site framed by the shell is
|
|
32
|
+
// also framed by the app — naming only the shell blocks the load.
|
|
33
|
+
//
|
|
34
|
+
// The loopback entry is the agent's own deploy: there the app and the sites host
|
|
35
|
+
// are two names on one server, and the app answers on any loopback name, so all
|
|
36
|
+
// of them have to be listed. A single fixed name leaves the site unframable for
|
|
37
|
+
// whichever spelling the app was not opened on.
|
|
38
|
+
const APP_HOSTS_BY_SITES_HOST = {
|
|
39
|
+
"sites.9remote.cc": ["https://9remote.cc"],
|
|
40
|
+
"sites-dev.9remote.cc": ["https://dev.9remote.cc"],
|
|
41
|
+
// No [::1]: CSP's frame-ancestors rejects an IPv6 literal as a source
|
|
42
|
+
// expression — Chrome drops the whole directive over it — so a page opened on
|
|
43
|
+
// that spelling cannot be named, and is left unsupported rather than silently
|
|
44
|
+
// unframable. localhost and 127.0.0.1 are what the agent is actually reached on.
|
|
45
|
+
"sites.localhost": ["http://localhost", "http://127.0.0.1"],
|
|
46
|
+
"sites.127.0.0.1": ["http://127.0.0.1"]
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// "" for an unknown host: the policy then names the shell alone, which is the
|
|
50
|
+
// safe direction — a load fails rather than an unknown origin being allowed.
|
|
51
|
+
// `port` is the sites origin's own port, and the app answers on the same one.
|
|
52
|
+
function appOriginsFor(hostname, port) {
|
|
53
|
+
const host = String(hostname || "").toLowerCase();
|
|
54
|
+
const bases = APP_HOSTS_BY_SITES_HOST[host];
|
|
55
|
+
if (!bases) return "";
|
|
56
|
+
return bases.map((b) => (port ? `${b}:${port}` : b)).join(" ");
|
|
57
|
+
}
|