initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
config/
|
||||
__pycache__/
|
||||
18
Dockerfile
Normal file
18
Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
FROM python:3.13-alpine
|
||||
|
||||
# Install build dependencies for cryptography
|
||||
RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev
|
||||
|
||||
# Upgrade pip and install application dependencies
|
||||
RUN pip install --upgrade pip \
|
||||
&& pip install --no-cache-dir "flet>=0.28.3,<0.29.0" "rns>=0.9.6,<0.10.0" "pydantic>=1.10,<2.0"
|
||||
|
||||
# Copy application source
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
|
||||
# Expose the web port
|
||||
EXPOSE 8550
|
||||
|
||||
# Run the web version of Ren Browser
|
||||
CMD ["python3", "-u", "-m", "ren_browser.app", "--web", "--port", "8550"]
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Sudo-Ivan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
50
README.md
Normal file
50
README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Ren Browser
|
||||
|
||||
A browser for the Reticulum Network. Work-in-progress.
|
||||
|
||||
## Development
|
||||
|
||||
**Requirements**
|
||||
|
||||
- Python 3.13+
|
||||
- Flet
|
||||
- Reticulum 0.9.6+
|
||||
|
||||
**Setup**
|
||||
|
||||
```bash
|
||||
poetry install
|
||||
```
|
||||
|
||||
### Desktop
|
||||
|
||||
```bash
|
||||
poetry run ren-browser-dev
|
||||
```
|
||||
|
||||
### Web
|
||||
|
||||
```bash
|
||||
poetry run ren-browser-web-dev
|
||||
```
|
||||
|
||||
### Mobile
|
||||
|
||||
**Android**
|
||||
|
||||
```bash
|
||||
poetry run ren-browser-android-dev
|
||||
```
|
||||
|
||||
**iOS**
|
||||
|
||||
```bash
|
||||
poetry run ren-browser-ios-dev
|
||||
```
|
||||
|
||||
### Docker/Podman
|
||||
|
||||
```bash
|
||||
docker build -t ren-browser .
|
||||
docker run -p 8550:8550 ren-browser
|
||||
```
|
||||
411
poetry.lock
generated
Normal file
411
poetry.lock
generated
Normal file
@@ -0,0 +1,411 @@
|
||||
# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.9.0"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
markers = "platform_system != \"Pyodide\""
|
||||
files = [
|
||||
{file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"},
|
||||
{file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
idna = ">=2.8"
|
||||
sniffio = ">=1.1"
|
||||
|
||||
[package.extras]
|
||||
doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
|
||||
test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""]
|
||||
trio = ["trio (>=0.26.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.4.26"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
markers = "platform_system != \"Pyodide\""
|
||||
files = [
|
||||
{file = "certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3"},
|
||||
{file = "certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "1.17.1"
|
||||
description = "Foreign Function Interface for Python calling C code."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "platform_python_implementation != \"PyPy\""
|
||||
files = [
|
||||
{file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"},
|
||||
{file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pycparser = "*"
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "45.0.3"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
optional = false
|
||||
python-versions = "!=3.9.0,!=3.9.1,>=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "cryptography-45.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:7573d9eebaeceeb55285205dbbb8753ac1e962af3d9640791d12b36864065e71"},
|
||||
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d377dde61c5d67eb4311eace661c3efda46c62113ff56bf05e2d679e02aebb5b"},
|
||||
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fae1e637f527750811588e4582988932c222f8251f7b7ea93739acb624e1487f"},
|
||||
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ca932e11218bcc9ef812aa497cdf669484870ecbcf2d99b765d6c27a86000942"},
|
||||
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af3f92b1dc25621f5fad065288a44ac790c5798e986a34d393ab27d2b27fcff9"},
|
||||
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f8f8f0b73b885ddd7f3d8c2b2234a7d3ba49002b0223f58cfde1bedd9563c56"},
|
||||
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9cc80ce69032ffa528b5e16d217fa4d8d4bb7d6ba8659c1b4d74a1b0f4235fca"},
|
||||
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c824c9281cb628015bfc3c59335163d4ca0540d49de4582d6c2637312907e4b1"},
|
||||
{file = "cryptography-45.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5833bb4355cb377ebd880457663a972cd044e7f49585aee39245c0d592904578"},
|
||||
{file = "cryptography-45.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bb5bf55dcb69f7067d80354d0a348368da907345a2c448b0babc4215ccd3497"},
|
||||
{file = "cryptography-45.0.3-cp311-abi3-win32.whl", hash = "sha256:3ad69eeb92a9de9421e1f6685e85a10fbcfb75c833b42cc9bc2ba9fb00da4710"},
|
||||
{file = "cryptography-45.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:97787952246a77d77934d41b62fb1b6f3581d83f71b44796a4158d93b8f5c490"},
|
||||
{file = "cryptography-45.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:c92519d242703b675ccefd0f0562eb45e74d438e001f8ab52d628e885751fb06"},
|
||||
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5edcb90da1843df85292ef3a313513766a78fbbb83f584a5a58fb001a5a9d57"},
|
||||
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38deed72285c7ed699864f964a3f4cf11ab3fb38e8d39cfcd96710cd2b5bb716"},
|
||||
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5555365a50efe1f486eed6ac7062c33b97ccef409f5970a0b6f205a7cfab59c8"},
|
||||
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9e4253ed8f5948a3589b3caee7ad9a5bf218ffd16869c516535325fece163dcc"},
|
||||
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cfd84777b4b6684955ce86156cfb5e08d75e80dc2585e10d69e47f014f0a5342"},
|
||||
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:a2b56de3417fd5f48773ad8e91abaa700b678dc7fe1e0c757e1ae340779acf7b"},
|
||||
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:57a6500d459e8035e813bd8b51b671977fb149a8c95ed814989da682314d0782"},
|
||||
{file = "cryptography-45.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f22af3c78abfbc7cbcdf2c55d23c3e022e1a462ee2481011d518c7fb9c9f3d65"},
|
||||
{file = "cryptography-45.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:232954730c362638544758a8160c4ee1b832dc011d2c41a306ad8f7cccc5bb0b"},
|
||||
{file = "cryptography-45.0.3-cp37-abi3-win32.whl", hash = "sha256:cb6ab89421bc90e0422aca911c69044c2912fc3debb19bb3c1bfe28ee3dff6ab"},
|
||||
{file = "cryptography-45.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:d54ae41e6bd70ea23707843021c778f151ca258081586f0cfa31d936ae43d1b2"},
|
||||
{file = "cryptography-45.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed43d396f42028c1f47b5fec012e9e12631266e3825e95c00e3cf94d472dac49"},
|
||||
{file = "cryptography-45.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fed5aaca1750e46db870874c9c273cd5182a9e9deb16f06f7bdffdb5c2bde4b9"},
|
||||
{file = "cryptography-45.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:00094838ecc7c6594171e8c8a9166124c1197b074cfca23645cee573910d76bc"},
|
||||
{file = "cryptography-45.0.3-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:92d5f428c1a0439b2040435a1d6bc1b26ebf0af88b093c3628913dd464d13fa1"},
|
||||
{file = "cryptography-45.0.3-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:ec64ee375b5aaa354b2b273c921144a660a511f9df8785e6d1c942967106438e"},
|
||||
{file = "cryptography-45.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:71320fbefd05454ef2d457c481ba9a5b0e540f3753354fff6f780927c25d19b0"},
|
||||
{file = "cryptography-45.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:edd6d51869beb7f0d472e902ef231a9b7689508e83880ea16ca3311a00bf5ce7"},
|
||||
{file = "cryptography-45.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:555e5e2d3a53b4fabeca32835878b2818b3f23966a4efb0d566689777c5a12c8"},
|
||||
{file = "cryptography-45.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:25286aacb947286620a31f78f2ed1a32cded7be5d8b729ba3fb2c988457639e4"},
|
||||
{file = "cryptography-45.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:050ce5209d5072472971e6efbfc8ec5a8f9a841de5a4db0ebd9c2e392cb81972"},
|
||||
{file = "cryptography-45.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:dc10ec1e9f21f33420cc05214989544727e776286c1c16697178978327b95c9c"},
|
||||
{file = "cryptography-45.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:9eda14f049d7f09c2e8fb411dda17dd6b16a3c76a1de5e249188a32aeb92de19"},
|
||||
{file = "cryptography-45.0.3.tar.gz", hash = "sha256:ec21313dd335c51d7877baf2972569f40a4291b76a0ce51391523ae358d05899"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cffi = {version = ">=1.14", markers = "platform_python_implementation != \"PyPy\""}
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs ; python_full_version >= \"3.8.0\"", "sphinx-rtd-theme (>=3.0.0) ; python_full_version >= \"3.8.0\""]
|
||||
docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"]
|
||||
nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_full_version >= \"3.8.0\""]
|
||||
pep8test = ["check-sdist ; python_full_version >= \"3.8.0\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"]
|
||||
sdist = ["build (>=1.0.0)"]
|
||||
ssh = ["bcrypt (>=3.1.5)"]
|
||||
test = ["certifi (>=2024)", "cryptography-vectors (==45.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
|
||||
test-randomorder = ["pytest-randomly"]
|
||||
|
||||
[[package]]
|
||||
name = "flet"
|
||||
version = "0.28.3"
|
||||
description = "Flet for Python - easily build interactive multi-platform apps in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "flet-0.28.3-py3-none-any.whl", hash = "sha256:649bfc4af7933956ecf44963df6c0d997bff9ceeaf89d3c86d96803840cab83e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
httpx = {version = "*", markers = "platform_system != \"Pyodide\""}
|
||||
oauthlib = {version = ">=3.2.2,<4.0.0", markers = "platform_system != \"Pyodide\""}
|
||||
repath = ">=0.9.0,<0.10.0"
|
||||
|
||||
[package.extras]
|
||||
all = ["flet-cli (==0.28.3)", "flet-desktop (==0.28.3) ; platform_system == \"Darwin\" or platform_system == \"Windows\"", "flet-desktop-light (==0.28.3) ; platform_system == \"Linux\"", "flet-web (==0.28.3)"]
|
||||
cli = ["flet-cli (==0.28.3)"]
|
||||
desktop = ["flet-desktop (==0.28.3) ; platform_system == \"Darwin\" or platform_system == \"Windows\"", "flet-desktop-light (==0.28.3) ; platform_system == \"Linux\""]
|
||||
web = ["flet-web (==0.28.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "platform_system != \"Pyodide\""
|
||||
files = [
|
||||
{file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"},
|
||||
{file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
description = "A minimal low-level HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "platform_system != \"Pyodide\""
|
||||
files = [
|
||||
{file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"},
|
||||
{file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
certifi = "*"
|
||||
h11 = ">=0.16"
|
||||
|
||||
[package.extras]
|
||||
asyncio = ["anyio (>=4.0,<5.0)"]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (==1.*)"]
|
||||
trio = ["trio (>=0.22.0,<1.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
description = "The next generation HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "platform_system != \"Pyodide\""
|
||||
files = [
|
||||
{file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
|
||||
{file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
anyio = "*"
|
||||
certifi = "*"
|
||||
httpcore = "==1.*"
|
||||
idna = "*"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
|
||||
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (==1.*)"]
|
||||
zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
markers = "platform_system != \"Pyodide\""
|
||||
files = [
|
||||
{file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
|
||||
{file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "oauthlib"
|
||||
version = "3.2.2"
|
||||
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
markers = "platform_system != \"Pyodide\""
|
||||
files = [
|
||||
{file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"},
|
||||
{file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
rsa = ["cryptography (>=3.0.0)"]
|
||||
signals = ["blinker (>=1.4.0)"]
|
||||
signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.22"
|
||||
description = "C parser in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "platform_python_implementation != \"PyPy\""
|
||||
files = [
|
||||
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
|
||||
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyserial"
|
||||
version = "3.5"
|
||||
description = "Python Serial Port Extension"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"},
|
||||
{file = "pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
cp2110 = ["hidapi"]
|
||||
|
||||
[[package]]
|
||||
name = "repath"
|
||||
version = "0.9.0"
|
||||
description = "Generate regular expressions form ExpressJS path patterns"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "repath-0.9.0-py3-none-any.whl", hash = "sha256:ee079d6c91faeb843274d22d8f786094ee01316ecfe293a1eb6546312bb6a318"},
|
||||
{file = "repath-0.9.0.tar.gz", hash = "sha256:8292139bac6a0e43fd9d70605d4e8daeb25d46672e484ed31a24c7ce0aef0fb7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.9.0"
|
||||
|
||||
[[package]]
|
||||
name = "rns"
|
||||
version = "0.9.6"
|
||||
description = "Self-configuring, encrypted and resilient mesh networking stack for LoRa, packet radio, WiFi and everything in between"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "rns-0.9.6-py3-none-any.whl", hash = "sha256:a23c64a04c1e83fd0ab449f564ac904da7fd4f61c0faf68a063f486cc48b44bd"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cryptography = ">=3.4.7"
|
||||
pyserial = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.11.11"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "ruff-0.11.11-py3-none-linux_armv6l.whl", hash = "sha256:9924e5ae54125ed8958a4f7de320dab7380f6e9fa3195e3dc3b137c6842a0092"},
|
||||
{file = "ruff-0.11.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c8a93276393d91e952f790148eb226658dd275cddfde96c6ca304873f11d2ae4"},
|
||||
{file = "ruff-0.11.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d6e333dbe2e6ae84cdedefa943dfd6434753ad321764fd937eef9d6b62022bcd"},
|
||||
{file = "ruff-0.11.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7885d9a5e4c77b24e8c88aba8c80be9255fa22ab326019dac2356cff42089fc6"},
|
||||
{file = "ruff-0.11.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b5ab797fcc09121ed82e9b12b6f27e34859e4227080a42d090881be888755d4"},
|
||||
{file = "ruff-0.11.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e231ff3132c1119ece836487a02785f099a43992b95c2f62847d29bace3c75ac"},
|
||||
{file = "ruff-0.11.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a97c9babe1d4081037a90289986925726b802d180cca784ac8da2bbbc335f709"},
|
||||
{file = "ruff-0.11.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8c4ddcbe8a19f59f57fd814b8b117d4fcea9bee7c0492e6cf5fdc22cfa563c8"},
|
||||
{file = "ruff-0.11.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6224076c344a7694c6fbbb70d4f2a7b730f6d47d2a9dc1e7f9d9bb583faf390b"},
|
||||
{file = "ruff-0.11.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:882821fcdf7ae8db7a951df1903d9cb032bbe838852e5fc3c2b6c3ab54e39875"},
|
||||
{file = "ruff-0.11.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:dcec2d50756463d9df075a26a85a6affbc1b0148873da3997286caf1ce03cae1"},
|
||||
{file = "ruff-0.11.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:99c28505ecbaeb6594701a74e395b187ee083ee26478c1a795d35084d53ebd81"},
|
||||
{file = "ruff-0.11.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9263f9e5aa4ff1dec765e99810f1cc53f0c868c5329b69f13845f699fe74f639"},
|
||||
{file = "ruff-0.11.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:64ac6f885e3ecb2fdbb71de2701d4e34526651f1e8503af8fb30d4915a3fe345"},
|
||||
{file = "ruff-0.11.11-py3-none-win32.whl", hash = "sha256:1adcb9a18802268aaa891ffb67b1c94cd70578f126637118e8099b8e4adcf112"},
|
||||
{file = "ruff-0.11.11-py3-none-win_amd64.whl", hash = "sha256:748b4bb245f11e91a04a4ff0f96e386711df0a30412b9fe0c74d5bdc0e4a531f"},
|
||||
{file = "ruff-0.11.11-py3-none-win_arm64.whl", hash = "sha256:6c51f136c0364ab1b774767aa8b86331bd8e9d414e2d107db7a2189f35ea1f7b"},
|
||||
{file = "ruff-0.11.11.tar.gz", hash = "sha256:7774173cc7c1980e6bf67569ebb7085989a78a103922fb83ef3dfe230cd0687d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
|
||||
{file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
description = "Sniff out which async library your code is running under"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
markers = "platform_system != \"Pyodide\""
|
||||
files = [
|
||||
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
|
||||
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
|
||||
]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.13"
|
||||
content-hash = "7c33d5fc8c448ce0080a3dd31c3e54ef6b559cad67354012ffb822867c21fbda"
|
||||
31
pyproject.toml
Normal file
31
pyproject.toml
Normal file
@@ -0,0 +1,31 @@
|
||||
[project]
|
||||
name = "ren-browser"
|
||||
version = "0.1.0"
|
||||
description = "A browser for the Reticulum Network."
|
||||
authors = [
|
||||
{name = "Sudo-Ivan"}
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"flet (>=0.28.3,<0.29.0)",
|
||||
"rns (>=0.9.6,<0.10.0)"
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[project.scripts]
|
||||
ren-browser = "ren_browser.app:run"
|
||||
ren-browser-web = "ren_browser.app:web"
|
||||
ren-browser-android = "ren_browser.app:android"
|
||||
ren-browser-ios = "ren_browser.app:ios"
|
||||
ren-browser-dev = "ren_browser.app:run_dev"
|
||||
ren-browser-web-dev = "ren_browser.app:web_dev"
|
||||
ren-browser-android-dev = "ren_browser.app:android_dev"
|
||||
ren-browser-ios-dev = "ren_browser.app:ios_dev"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
ruff = "^0.11.11"
|
||||
|
||||
53
ren_browser/announces/announces.py
Normal file
53
ren_browser/announces/announces.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import RNS
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, List
|
||||
|
||||
@dataclass
|
||||
class Announce:
|
||||
destination_hash: str
|
||||
display_name: Optional[str]
|
||||
timestamp: int
|
||||
|
||||
class AnnounceService:
|
||||
"""
|
||||
Service to listen for Reticulum announces and collect them.
|
||||
Calls update_callback whenever a new announce is received.
|
||||
"""
|
||||
def __init__(self, update_callback):
|
||||
# Accept all announce aspects
|
||||
self.aspect_filter = "nomadnetwork.node"
|
||||
self.receive_path_responses = True
|
||||
self.announces: List[Announce] = []
|
||||
self.update_callback = update_callback
|
||||
# Initialize Reticulum transport once
|
||||
try:
|
||||
RNS.Reticulum()
|
||||
except OSError:
|
||||
# Already initialized
|
||||
pass
|
||||
# Register self as announce handler
|
||||
RNS.Transport.register_announce_handler(self)
|
||||
|
||||
def received_announce(self, destination_hash, announced_identity, app_data):
|
||||
# Called by RNS when an announce is received
|
||||
ts = int(time.time())
|
||||
display_name = None
|
||||
if app_data:
|
||||
try:
|
||||
display_name = app_data.decode("utf-8")
|
||||
except:
|
||||
pass
|
||||
announce = Announce(destination_hash.hex(), display_name, ts)
|
||||
# Deduplicate and move announce to top
|
||||
# Remove any existing announces with same destination_hash
|
||||
self.announces = [ann for ann in self.announces if ann.destination_hash != announce.destination_hash]
|
||||
# Insert new announce at front of list
|
||||
self.announces.insert(0, announce)
|
||||
# Notify UI of new announce
|
||||
if self.update_callback:
|
||||
self.update_callback(self.announces)
|
||||
|
||||
def get_announces(self) -> List[Announce]:
|
||||
"""Return collected announces."""
|
||||
return self.announces
|
||||
71
ren_browser/app.py
Normal file
71
ren_browser/app.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import flet as ft
|
||||
from flet import Page, AppView
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from ren_browser.ui.ui import build_ui
|
||||
|
||||
# Current renderer name
|
||||
RENDERER = "plaintext"
|
||||
|
||||
async def main(page: Page):
|
||||
# Build the main UI layout
|
||||
build_ui(page)
|
||||
|
||||
def run():
|
||||
global RENDERER
|
||||
parser = argparse.ArgumentParser(description="Ren Browser")
|
||||
parser.add_argument("-r", "--renderer", choices=["plaintext", "micron"], default=RENDERER, help="Select renderer (plaintext or micron)")
|
||||
parser.add_argument("-w", "--web", action="store_true", help="Launch in web browser mode")
|
||||
parser.add_argument("-p", "--port", type=int, default=None, help="Port for web server")
|
||||
args = parser.parse_args()
|
||||
RENDERER = args.renderer
|
||||
|
||||
if args.web:
|
||||
# Run web mode on optional fixed port
|
||||
if args.port is not None:
|
||||
ft.app(main, view=AppView.WEB_BROWSER, port=args.port)
|
||||
else:
|
||||
ft.app(main, view=AppView.WEB_BROWSER)
|
||||
else:
|
||||
ft.app(main)
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
|
||||
def web():
|
||||
"""Launch Ren Browser in web mode via Flet CLI."""
|
||||
rc = subprocess.call(["flet", "run", "ren_browser/app.py", "--web"])
|
||||
sys.exit(rc)
|
||||
|
||||
def android():
|
||||
"""Launch Ren Browser in Android mode via Flet CLI."""
|
||||
rc = subprocess.call(["flet", "run", "ren_browser/app.py", "--android"])
|
||||
sys.exit(rc)
|
||||
|
||||
def ios():
|
||||
"""Launch Ren Browser in iOS mode via Flet CLI."""
|
||||
rc = subprocess.call(["flet", "run", "ren_browser/app.py", "--ios"])
|
||||
sys.exit(rc)
|
||||
|
||||
# Hot reload (dev) mode entrypoints
|
||||
|
||||
def run_dev():
|
||||
"""Launch Ren Browser in desktop mode via Flet CLI with hot reload."""
|
||||
rc = subprocess.call(["flet", "run", "-d", "-r", "ren_browser/app.py"])
|
||||
sys.exit(rc)
|
||||
|
||||
def web_dev():
|
||||
"""Launch Ren Browser in web mode via Flet CLI with hot reload."""
|
||||
rc = subprocess.call(["flet", "run", "--web", "-d", "-r", "ren_browser/app.py"])
|
||||
sys.exit(rc)
|
||||
|
||||
def android_dev():
|
||||
"""Launch Ren Browser in Android mode via Flet CLI with hot reload."""
|
||||
rc = subprocess.call(["flet", "run", "--android", "-d", "-r", "ren_browser/app.py"])
|
||||
sys.exit(rc)
|
||||
|
||||
def ios_dev():
|
||||
"""Launch Ren Browser in iOS mode via Flet CLI with hot reload."""
|
||||
rc = subprocess.call(["flet", "run", "--ios", "-d", "-r", "ren_browser/app.py"])
|
||||
sys.exit(rc)
|
||||
74
ren_browser/pages/page_request.py
Normal file
74
ren_browser/pages/page_request.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from typing import Optional, Dict
|
||||
from pydantic import BaseModel
|
||||
import os, time, threading
|
||||
import RNS
|
||||
|
||||
class PageRequest(BaseModel):
|
||||
destination_hash: str
|
||||
page_path: str
|
||||
field_data: Optional[Dict] = None
|
||||
|
||||
class PageFetcher:
|
||||
"""
|
||||
Fetcher to download pages from the Reticulum network.
|
||||
"""
|
||||
def __init__(self):
|
||||
# Initialize Reticulum with default config (singleton)
|
||||
try:
|
||||
RNS.Reticulum()
|
||||
except OSError:
|
||||
# Already initialized
|
||||
pass
|
||||
|
||||
def fetch_page(self, req: PageRequest) -> str:
|
||||
"""
|
||||
Download page content for the given PageRequest.
|
||||
Placeholder implementation: replace with real network logic.
|
||||
"""
|
||||
# Establish path and identity
|
||||
dest_bytes = bytes.fromhex(req.destination_hash)
|
||||
# Request path if needed, with timeout
|
||||
if not RNS.Transport.has_path(dest_bytes):
|
||||
RNS.Transport.request_path(dest_bytes)
|
||||
start = time.time()
|
||||
# Wait up to 30 seconds for path discovery
|
||||
while not RNS.Transport.has_path(dest_bytes):
|
||||
if time.time() - start > 30:
|
||||
raise Exception(f"No path to destination {req.destination_hash}")
|
||||
time.sleep(0.1)
|
||||
# Recall identity
|
||||
identity = RNS.Identity.recall(dest_bytes)
|
||||
if not identity:
|
||||
raise Exception('Identity not found')
|
||||
# Create client destination and announce so the server learns our path
|
||||
destination = RNS.Destination(
|
||||
identity,
|
||||
RNS.Destination.OUT,
|
||||
RNS.Destination.SINGLE,
|
||||
'nomadnetwork',
|
||||
'node',
|
||||
)
|
||||
link = RNS.Link(destination)
|
||||
|
||||
# Prepare sync fetch
|
||||
result = {'data': None}
|
||||
ev = threading.Event()
|
||||
|
||||
def on_response(receipt):
|
||||
data = receipt.response
|
||||
if isinstance(data, bytes):
|
||||
result['data'] = data.decode('utf-8')
|
||||
else:
|
||||
result['data'] = str(data)
|
||||
ev.set()
|
||||
|
||||
def on_failed(_):
|
||||
ev.set()
|
||||
|
||||
# Set up request on link establishment
|
||||
link.set_link_established_callback(
|
||||
lambda l: l.request(req.page_path, req.field_data, response_callback=on_response, failed_callback=on_failed)
|
||||
)
|
||||
# Wait for response or timeout
|
||||
ev.wait(timeout=15)
|
||||
return result['data'] or 'No content received'
|
||||
1
ren_browser/profiler/profiler.py
Normal file
1
ren_browser/profiler/profiler.py
Normal file
@@ -0,0 +1 @@
|
||||
# Add a profiler to the browser.
|
||||
0
ren_browser/renderer/__init__.py
Normal file
0
ren_browser/renderer/__init__.py
Normal file
0
ren_browser/renderer/micron/__init__.py
Normal file
0
ren_browser/renderer/micron/__init__.py
Normal file
13
ren_browser/renderer/micron/micron.py
Normal file
13
ren_browser/renderer/micron/micron.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import flet as ft
|
||||
|
||||
def render_micron(content: str) -> ft.Control:
|
||||
"""
|
||||
Render micron markup content to a Flet control placeholder.
|
||||
Currently displays raw content.
|
||||
"""
|
||||
return ft.Text(
|
||||
content,
|
||||
selectable=True,
|
||||
font_family="monospace",
|
||||
expand=True,
|
||||
)
|
||||
0
ren_browser/renderer/plaintext/__init__.py
Normal file
0
ren_browser/renderer/plaintext/__init__.py
Normal file
14
ren_browser/renderer/plaintext/plaintext.py
Normal file
14
ren_browser/renderer/plaintext/plaintext.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import flet as ft
|
||||
|
||||
|
||||
def render_plaintext(content: str) -> ft.Control:
|
||||
"""
|
||||
Fallback plaintext renderer: displays raw text safely in a monospace, selectable control.
|
||||
"""
|
||||
# Use monospace font and make text selectable
|
||||
return ft.Text(
|
||||
content,
|
||||
selectable=True,
|
||||
font_family="monospace",
|
||||
expand=True,
|
||||
)
|
||||
1
ren_browser/storage/storage.py
Normal file
1
ren_browser/storage/storage.py
Normal file
@@ -0,0 +1 @@
|
||||
# Add storage system/management, eg handling downloading files, saving bookmarks, caching, tabs and history.
|
||||
114
ren_browser/tabs/tabs.py
Normal file
114
ren_browser/tabs/tabs.py
Normal file
@@ -0,0 +1,114 @@
|
||||
import flet as ft
|
||||
from types import SimpleNamespace
|
||||
from ren_browser.renderer.plaintext.plaintext import render_plaintext
|
||||
from ren_browser.renderer.micron.micron import render_micron
|
||||
|
||||
class TabsManager:
|
||||
def __init__(self, page: ft.Page):
|
||||
import ren_browser.app as app_module
|
||||
self.page = page
|
||||
# State: list of tabs and current index
|
||||
self.manager = SimpleNamespace(tabs=[], index=0)
|
||||
# UI components
|
||||
self.tab_bar = ft.Row(spacing=4)
|
||||
self.content_container = ft.Container(expand=True)
|
||||
|
||||
# Initialize with default "Home" tab only, using selected renderer
|
||||
default_content = render_micron("Welcome to Ren Browser") if app_module.RENDERER == "micron" else render_plaintext("Welcome to Ren Browser")
|
||||
self._add_tab_internal("Home", default_content)
|
||||
# Action buttons
|
||||
self.add_btn = ft.IconButton(ft.Icons.ADD, tooltip="New Tab", on_click=self._on_add_click)
|
||||
self.close_btn = ft.IconButton(ft.Icons.CLOSE, tooltip="Close Tab", on_click=self._on_close_click)
|
||||
# Append add and close buttons
|
||||
self.tab_bar.controls.extend([self.add_btn, self.close_btn])
|
||||
# Select the first tab
|
||||
self.select_tab(0)
|
||||
|
||||
def _add_tab_internal(self, title: str, content: ft.Control):
|
||||
idx = len(self.manager.tabs)
|
||||
# Create per-tab URL bar and GO button
|
||||
url_field = ft.TextField(label="URL", value=title, expand=True)
|
||||
go_btn = ft.IconButton(ft.Icons.OPEN_IN_BROWSER, tooltip="Load URL", on_click=lambda e, i=idx: self._on_tab_go(e, i))
|
||||
# Wrap the content in a Column: URL bar + initial content
|
||||
content_control = content
|
||||
tab_content = ft.Column(
|
||||
expand=True,
|
||||
controls=[
|
||||
ft.Row([url_field, go_btn]),
|
||||
content_control,
|
||||
],
|
||||
)
|
||||
# Store tab data
|
||||
self.manager.tabs.append({
|
||||
"title": title,
|
||||
"url_field": url_field,
|
||||
"content_control": content_control,
|
||||
"content": tab_content,
|
||||
})
|
||||
# Create stylable tab button container
|
||||
btn = ft.Container(
|
||||
content=ft.Text(title),
|
||||
on_click=lambda e, i=idx: self.select_tab(i),
|
||||
padding=ft.padding.symmetric(horizontal=12, vertical=6),
|
||||
border_radius=5,
|
||||
bgcolor=ft.Colors.SURFACE_CONTAINER_HIGHEST,
|
||||
)
|
||||
# Insert before the add and close buttons
|
||||
insert_pos = max(0, len(self.tab_bar.controls) - 2)
|
||||
self.tab_bar.controls.insert(insert_pos, btn)
|
||||
|
||||
def _on_add_click(self, e):
|
||||
title = f"Tab {len(self.manager.tabs) + 1}"
|
||||
# Render new tab content based on selected renderer
|
||||
content_text = f"Content for {title}"
|
||||
import ren_browser.app as app_module
|
||||
content = render_micron(content_text) if app_module.RENDERER == "micron" else render_plaintext(content_text)
|
||||
self._add_tab_internal(title, content)
|
||||
# Select the new tab
|
||||
self.select_tab(len(self.manager.tabs) - 1)
|
||||
self.page.update()
|
||||
|
||||
def _on_close_click(self, e):
|
||||
# Do not allow closing all tabs
|
||||
if len(self.manager.tabs) <= 1:
|
||||
return
|
||||
idx = self.manager.index
|
||||
# Remove tab data and button
|
||||
self.manager.tabs.pop(idx)
|
||||
self.tab_bar.controls.pop(idx)
|
||||
# Reassign on_click handlers to correct indices
|
||||
for i, control in enumerate(self.tab_bar.controls[:-2]):
|
||||
control.on_click = lambda e, i=i: self.select_tab(i)
|
||||
# Adjust selected index
|
||||
new_idx = min(idx, len(self.manager.tabs) - 1)
|
||||
self.select_tab(new_idx)
|
||||
self.page.update()
|
||||
|
||||
def select_tab(self, idx: int):
|
||||
self.manager.index = idx
|
||||
# Highlight active tab and dim others
|
||||
for i, control in enumerate(self.tab_bar.controls[:-2]):
|
||||
if i == idx:
|
||||
control.bgcolor = ft.Colors.PRIMARY_CONTAINER
|
||||
else:
|
||||
control.bgcolor = ft.Colors.SURFACE_CONTAINER_HIGHEST
|
||||
# Update displayed content
|
||||
self.content_container.content = self.manager.tabs[idx]["content"]
|
||||
self.page.update()
|
||||
|
||||
def _on_tab_go(self, e, idx: int):
|
||||
"""Handle loading a new URL in a specific tab (placeholder logic)."""
|
||||
tab = self.manager.tabs[idx]
|
||||
url = tab["url_field"].value.strip()
|
||||
if not url:
|
||||
return
|
||||
# Placeholder: update the content_control using selected renderer
|
||||
placeholder_text = f"Loading content for {url}"
|
||||
import ren_browser.app as app_module
|
||||
new_control = render_micron(placeholder_text) if app_module.RENDERER == "micron" else render_plaintext(placeholder_text)
|
||||
tab["content_control"] = new_control
|
||||
tab["content"].controls[1] = new_control
|
||||
# Refresh the displayed content if this tab is active
|
||||
if self.manager.index == idx:
|
||||
self.content_container.content = tab["content"]
|
||||
self.page.update()
|
||||
111
ren_browser/ui/ui.py
Normal file
111
ren_browser/ui/ui.py
Normal file
@@ -0,0 +1,111 @@
|
||||
import flet as ft
|
||||
from flet import Page
|
||||
from ren_browser.tabs.tabs import TabsManager
|
||||
from ren_browser.renderer.plaintext.plaintext import render_plaintext
|
||||
from ren_browser.renderer.micron.micron import render_micron
|
||||
from ren_browser.announces.announces import AnnounceService
|
||||
from ren_browser.pages.page_request import PageFetcher, PageRequest
|
||||
|
||||
|
||||
def build_ui(page: Page):
|
||||
import ren_browser.app as app_module
|
||||
# Page properties
|
||||
page.title = "Ren Browser"
|
||||
page.theme_mode = ft.ThemeMode.DARK
|
||||
page.appbar = ft.AppBar(title=ft.Text("Ren Browser"))
|
||||
page.padding = 20
|
||||
page.window_width = 800
|
||||
page.window_height = 600
|
||||
|
||||
# Initialize page fetcher and announce service
|
||||
page_fetcher = PageFetcher()
|
||||
# Sidebar announces list in a scrollable ListView within a NavigationDrawer
|
||||
announce_list = ft.ListView(expand=True, spacing=1)
|
||||
def update_announces(ann_list):
|
||||
announce_list.controls.clear()
|
||||
for ann in ann_list:
|
||||
label = ann.display_name or ann.destination_hash
|
||||
# Use display_name for tab title, fallback to "Anonymous"; set URL bar to full path
|
||||
def on_click_ann(e, dest=ann.destination_hash, disp=ann.display_name):
|
||||
title = disp or "Anonymous"
|
||||
# Full URL including page path
|
||||
full_url = f"{dest}:/page/index.mu"
|
||||
placeholder = render_plaintext(f"Fetching content for {full_url}")
|
||||
tab_manager._add_tab_internal(title, placeholder)
|
||||
idx = len(tab_manager.manager.tabs) - 1
|
||||
# Set URL bar to full URL
|
||||
tab = tab_manager.manager.tabs[idx]
|
||||
tab["url_field"].value = full_url
|
||||
# Select the new tab and refresh UI
|
||||
tab_manager.select_tab(idx)
|
||||
page.update()
|
||||
def fetch_and_update():
|
||||
req = PageRequest(destination_hash=dest, page_path="/page/index.mu")
|
||||
try:
|
||||
result = page_fetcher.fetch_page(req)
|
||||
except Exception as ex:
|
||||
result = f"Error: {ex}"
|
||||
tab = tab_manager.manager.tabs[idx]
|
||||
# Use micron renderer for .mu pages, fallback to plaintext
|
||||
if req.page_path.endswith(".mu"):
|
||||
new_control = render_micron(result)
|
||||
else:
|
||||
new_control = render_plaintext(result)
|
||||
tab["content_control"] = new_control
|
||||
# Replace the content control in the tab's column
|
||||
tab["content"].controls[1] = new_control
|
||||
if tab_manager.manager.index == idx:
|
||||
tab_manager.content_container.content = tab["content"]
|
||||
page.update()
|
||||
page.run_thread(fetch_and_update)
|
||||
announce_list.controls.append(ft.TextButton(label, on_click=on_click_ann))
|
||||
page.update()
|
||||
AnnounceService(update_callback=update_announces)
|
||||
# Make sidebar collapsible via drawer
|
||||
page.drawer = ft.NavigationDrawer(
|
||||
controls=[
|
||||
ft.Text("Announcements", weight=ft.FontWeight.BOLD),
|
||||
ft.Divider(),
|
||||
announce_list,
|
||||
],
|
||||
)
|
||||
# Add hamburger button to toggle drawer
|
||||
page.appbar.leading = ft.IconButton(
|
||||
ft.Icons.MENU,
|
||||
tooltip="Toggle sidebar",
|
||||
on_click=lambda e: (setattr(page.drawer, 'open', not page.drawer.open), page.update()),
|
||||
)
|
||||
|
||||
# Dynamic tabs manager for pages
|
||||
tab_manager = TabsManager(page)
|
||||
# Main area: tab bar and content
|
||||
main_area = ft.Column(
|
||||
expand=True,
|
||||
controls=[
|
||||
tab_manager.tab_bar,
|
||||
tab_manager.content_container,
|
||||
],
|
||||
)
|
||||
|
||||
# Layout: main content only (sidebar in drawer)
|
||||
layout = ft.Row(expand=True, controls=[main_area])
|
||||
|
||||
# Render main layout with status
|
||||
page.add(
|
||||
ft.Column(
|
||||
expand=True,
|
||||
controls=[
|
||||
layout,
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text(
|
||||
f"Renderer: {app_module.RENDERER}",
|
||||
color=ft.Colors.GREY,
|
||||
size=12,
|
||||
),
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.END,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
Reference in New Issue
Block a user