tina4-python 3.13.107__tar.gz → 3.13.109__tar.gz
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.
- {tina4_python-3.13.107 → tina4_python-3.13.109}/PKG-INFO +3 -4
- {tina4_python-3.13.107 → tina4_python-3.13.109}/README.md +2 -3
- {tina4_python-3.13.107 → tina4_python-3.13.109}/pyproject.toml +1 -1
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/__init__.py +1 -1
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/cache/__init__.py +35 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/database/adapter.py +58 -13
- tina4_python-3.13.109/tina4_python/public/js/tina4js.min.js +48 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/service/__init__.py +10 -0
- tina4_python-3.13.107/tina4_python/public/js/tina4js.min.js +0 -48
- {tina4_python-3.13.107 → tina4_python-3.13.109}/.gitignore +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/ai/client.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/context/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/context/chunker.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/core/port_takeover.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/core/request.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/core/response.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/core/router.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/core/server.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/database/database_url.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/database/sql_translator.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/docstore/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/env.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/frond/compiler.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/frond/parser.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/mqtt/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/mqtt/message.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/orm/point.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/queue/amqp_url.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/realtime/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/realtime/models/Attachment.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/realtime/models/Channel.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/realtime/models/ChannelMember.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/realtime/models/Message.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/realtime/models/Workspace.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/realtime/models/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/realtime/storage.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/session_handlers/memcached_handler.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/sso.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tina4-python
|
|
3
|
-
Version: 3.13.
|
|
3
|
+
Version: 3.13.109
|
|
4
4
|
Summary: Tina4 Python v3 — Zero-dependency, lightweight web framework
|
|
5
5
|
Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -56,13 +56,12 @@ Description-Content-Type: text/markdown
|
|
|
56
56
|
<h1 align="center">Tina4 Python</h1>
|
|
57
57
|
|
|
58
58
|
<p align="center">
|
|
59
|
-
|
|
59
|
+
Zero dependencies. One import, everything works.
|
|
60
60
|
</p>
|
|
61
61
|
|
|
62
62
|
<p align="center">
|
|
63
63
|
<a href="https://pypi.org/project/tina4-python/"><img src="https://img.shields.io/pypi/v/tina4-python?color=7b1fa2&label=PyPI" alt="PyPI"></a>
|
|
64
|
-
<img src="https://img.shields.io/
|
|
65
|
-
<img src="https://img.shields.io/badge/features-98-blue" alt="Features">
|
|
64
|
+
<a href="https://github.com/tina4stack/tina4-python/actions/workflows/test.yml"><img src="https://img.shields.io/github/actions/workflow/status/tina4stack/tina4-python/test.yml?label=tests" alt="Tests"></a>
|
|
66
65
|
<img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero Deps">
|
|
67
66
|
<a href="https://tina4.com"><img src="https://img.shields.io/badge/docs-tina4.com-7b1fa2" alt="Docs"></a>
|
|
68
67
|
</p>
|
|
@@ -5,13 +5,12 @@
|
|
|
5
5
|
<h1 align="center">Tina4 Python</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
|
|
8
|
+
Zero dependencies. One import, everything works.
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
12
|
<a href="https://pypi.org/project/tina4-python/"><img src="https://img.shields.io/pypi/v/tina4-python?color=7b1fa2&label=PyPI" alt="PyPI"></a>
|
|
13
|
-
<img src="https://img.shields.io/
|
|
14
|
-
<img src="https://img.shields.io/badge/features-98-blue" alt="Features">
|
|
13
|
+
<a href="https://github.com/tina4stack/tina4-python/actions/workflows/test.yml"><img src="https://img.shields.io/github/actions/workflow/status/tina4stack/tina4-python/test.yml?label=tests" alt="Tests"></a>
|
|
15
14
|
<img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero Deps">
|
|
16
15
|
<a href="https://tina4.com"><img src="https://img.shields.io/badge/docs-tina4.com-7b1fa2" alt="Docs"></a>
|
|
17
16
|
</p>
|
|
@@ -1190,6 +1190,17 @@ def _vary_fields(response) -> list:
|
|
|
1190
1190
|
return [f.strip().lower() for f in raw.split(",") if f.strip()]
|
|
1191
1191
|
|
|
1192
1192
|
|
|
1193
|
+
def _cache_control_tokens(carrier) -> set:
|
|
1194
|
+
"""The Cache-Control directive names on a request or response, lowercased.
|
|
1195
|
+
|
|
1196
|
+
Parsed as comma-separated tokens with any ``=value`` stripped, rather than by
|
|
1197
|
+
substring search, so ``no-cache="Set-Cookie"`` is recognised as ``no-cache``
|
|
1198
|
+
and a directive name never matches as a fragment of a longer one.
|
|
1199
|
+
"""
|
|
1200
|
+
raw = _header_value(carrier, "Cache-Control") or ""
|
|
1201
|
+
return {token.split("=", 1)[0].strip().lower() for token in raw.split(",") if token.strip()}
|
|
1202
|
+
|
|
1203
|
+
|
|
1193
1204
|
def _shared_cache_allowed(response) -> bool:
|
|
1194
1205
|
"""Does the response carry a directive that lets a shared cache store it?"""
|
|
1195
1206
|
cc = (_header_value(response, "Cache-Control") or "").lower()
|
|
@@ -1373,11 +1384,35 @@ class ResponseCache:
|
|
|
1373
1384
|
URL, because the key is method + URL only.
|
|
1374
1385
|
* s4.1 — "A stored response with a Vary header field value containing a
|
|
1375
1386
|
member '*' always fails to match", so storing one is pointless.
|
|
1387
|
+
|
|
1388
|
+
The same s3 reasoning applies to two cases the Authorization check alone
|
|
1389
|
+
does not cover, and both were storable before 3.13.108:
|
|
1390
|
+
|
|
1391
|
+
* ``no-store`` forbids storage in any cache and ``private`` forbids it in
|
|
1392
|
+
a shared one. Neither was honoured, so a handler had no way at all to
|
|
1393
|
+
keep a response out of this cache — setting the correct standard header
|
|
1394
|
+
did nothing.
|
|
1395
|
+
* A caller identified by a session cookie is as specific as one carrying
|
|
1396
|
+
Authorization, and Tina4's own session mechanism IS a cookie. Because
|
|
1397
|
+
the key is method + URL, storing such a response replays one signed-in
|
|
1398
|
+
user's page to whoever asks for that URL next. A response that installs
|
|
1399
|
+
a session is per-user by construction for the same reason.
|
|
1400
|
+
|
|
1401
|
+
Both cookie cases stay cacheable when the response opts in explicitly with
|
|
1402
|
+
a shared-cache directive, which is how a genuinely public page served to
|
|
1403
|
+
cookie-bearing browsers keeps its hit rate.
|
|
1376
1404
|
"""
|
|
1377
1405
|
if "*" in _vary_fields(response):
|
|
1378
1406
|
return False
|
|
1407
|
+
directives = _cache_control_tokens(response)
|
|
1408
|
+
if directives & {"no-store", "private", "no-cache"}:
|
|
1409
|
+
return False
|
|
1379
1410
|
if _header_value(request, "Authorization") is not None:
|
|
1380
1411
|
return _shared_cache_allowed(response)
|
|
1412
|
+
if _header_value(request, "Cookie") is not None:
|
|
1413
|
+
return _shared_cache_allowed(response)
|
|
1414
|
+
if _header_value(response, "Set-Cookie") is not None:
|
|
1415
|
+
return _shared_cache_allowed(response)
|
|
1381
1416
|
return True
|
|
1382
1417
|
|
|
1383
1418
|
@staticmethod
|
|
@@ -62,16 +62,41 @@ def resolve_connect_timeout() -> float | None:
|
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
def driver_connect_timeout_seconds(seconds: float | None) -> int | None:
|
|
65
|
-
"""A driver's own connect-timeout option
|
|
65
|
+
"""A driver's own connect-timeout option, in whole seconds, STRICTLY longer
|
|
66
|
+
than the bound it protects.
|
|
66
67
|
|
|
67
68
|
Every driver option here (libpq ``connect_timeout``, mysql-connector
|
|
68
69
|
``connection_timeout``, pymssql ``login_timeout``, pyodbc ``timeout``) is
|
|
69
|
-
whole seconds
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
whole seconds, so the bound has to become an integer — and which integer
|
|
71
|
+
decides whether the driver's deadline lands after ours or on top of it.
|
|
72
|
+
``ceil`` put it on top: for a whole-second bound ``ceil(N) == N``, and the
|
|
73
|
+
shipped default of 10 is whole. ``floor(s) + 1`` is strictly greater for
|
|
74
|
+
every input, fractional or whole, which is what leaves the driver's own
|
|
75
|
+
timer the room to fire first — the thing this wrapper is built on. The
|
|
76
|
+
cost is at most one extra second, on a path that has already failed.
|
|
73
77
|
"""
|
|
74
|
-
return None if seconds is None else max(1, math.
|
|
78
|
+
return None if seconds is None else max(1, math.floor(seconds) + 1)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def bound_was_reached(elapsed_monotonic: float, elapsed_realtime: float,
|
|
82
|
+
seconds: float | None) -> bool:
|
|
83
|
+
"""Did a connect that failed take at least the configured bound?
|
|
84
|
+
|
|
85
|
+
Two readings, because the framework and the driver do not share a clock.
|
|
86
|
+
:func:`connect_deadline` times on ``time.monotonic()``; libpq times its own
|
|
87
|
+
``connect_timeout`` on ``gettimeofday()``. NTP moves the wall clock and
|
|
88
|
+
never touches the monotonic one, so a forward step or slew can make the
|
|
89
|
+
driver abort before a monotonic reading has reached the bound.
|
|
90
|
+
|
|
91
|
+
Taking the LARGER of the two readings covers both directions: the realtime
|
|
92
|
+
reading catches a forward jump, and keeping the monotonic reading means a
|
|
93
|
+
BACKWARD jump cannot hide a timeout that really did happen.
|
|
94
|
+
|
|
95
|
+
Pure, so the decision is testable without faking a clock.
|
|
96
|
+
"""
|
|
97
|
+
if seconds is None:
|
|
98
|
+
return False
|
|
99
|
+
return max(elapsed_monotonic, elapsed_realtime) >= seconds
|
|
75
100
|
|
|
76
101
|
|
|
77
102
|
@contextlib.contextmanager
|
|
@@ -105,19 +130,39 @@ def connect_deadline(host, port):
|
|
|
105
130
|
throws away the driver's own diagnosis, and for the watchdog adapters it
|
|
106
131
|
would abandon a thread the driver could have unwound itself.
|
|
107
132
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
133
|
+
THE INVARIANT, AND WHY IT TAKES TWO CLOCKS
|
|
134
|
+
------------------------------------------
|
|
135
|
+
What decides the race is not the driver's OPTION against our bound — it
|
|
136
|
+
is the driver's ABORT INSTANT against our reading of a clock. Two things
|
|
137
|
+
have to hold, and each of them was broken:
|
|
138
|
+
|
|
139
|
+
* The driver's option must be STRICTLY greater than our bound, so its
|
|
140
|
+
deadline lands after ours instead of on top of it.
|
|
141
|
+
:func:`driver_connect_timeout_seconds` returns ``floor(s) + 1`` for that
|
|
142
|
+
reason; ``ceil`` left a whole-second bound with no separation at all, and
|
|
143
|
+
the default bound is a whole number.
|
|
144
|
+
|
|
145
|
+
* The comparison has to be made on the clock the DRIVER used. libpq times
|
|
146
|
+
``connect_timeout`` with ``gettimeofday()`` — CLOCK_REALTIME — while a
|
|
147
|
+
duration in Python belongs on ``time.monotonic()``. NTP slews and steps
|
|
148
|
+
realtime and never touches monotonic, so a monotonic reading can still
|
|
149
|
+
sit below the bound at the instant the driver has already given up, and
|
|
150
|
+
then the driver's own message — which names no tunable — reaches
|
|
151
|
+
the caller. :func:`bound_was_reached` compares both readings.
|
|
152
|
+
|
|
153
|
+
Break either half and a bare driver message reaches the caller instead of
|
|
154
|
+
ours.
|
|
113
155
|
"""
|
|
114
156
|
seconds = resolve_connect_timeout()
|
|
115
157
|
started = time.monotonic()
|
|
158
|
+
started_realtime = time.time()
|
|
116
159
|
try:
|
|
117
160
|
yield seconds
|
|
118
161
|
except Exception as failure:
|
|
119
|
-
|
|
120
|
-
|
|
162
|
+
elapsed_monotonic = time.monotonic() - started
|
|
163
|
+
elapsed_realtime = time.time() - started_realtime
|
|
164
|
+
elapsed = max(elapsed_monotonic, elapsed_realtime)
|
|
165
|
+
if bound_was_reached(elapsed_monotonic, elapsed_realtime, seconds):
|
|
121
166
|
raise DatabaseConnectTimeout(
|
|
122
167
|
f"Database connect to {host}:{port} timed out after {elapsed:.1f}s "
|
|
123
168
|
f"({CONNECT_TIMEOUT_VARIABLE}={seconds:g} seconds). "
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";var Tina4=(()=>{var ee=Object.defineProperty;var Xe=Object.getOwnPropertyDescriptor;var Ye=Object.getOwnPropertyNames;var et=Object.prototype.hasOwnProperty;var tt=(e,n)=>{for(var t in n)ee(e,t,{get:n[t],enumerable:!0})},nt=(e,n,t,r)=>{if(n&&typeof n=="object"||typeof n=="function")for(let o of Ye(n))!et.call(e,o)&&o!==t&&ee(e,o,{get:()=>n[o],enumerable:!(r=Xe(n,o))||r.enumerable});return e};var rt=e=>nt(ee({},"__esModule",{value:!0}),e);var $t={};tt($t,{Tina4Element:()=>N,api:()=>Ae,batch:()=>G,clearPersistedKeys:()=>Ve,computed:()=>ye,createI18n:()=>de,effect:()=>E,html:()=>be,i18n:()=>Je,isSignal:()=>I,navigate:()=>Q,persist:()=>ze,pwa:()=>Ne,route:()=>_e,router:()=>xe,rtc:()=>De,rtcConfig:()=>X,signal:()=>w,sse:()=>Pe,ws:()=>K});var L=null,H=null,U=null,B=null;function O(e){B=e}function J(){return B}var me=null,he=null,ve=[],ot=512;var V=0,te=new Set;function w(e,n){let t=e,r=new Set,o={_t4:!0,get value(){if(L&&(r.add(L),H)){let s=L;H.push(()=>r.delete(s))}return t},set value(s){if(Object.is(s,t))return;let i=t;if(t=s,o._debugInfo&&o._debugInfo.updateCount++,he&&he(o,i,s),V>0)for(let c of r)te.add(c);else{let c;for(let a of[...r])try{a()}catch(l){c===void 0&&(c=l)}if(c!==void 0)throw c}},_subscribe(s){return r.add(s),()=>{r.delete(s)}},peek(){return t}};return me?(o._debugInfo={label:n,createdAt:Date.now(),updateCount:0,subs:r},me(o,n)):ve.length<ot&&ve.push({ref:new WeakRef(o),label:n,createdAt:Date.now(),subs:r}),o}function ye(e){let n=w(void 0);return E(()=>{n.value=e()}),{_t4:!0,get value(){return n.value},set value(t){throw new Error("[tina4] computed signals are read-only")},_subscribe(t){return n._subscribe(t)},peek(){return n.peek()}}}function E(e){let n=!1,t=[],r=[],o=()=>{for(let c of r)c();r=[]},s=()=>{if(n)return;for(let g of t)g();t=[],o();let c=L,a=H,l=U;L=s,H=t,U=r;try{e()}finally{L=c,H=a,U=l}};s();let i=()=>{n=!0;for(let c of t)c();t=[],o()};return U&&U.push(i),B&&B.push(i),i}function G(e){V++;try{e()}finally{if(V--,V===0){let n=[...te];te.clear();let t;for(let r of n)try{r()}catch(o){t===void 0&&(t=o)}if(t!==void 0)throw t}}}function I(e){return e!==null&&typeof e=="object"&&e._t4===!0}var Se=new WeakMap,ne="t4:";function be(e,...n){let t=Se.get(e);if(!t){let i=document.createElement("template"),c=new Map,a="";for(let l=0;l<e.length;l++)if(a+=e[l],l<n.length)if(dt(a)){let m=ut(e[l]);m&&c.set(l,m),a+=`__t4_${l}__`}else a+=`<!--${ne}${l}-->`;i.innerHTML=a,t={template:i,propertyNames:c},Se.set(e,t)}let r=t.template.content.cloneNode(!0),o=st(r);for(let{marker:i,index:c}of o)at(i,n[c]);let s=it(r);for(let i of s)ct(i,n,t.propertyNames);return r}function st(e){let n=[];return se(e,t=>{if(t.nodeType===8){let r=t.data;if(r&&r.startsWith(ne)){let o=parseInt(r.slice(ne.length),10);n.push({marker:t,index:o})}}}),n}function it(e){let n=[];return se(e,t=>{t.nodeType===1&&n.push(t)}),n}function se(e,n){let t=e.childNodes;for(let r=0;r<t.length;r++){let o=t[r];n(o),se(o,n)}}function at(e,n){let t=e.parentNode;if(t)if(I(n)){let r=document.createTextNode("");t.replaceChild(r,e),E(()=>{r.data=String(n.value??"")})}else if(typeof n=="function"){let r=document.createComment("");t.replaceChild(r,e);let o=[],s=[];E(()=>{for(let u of s)u();s=[];let i=[],c=J();O(i);let a=n();O(c),s=i;for(let u of o)u.parentNode?.removeChild(u);o=[];let l=oe(a),g=r.parentNode;if(!g)return;let m=Z(g);for(let u of l){let d=m?D(u,m):u;g.insertBefore(d,r),o.push(d)}})}else if(Te(n)){let r=Z(t);if(r){let o=document.createDocumentFragment();for(let s of Array.from(n.childNodes))o.appendChild(D(s,r));t.replaceChild(o,e)}else t.replaceChild(n,e)}else if(n instanceof Node){let r=Z(t);t.replaceChild(r?D(n,r):n,e)}else if(Array.isArray(n)){let r=Z(t),o=document.createDocumentFragment();for(let s of n){let i=oe(s);for(let c of i)o.appendChild(r?D(c,r):c)}t.replaceChild(o,e)}else{let r=document.createTextNode(String(n??""));t.replaceChild(r,e)}}function ct(e,n,t){let r=[];for(let o of Array.from(e.attributes)){let s=o.name,i=o.value;if(s.startsWith("@")){let a=s.slice(1),l=i.match(/__t4_(\d+)__/);if(l){let g=n[parseInt(l[1],10)];typeof g=="function"&&e.addEventListener(a,m=>G(()=>g(m)))}r.push(s);continue}if(s.startsWith("?")){let a=s.slice(1),l=i.match(/__t4_(\d+)__/);if(l){let g=n[parseInt(l[1],10)];if(I(g)){let m=g;E(()=>{m.value?e.setAttribute(a,""):e.removeAttribute(a)})}else typeof g=="function"?E(()=>{g()?e.setAttribute(a,""):e.removeAttribute(a)}):g&&e.setAttribute(a,"")}r.push(s);continue}if(s.startsWith(".")){let a=i.match(/__t4_(\d+)__/);if(a){let l=parseInt(a[1],10),g=t.get(l)??s.slice(1),m=n[l];I(m)?E(()=>{e[g]=m.value}):typeof m=="function"?E(()=>{e[g]=m()??""}):e[g]=m}r.push(s);continue}let c=i.match(/__t4_(\d+)__/);if(c){let a=n[parseInt(c[1],10)];if(I(a)){let l=a;E(()=>{e.setAttribute(s,String(l.value??""))})}else typeof a=="function"?E(()=>{e.setAttribute(s,String(a()??""))}):e.setAttribute(s,String(a??""))}}for(let o of r)e.removeAttribute(o)}var re="http://www.w3.org/2000/svg",lt="http://www.w3.org/1998/Math/MathML",we="http://www.w3.org/1999/xhtml";function Z(e){let n=e;for(;n&&n.nodeType===1;){let t=n,r=t.namespaceURI;if(r===re&&t.localName==="foreignObject")return null;if(r===re||r===lt)return r;if(r===we)return null;n=n.parentNode}return null}function D(e,n){if(e.nodeType!==1)return e;let t=e;if(t.namespaceURI===n){for(let s of Array.from(t.childNodes)){let i=D(s,n);i!==s&&t.replaceChild(i,s)}return t}let r=document.createElementNS(n,t.localName);for(let s of Array.from(t.attributes))r.setAttribute(s.name,s.value);let o=n===re&&t.localName==="foreignObject"?we:n;for(let s of Array.from(t.childNodes))r.appendChild(D(s,o));return r}function ut(e){return e.match(/\.([^\s"'<>/=]+)\s*=\s*["']?$/)?.[1]}function oe(e){if(e==null||e===!1)return[];if(Te(e))return Array.from(e.childNodes);if(e instanceof Node)return[e];if(Array.isArray(e)){let n=[];for(let t of e)n.push(...oe(t));return n}return[document.createTextNode(String(e))]}function Te(e){return e!=null&&typeof e=="object"&&e.nodeType===11}function dt(e){let n=!1,t=!1,r=!1;for(let o=0;o<e.length;o++){if(!r&&e.startsWith("<!--",o)){let i=e.indexOf("-->",o+4);if(i===-1)return!1;o=i+2;continue}let s=e[o];s==="<"&&!n&&!t&&(r=!0),s===">"&&!n&&!t&&(r=!1),r&&(s==='"'&&!n&&(t=!t),s==="'"&&!t&&(n=!n))}return r}var ke=null,Ce=null;var N=class extends HTMLElement{constructor(){super();this._props={};this._rendered=!1;this._disposeRender=null;this._innerDisposers=[];let t=this.constructor;this._root=t.shadow?this.attachShadow({mode:"open"}):this;for(let[r,o]of Object.entries(t.props))this._props[r]=w(this._coerce(this.getAttribute(r),o))}static get observedAttributes(){return Object.keys(this.props)}connectedCallback(){if(this._rendered)return;this._rendered=!0;let t=this.constructor,r=null;if(t.styles&&t.shadow&&this._root instanceof ShadowRoot){let o=document.createElement("style");o.textContent=t.styles,this._root.appendChild(o),r=o}this._disposeRender=E(()=>{this._innerDisposers.splice(0).forEach(a=>a());let o=[],s=J();O(o);let i=this.render();O(s),this._innerDisposers=o;let c=Array.from(this._root.childNodes);for(let a of c)a!==r&&this._root.removeChild(a);i&&this._root.appendChild(i)}),this.onMount(),ke&&ke(this)}disconnectedCallback(){this._disposeRender&&(this._disposeRender(),this._disposeRender=null),this._innerDisposers.splice(0).forEach(t=>t()),this.onUnmount(),Ce&&Ce(this)}attributeChangedCallback(t,r,o){let i=this.constructor.props[t];i&&this._props[t]&&(this._props[t].value=this._coerce(o,i))}prop(t){if(!this._props[t])throw new Error(`[tina4] Prop '${t}' not declared in static props of <${this.tagName.toLowerCase()}>`);return this._props[t]}emit(t,r){this.dispatchEvent(new CustomEvent(t,{bubbles:!0,composed:!0,...r}))}onMount(){}onUnmount(){}_coerce(t,r){return r===Boolean?t!==null:r===Number?t!==null?Number(t):0:t??""}};N.props={},N.styles="",N.shadow=!0;var ae=[],F=null,j="history",ft=!1,W=[],ie=[],Ee=0;function _e(e,n){let t=[],r;e==="*"?r=".*":r=e.replace(/\{(\w+)\}/g,(s,i)=>(t.push(i),"([^/]+)"));let o=new RegExp(`^${r}$`);typeof n=="function"?ae.push({pattern:e,regex:o,paramNames:t,handler:n}):ae.push({pattern:e,regex:o,paramNames:t,handler:n.handler,guard:n.guard})}function Q(e,n){if(j==="hash")if(n?.replace){let t=new URL(location.href);t.hash="#"+e,history.replaceState(null,"",t.toString()),$()}else{let t=new URL(location.href);t.hash="#"+e,history.pushState(null,"",t.toString()),$()}else n?.replace?history.replaceState(null,"",e):history.pushState(null,"",e),$()}function $(){if(!F)return;let e=performance.now(),n=++Ee,t=j==="hash"?location.hash.slice(1)||"/":location.pathname;for(let r of ae){let o=t.match(r.regex);if(!o)continue;let s={};if(r.paramNames.forEach((a,l)=>{s[a]=decodeURIComponent(o[l+1])}),r.guard){let a=r.guard();if(a===!1)return;if(typeof a=="string"){Q(a,{replace:!0});return}}ie.splice(0).forEach(a=>a()),F.innerHTML="";let i=[];O(i);let c=r.handler(s);if(c instanceof Promise)c.then(a=>{if(O(null),n!==Ee){for(let g of i)g();return}Re(F,a),ie=i;let l=performance.now()-e;for(let g of W)g({path:t,params:s,pattern:r.pattern,durationMs:l})});else{O(null),Re(F,c),ie=i;let a=performance.now()-e;for(let l of W)l({path:t,params:s,pattern:r.pattern,durationMs:a})}return}}function Re(e,n){n instanceof DocumentFragment||n instanceof Node?e.replaceChildren(n):typeof n=="string"?e.innerHTML=n:n!=null&&e.replaceChildren(document.createTextNode(String(n)))}var xe={start(e){if(F=document.querySelector(e.target),!F)throw new Error(`[tina4] Router target '${e.target}' not found in DOM`);j=e.mode??"history",ft=!0,window.addEventListener("popstate",$),j==="hash"&&window.addEventListener("hashchange",$),document.addEventListener("click",n=>{if(n.metaKey||n.ctrlKey||n.shiftKey||n.altKey)return;let t=n.target.closest("a[href]");if(!t||t.origin!==location.origin||t.hasAttribute("target")||t.hasAttribute("download")||t.getAttribute("rel")?.includes("external"))return;n.preventDefault();let r=j==="hash"?t.getAttribute("href"):t.pathname;Q(r)}),$()},on(e,n){return W.push(n),()=>{let t=W.indexOf(n);t>=0&&W.splice(t,1)}}};var _={baseUrl:"",auth:!1,tokenKey:"tina4_token",headers:{}},ce=[],le=[],gt=0;function ue(){try{return localStorage.getItem(_.tokenKey)}catch{return null}}function pt(e){try{localStorage.setItem(_.tokenKey,e)}catch{}}function Me(e,n){let t=Object.entries(n).map(([r,o])=>`${encodeURIComponent(r)}=${encodeURIComponent(String(o))}`).join("&");return e+(e.includes("?")?"&":"?")+t}async function Oe(e,n){e._url=n,e._requestId=++gt;for(let c of ce){let a=c(e);a&&(e=a)}let t=await fetch(n,e),r=t.headers.get("FreshToken");r&&pt(r);let o=t.headers.get("Content-Type")??"",s;o.includes("json")?s=await t.json():s=await t.text();let i={status:t.status,data:s,ok:t.ok,headers:t.headers,_requestId:e._requestId};for(let c of le){let a=c(i);a&&(i=a)}if(!t.ok)throw i;return i.data}async function q(e,n,t,r){let o={method:e,credentials:"same-origin",headers:{"Content-Type":"application/json",..._.headers}};if(_.auth){let s=ue();s&&(o.headers.Authorization=`Bearer ${s}`)}if(t!==void 0&&e!=="GET"){let s=typeof t=="object"&&t!==null?{...t}:t;if(_.auth&&typeof s=="object"&&s!==null){let i=ue();i&&(s.formToken=i)}o.body=JSON.stringify(s)}return r?.headers&&Object.assign(o.headers,r.headers),r?.params&&(n=Me(n,r.params)),Oe(o,_.baseUrl+n)}var Ae={configure(e){Object.assign(_,e)},get(e,n){return q("GET",e,void 0,n)},post(e,n,t){return q("POST",e,n,t)},put(e,n,t){return q("PUT",e,n,t)},patch(e,n,t){return q("PATCH",e,n,t)},delete(e,n){return q("DELETE",e,void 0,n)},async graphql(e,n,t,r){return q("POST",e,{query:n,variables:t||{}},r)},async upload(e,n,t){let r={method:"POST",headers:{..._.headers},body:n};if(delete r.headers["Content-Type"],delete r.headers["content-type"],_.auth){let o=ue();o&&(r.headers.Authorization=`Bearer ${o}`)}return t?.headers&&Object.assign(r.headers,t.headers),t?.params&&(e=Me(e,t.params)),Oe(r,_.baseUrl+e)},intercept(e,n){e==="request"?ce.push(n):le.push(n)},_reset(){_.baseUrl="",_.auth=!1,_.tokenKey="tina4_token",_.headers={},ce.length=0,le.length=0}};function mt(e){let n=e.cacheStrategy??"network-first",t=JSON.stringify(e.precache??[]),r=e.offlineRoute?`'${e.offlineRoute}'`:"null";return`
|
|
2
|
+
const CACHE = 'tina4-v1';
|
|
3
|
+
const PRECACHE = ${t};
|
|
4
|
+
const OFFLINE = ${r};
|
|
5
|
+
|
|
6
|
+
self.addEventListener('install', (e) => {
|
|
7
|
+
e.waitUntil(
|
|
8
|
+
caches.open(CACHE).then((c) => c.addAll(PRECACHE)).then(() => self.skipWaiting())
|
|
9
|
+
);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
self.addEventListener('activate', (e) => {
|
|
13
|
+
e.waitUntil(self.clients.claim());
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
self.addEventListener('fetch', (e) => {
|
|
17
|
+
const req = e.request;
|
|
18
|
+
if (req.method !== 'GET') return;
|
|
19
|
+
|
|
20
|
+
${n==="cache-first"?`
|
|
21
|
+
e.respondWith(
|
|
22
|
+
caches.match(req).then((cached) => cached || fetch(req).then((res) => {
|
|
23
|
+
const clone = res.clone();
|
|
24
|
+
caches.open(CACHE).then((c) => c.put(req, clone));
|
|
25
|
+
return res;
|
|
26
|
+
})).catch(() => OFFLINE ? caches.match(OFFLINE) : new Response('Offline', { status: 503 }))
|
|
27
|
+
);`:n==="stale-while-revalidate"?`
|
|
28
|
+
e.respondWith(
|
|
29
|
+
caches.match(req).then((cached) => {
|
|
30
|
+
const fetched = fetch(req).then((res) => {
|
|
31
|
+
caches.open(CACHE).then((c) => c.put(req, res.clone()));
|
|
32
|
+
return res;
|
|
33
|
+
});
|
|
34
|
+
return cached || fetched;
|
|
35
|
+
}).catch(() => OFFLINE ? caches.match(OFFLINE) : new Response('Offline', { status: 503 }))
|
|
36
|
+
);`:`
|
|
37
|
+
e.respondWith(
|
|
38
|
+
fetch(req).then((res) => {
|
|
39
|
+
const clone = res.clone();
|
|
40
|
+
caches.open(CACHE).then((c) => c.put(req, clone));
|
|
41
|
+
return res;
|
|
42
|
+
}).catch(() => caches.match(req).then((cached) =>
|
|
43
|
+
cached || (OFFLINE ? caches.match(OFFLINE) : new Response('Offline', { status: 503 }))
|
|
44
|
+
))
|
|
45
|
+
);`}
|
|
46
|
+
});
|
|
47
|
+
`.trim()}function Ie(e){let n={name:e.name,short_name:e.shortName??e.name,start_url:"/",display:e.display??"standalone",background_color:e.backgroundColor??"#ffffff",theme_color:e.themeColor??"#000000"};return e.icon&&(n.icons=[{src:e.icon,sizes:"192x192",type:"image/png"},{src:e.icon,sizes:"512x512",type:"image/png"}]),n}var Ne={register(e){let n=Ie(e),t=new Blob([JSON.stringify(n)],{type:"application/json"}),r=document.createElement("link");r.rel="manifest",r.href=URL.createObjectURL(t),document.head.appendChild(r);let o=document.querySelector('meta[name="theme-color"]');o||(o=document.createElement("meta"),o.name="theme-color",document.head.appendChild(o)),o.content=e.themeColor??"#000000","serviceWorker"in navigator&&(e.swUrl?navigator.serviceWorker.register(e.swUrl).catch(s=>{console.warn("[tina4] Service worker registration failed:",s)}):navigator.serviceWorker.register("/sw.js").catch(()=>{console.info("[tina4] No service worker at /sw.js. Use pwa.generateServiceWorker() to create one, or pass swUrl in config.")}))},generateServiceWorker(e){return mt(e)},generateManifest(e){return Ie(e)}};var ht={reconnect:!0,reconnectDelay:1e3,reconnectMaxDelay:3e4,reconnectAttempts:1/0,protocols:[],token:""};function vt(e){let n=Array.isArray(e.protocols)?e.protocols:e.protocols?[e.protocols]:[];return e.token?["bearer",e.token,...n]:e.protocols}function yt(e,n={}){let t={...ht,...n},r=w("connecting"),o=w(!1),s=w(null),i=w(null),c=w(0),a={message:[],open:[],close:[],error:[]},l=null,g=!1,m=t.reconnectDelay,u=null,d=0;function f(v){if(typeof v!="string")return v;try{return JSON.parse(v)}catch{return v}}function h(){r.value=d>0?"reconnecting":"connecting";try{l=new WebSocket(e,vt(t))}catch{r.value="closed",o.value=!1;return}l.onopen=()=>{r.value="open",o.value=!0,i.value=null,d=0,m=t.reconnectDelay,c.value=0;for(let v of a.open)v()},l.onmessage=v=>{let T=f(v.data);s.value=T;for(let R of a.message)R(T)},l.onclose=v=>{r.value="closed",o.value=!1;for(let T of a.close)T(v.code,v.reason);!g&&t.reconnect&&d<t.reconnectAttempts&&x()},l.onerror=v=>{i.value=v;for(let T of a.error)T(v)}}function x(){d++,c.value=d,r.value="reconnecting",u=setTimeout(()=>{u=null,h()},m),m=Math.min(m*2,t.reconnectMaxDelay)}let C={status:r,connected:o,lastMessage:s,error:i,reconnectCount:c,send(v){if(!l||l.readyState!==WebSocket.OPEN)throw new Error("[tina4] WebSocket is not connected");let T=typeof v=="string"?v:JSON.stringify(v);l.send(T)},on(v,T){return a[v].push(T),()=>{let R=a[v],A=R.indexOf(T);A>=0&&R.splice(A,1)}},pipe(v,T){let R=A=>{v.value=T(A,v.value)};return C.on("message",R)},close(v,T){g=!0,u&&(clearTimeout(u),u=null),l&&l.close(v??1e3,T??""),r.value="closed",o.value=!1}};return h(),C}var K={connect:yt};var St={mode:"eventsource",method:"GET",headers:{},body:void 0,reconnect:!0,reconnectDelay:1e3,reconnectMaxDelay:3e4,reconnectAttempts:1/0,events:[],json:!0};function bt(e,n={}){let t={...St,...n},r=w("connecting"),o=w(!1),s=w(null),i=w(null),c=w(null),a=w(0),l={message:[],open:[],close:[],error:[]},g=null,m=null,u=!1,d=t.reconnectDelay,f=null,h=0;function x(p){if(!t.json||typeof p!="string")return p;try{return JSON.parse(p)}catch{return p}}function C(p,S){s.value=p,i.value=S;for(let k of l.message)k(p,S??void 0)}function v(){r.value="open",o.value=!0,c.value=null,h=0,d=t.reconnectDelay,a.value=0;for(let p of l.open)p()}function T(){r.value="closed",o.value=!1;for(let p of l.close)p();!u&&t.reconnect&&h<t.reconnectAttempts&&Y()}function R(p){c.value=p;for(let S of l.error)S(p)}function A(){r.value=h>0?"reconnecting":"connecting";try{g=new EventSource(e)}catch{r.value="closed",o.value=!1;return}g.onopen=()=>v(),g.onmessage=p=>{C(x(p.data),null)};for(let p of t.events)g.addEventListener(p,S=>{C(x(S.data),p)});g.onerror=p=>{R(p),g&&g.readyState===2&&(g=null,T())}}function z(){r.value=h>0?"reconnecting":"connecting",m=new AbortController;let p={method:t.method,headers:t.headers,signal:m.signal};t.body!==void 0&&(p.body=typeof t.body=="string"?t.body:JSON.stringify(t.body)),fetch(e,p).then(async S=>{if(!S.ok){R(new Error(`[tina4] SSE fetch ${S.status}`)),T();return}v();let k=S.body.getReader(),M=new TextDecoder,P="";for(;;){let{done:Ge,value:Ze}=await k.read();if(Ge)break;P+=M.decode(Ze,{stream:!0});let ge=P.split(`
|
|
48
|
+
`);P=ge.pop();for(let Qe of ge){let pe=Qe.trim();pe&&C(x(pe),null)}}let fe=P.trim();fe&&C(x(fe),null),m=null,T()}).catch(S=>{S.name!=="AbortError"&&(m=null,R(S),T())})}function Y(){h++,a.value=h,r.value="reconnecting",f=setTimeout(()=>{f=null,b()},d),d=Math.min(d*2,t.reconnectMaxDelay)}function b(){t.mode==="fetch"?z():A()}let y={status:r,connected:o,lastMessage:s,lastEvent:i,error:c,reconnectCount:a,on(p,S){return l[p].push(S),()=>{let k=l[p],M=k.indexOf(S);M>=0&&k.splice(M,1)}},pipe(p,S){let k=M=>{p.value=S(M,p.value)};return y.on("message",k)},close(){u=!0,f&&(clearTimeout(f),f=null),g&&(g.close(),g=null),m&&(m.abort(),m=null),r.value="closed",o.value=!1}};return b(),y}var Pe={connect:bt};async function X(e="/api/rtc/config"){let n=await fetch(e);if(!n.ok)throw new Error(`[tina4] rtc config fetch failed: ${n.status}`);return n.json()}function wt(){let e=window.location;return`${e.protocol==="https:"?"wss:":"ws:"}//${e.host}`}function Le(e){return/^wss?:\/\//.test(e)?e:wt()+(e.startsWith("/")?e:"/"+e)}function Tt(){let e=globalThis.crypto;if(e&&"randomUUID"in e)return e.randomUUID().slice(0,8);let n="";for(let t=0;t<8;t++)n+=Math.floor(16*(.5+t)).toString(16);return n+Date.now().toString(16).slice(-4)}async function kt(e,n={}){let t=w("connecting"),r=w(null),o=w([]),s=w(!1),i=w(null),c=Tt(),a=n.config??await X(n.configUrl),l=n.iceServers??a.iceServers??[],g=n.signallingUrl??a.signalling??"/ws/rtc",m=Le(g.includes("{room}")?g.replace("{room}",e):`${g}/${e}`),u=null;n.media instanceof MediaStream?u=n.media:n.media!==!1&&(u=await navigator.mediaDevices.getUserMedia(n.media??{audio:!0,video:!0})),r.value=u;let d=u?.getVideoTracks()[0]??null,f=new Map,h=K.connect(m);function x(){o.value=[...f.entries()].map(([b,y])=>({id:b,stream:y.stream}))}function C(b){try{h.send({...b,from:c})}catch{}}function v(b){let y=f.get(b);if(y)return y;let p=new RTCPeerConnection({iceServers:l}),S={pc:p,polite:c<b,makingOffer:!1,ignoreOffer:!1,stream:null};if(f.set(b,S),u)for(let k of u.getTracks())p.addTrack(k,u);return p.onnegotiationneeded=async()=>{try{S.makingOffer=!0,await p.setLocalDescription(),C({type:"desc",to:b,description:p.localDescription})}catch(k){i.value=k}finally{S.makingOffer=!1}},p.onicecandidate=({candidate:k})=>{k&&C({type:"ice",to:b,candidate:k})},p.ontrack=({streams:k})=>{S.stream=k[0]??null,x()},p.onconnectionstatechange=()=>{["failed","closed"].includes(p.connectionState)?T(b):p.connectionState==="connected"&&(t.value="connected")},x(),S}function T(b){let y=f.get(b);if(y){try{y.pc.close()}catch{}f.delete(b),x()}}async function R(b){let y=b,p=y.from;if(!p||p===c||y.to&&y.to!==c)return;if(y.type==="hello"){v(p),C({type:"welcome",to:p});return}if(y.type==="welcome"){v(p);return}if(y.type==="bye"){T(p);return}let S=v(p),k=S.pc;if(y.type==="desc"){let M=y.description,P=M.type==="offer"&&(S.makingOffer||k.signalingState!=="stable");if(S.ignoreOffer=!S.polite&&P,S.ignoreOffer)return;await k.setRemoteDescription(M),M.type==="offer"&&(await k.setLocalDescription(),C({type:"desc",to:p,description:k.localDescription}))}else if(y.type==="ice")try{await k.addIceCandidate(y.candidate)}catch(M){S.ignoreOffer||(i.value=M)}}h.on("message",b=>{R(b)}),h.on("open",()=>{C({type:"hello"})});async function A(b){if(b)for(let{pc:y}of f.values()){let p=y.getSenders().find(S=>S.track?.kind==="video");p&&await p.replaceTrack(b)}}async function z(){await A(d),s.value=!1}async function Y(){let y=(await navigator.mediaDevices.getDisplayMedia({video:!0})).getVideoTracks()[0];await A(y),y.onended=()=>{z()},s.value=!0}return{status:t,localStream:r,peers:o,screenSharing:s,error:i,id:c,shareScreen:Y,stopScreen:z,toggleAudio(b){let y=u?.getAudioTracks()[0];return y?(y.enabled=b??!y.enabled,y.enabled):!1},toggleVideo(b){let y=u?.getVideoTracks()[0];return y?(y.enabled=b??!y.enabled,y.enabled):!1},leave(){C({type:"bye"});for(let b of[...f.keys()])T(b);if(u)for(let b of u.getTracks())b.stop();h.close(),t.value="closed"}}}function Ct(e,n={}){let t=w([]),r=w([]),o=w([]),s=new Map,i=n.typingTimeout??3e3,c=n.url??"/ws/chat",a=Le(c.includes("{channel}")?c.replace("{channel}",String(e)):`${c}/${e}`),l=K.connect(a,{token:n.token});function g(d){o.value.includes(d)||(o.value=[...o.value,d]);let f=s.get(d);f&&clearTimeout(f),s.set(d,setTimeout(()=>{o.value=o.value.filter(h=>h!==d),s.delete(d)},i))}l.on("message",d=>{let f=d;switch(f.type){case"message":t.value=[...t.value,f.message];break;case"presence":f.event==="roster"?r.value=f.users??[]:f.event==="join"&&f.user_id?r.value=[...new Set([...r.value,f.user_id])]:f.event==="leave"&&(r.value=r.value.filter(h=>h!==f.user_id));break;case"typing":f.user_id&&g(f.user_id);break}});let m=n.apiBase??"",u=n.messagesPath??"/api/channels/{id}/messages";return{status:l.status,connected:l.connected,messages:t,presence:r,typing:o,send(d,f){l.send({type:"message",body:d,thread_id:f??null})},sendTyping(){l.send({type:"typing"})},markRead(){l.send({type:"read"})},async history(d,f=50){let h=u.replace("{id}",String(e)),x=new URLSearchParams({limit:String(f)});d&&x.set("before",String(d));let C={};n.token&&(C.Authorization=`Bearer ${n.token}`);let v=await fetch(`${m}${h}?${x}`,{headers:C});if(!v.ok)throw new Error(`[tina4] chat history failed: ${v.status}`);let T=await v.json(),R=[...T].reverse();return t.value=[...R,...t.value],T},close(){for(let d of s.values())clearTimeout(d);s.clear(),l.close()}}}async function Et(e,n,t={}){let r=t.filesPath??"/api/files",o=new FormData;o.append("channel_id",String(e)),o.append("file",n,n.name??"file");let s={};t.token&&(s.Authorization=`Bearer ${t.token}`);let i=await fetch(`${t.apiBase??""}${r}`,{method:"POST",body:o,headers:s});if(!i.ok)throw new Error(`[tina4] file upload failed: ${i.status}`);return i.json()}async function Rt(e,n={}){let t=/^https?:\/\//.test(e)?e:`${n.apiBase??""}${n.filesPath??"/api/files"}/${e}`,r={};n.token&&(r.Authorization=`Bearer ${n.token}`);let o=await fetch(t,{headers:r});if(!o.ok)throw new Error(`[tina4] file fetch failed: ${o.status}`);return URL.createObjectURL(await o.blob())}var De={config:X,call:kt,chat:Ct,upload:Et,fetchBlob:Rt};var Fe={read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},$e=/(token|password|passwd|secret|api[_-]?key|apikey|auth(?!or)|credential|jwt|bearer|otp|seed|private[_-]?key|session[_-]?id)/i,_t=/^[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}$/,xt=/^[A-Za-z0-9+/_=-]{40,}$/,qe=new Set;function Mt(e,n){if($e.test(e))return`key name "${e}" looks like a credential`;if(typeof n=="string"){if(_t.test(n))return"value looks like a JWT";if(n.length>=40&&xt.test(n))return"value looks like a long base64 / token"}if(n&&typeof n=="object"&&!Array.isArray(n)){for(let t of Object.keys(n))if($e.test(t))return`object contains a credential-shape field "${t}"`}return null}function Ot(e,n){qe.has(n)||(qe.add(n),console.warn(`[tina4 persist] ${e} (key: ${JSON.stringify(n)}). localStorage is XSS-readable and never appropriate for credentials, tokens, passwords, personal data, or secrets. See STORAGE.md.`))}function He(e){if(typeof globalThis>"u")return null;try{let n=e==="local"?globalThis.localStorage:globalThis.sessionStorage;return!n||typeof n.getItem!="function"?null:n}catch{return null}}function je(e,n,t){try{let r=JSON.parse(e);return r&&typeof r=="object"&&"value"in r?{version:r.v,payload:r.value}:{version:void 0,payload:r}}catch{return{version:void 0,payload:t?e:n.read(e)}}}function We(e,n,t){return t?e:n.read(typeof e=="string"?e:JSON.stringify(e))}function At(e,n,t,r,o,s,i){try{let c=n.getItem(t);if(c===null)return;let a=je(c,o,s);if(a.version===r||a.version===void 0){e.value=We(a.payload,o,s);return}if(i){try{e.value=i(a.payload,a.version)}catch(l){console.warn(`[tina4 persist] migrate() threw for key "${t}":`,l)}return}console.warn(`[tina4 persist] stored version ${a.version} does not match current ${r} for key "${t}", and no migrate() was provided. Discarding the stored value.`)}catch(c){console.warn(`[tina4 persist] failed to read key "${t}":`,c)}}function Ke(e,n,t){if(t)return;let r=Mt(e,n);r&&Ot(r,e)}function It(e,n,t,r,o,s,i){return E(()=>{let c=e.value;Ke(t,c,i);try{let a=JSON.stringify(s?{v:r,value:c}:{v:r,value:o.write(c)});n.setItem(t,a)}catch(a){console.warn(`[tina4 persist] failed to write key "${t}":`,a)}})}function Nt(e,n,t,r,o,s,i,c){if(!c||typeof globalThis>"u"||!("addEventListener"in globalThis))return null;let a=l=>{let g=l;if(!(g.storageArea!==n||g.key!==t||g.newValue===null))try{let m=je(g.newValue,o,s);m.version!==void 0&&m.version!==r&&i?e.value=i(m.payload,m.version):(m.version===r||m.version===void 0)&&(e.value=We(m.payload,o,s))}catch(m){console.warn(`[tina4 persist] failed to parse storage event for key "${t}":`,m)}};return globalThis.addEventListener?.("storage",a),()=>{globalThis.removeEventListener?.("storage",a)}}function Pt(e,n){try{e.removeItem(n)}catch(t){console.warn(`[tina4 persist] failed to clear key "${n}":`,t)}}function ze(e,n){let{key:t,storage:r="local",serializer:o=Fe,version:s=1,migrate:i,syncTabs:c=!1,silenceCredentialWarning:a=!1}=n;if(!t||typeof t!="string")throw new Error("[tina4 persist] options.key is required and must be a string");let l=o===Fe,g=He(r);if(!g)return Ue(e,()=>{},()=>{});At(e,g,t,s,o,l,i),Ke(t,e.peek(),a);let m=It(e,g,t,s,o,l,a),u=Nt(e,g,t,s,o,l,i,c);return Ue(e,()=>Pt(g,t),()=>{m(),u&&u()})}function Ue(e,n,t){return Object.assign(e,{clear:n,dispose:t})}function Ve(e,n="local"){let t=He(n);if(t)for(let r of e)try{t.removeItem(r)}catch(o){console.warn(`[tina4 persist] failed to clear key "${r}":`,o)}}var Lt=["ar","he","fa","ur","ps","dv","syr","ckb","yi"];function Dt(){return globalThis.navigator?.language||"en"}function Be(e,n="",t={}){for(let[r,o]of Object.entries(e)){let s=n?`${n}.${r}`:r;if(o!==null&&typeof o=="object"&&!Array.isArray(o))Be(o,s,t);else{let i=String(o);t[s]=i,r in t||(t[r]=i)}}return t}function Ft(e,n){return e.replace(/\{(\w+)\}/g,(t,r)=>Object.prototype.hasOwnProperty.call(n,r)?String(n[r]):t)}function de(e={}){let n=e.locale||Dt(),t=e.fallbackLocale||n,r=new Set([...Lt,...e.rtlLocales||[]]),o=w(n,"i18n.locale"),s=new Map,i=new Map;function c(u,d){let f=Be(d),h=s.get(u);s.set(u,h?{...h,...f}:f)}if(e.messages)for(let[u,d]of Object.entries(e.messages))c(u,d);function a(u,d){return s.get(u)?.[d]}function l(u,d){let f=`n|${u}|${JSON.stringify(d||{})}`,h=i.get(f);return h||(h=new Intl.NumberFormat(u,d),i.set(f,h)),h}function g(u,d){let f=`d|${u}|${JSON.stringify(d||{})}`,h=i.get(f);return h||(h=new Intl.DateTimeFormat(u,d),i.set(f,h)),h}function m(u,d){let f=`r|${u}|${JSON.stringify(d||{})}`,h=i.get(f);return h||(h=new Intl.RelativeTimeFormat(u,d),i.set(f,h)),h}return{locale:o,t(u,d){let f=o.value,h=a(f,u);return h===void 0&&t!==f&&(h=a(t,u)),h===void 0&&(h=u),d?Ft(h,d):h},setLocale(u){o.value=u},getLocale(){return o.value},addMessages:c,hasLocale(u){return s.has(u)},availableLocales(){return[...s.keys()].sort()},async loadMessages(u,d){let f=await fetch(d);if(!f.ok)throw new Error(`[tina4 i18n] failed to load "${u}" from ${d}: ${f.status}`);c(u,await f.json())},number(u,d){return l(o.value,d).format(u)},currency(u,d,f){return l(o.value,{style:"currency",currency:d,...f}).format(u)},date(u,d){let f=u instanceof Date?u:new Date(u);return g(o.value,d).format(f)},relativeTime(u,d,f){return m(o.value,f||{numeric:"auto"}).format(u,d)},isRTL(){return r.has(o.value.split("-")[0].toLowerCase())},dir(){return this.isRTL()?"rtl":"ltr"}}}var Je=de();return rt($t);})();
|
|
@@ -356,6 +356,16 @@ class ServiceRunner:
|
|
|
356
356
|
|
|
357
357
|
for svc in self.services:
|
|
358
358
|
svc["running"] = False
|
|
359
|
+
instance = svc.get("instance")
|
|
360
|
+
if instance is not None and hasattr(instance, "stop") and callable(instance.stop):
|
|
361
|
+
try:
|
|
362
|
+
instance.stop()
|
|
363
|
+
except Exception as exc:
|
|
364
|
+
_get_log().error(
|
|
365
|
+
"Error stopping service instance",
|
|
366
|
+
name=svc.get("name"),
|
|
367
|
+
error=str(exc),
|
|
368
|
+
)
|
|
359
369
|
|
|
360
370
|
# Give threads a moment to finish
|
|
361
371
|
for t in self._threads:
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
"use strict";var Tina4=(()=>{var X=Object.defineProperty;var Be=Object.getOwnPropertyDescriptor;var Ve=Object.getOwnPropertyNames;var Ge=Object.prototype.hasOwnProperty;var Ze=(e,n)=>{for(var t in n)X(e,t,{get:n[t],enumerable:!0})},Qe=(e,n,t,r)=>{if(n&&typeof n=="object"||typeof n=="function")for(let o of Ve(n))!Ge.call(e,o)&&o!==t&&X(e,o,{get:()=>n[o],enumerable:!(r=Be(n,o))||r.enumerable});return e};var Xe=e=>Qe(X({},"__esModule",{value:!0}),e);var Et={};Ze(Et,{Tina4Element:()=>P,api:()=>_e,batch:()=>V,clearPersistedKeys:()=>je,computed:()=>me,createI18n:()=>le,effect:()=>R,html:()=>ve,i18n:()=>We,isSignal:()=>I,navigate:()=>G,persist:()=>Ue,pwa:()=>Me,route:()=>Te,router:()=>Ce,rtc:()=>Ie,rtcConfig:()=>Z,signal:()=>k,sse:()=>Oe,ws:()=>W});var L=null,q=null,$=null,J=null;function O(e){J=e}function B(){return J}var fe=null,ge=null,pe=[],Ye=512;var z=0,Y=new Set;function k(e,n){let t=e,r=new Set,o={_t4:!0,get value(){if(L&&(r.add(L),q)){let s=L;q.push(()=>r.delete(s))}return t},set value(s){if(Object.is(s,t))return;let i=t;if(t=s,o._debugInfo&&o._debugInfo.updateCount++,ge&&ge(o,i,s),z>0)for(let l of r)Y.add(l);else{let l;for(let d of[...r])try{d()}catch(f){l===void 0&&(l=f)}if(l!==void 0)throw l}},_subscribe(s){return r.add(s),()=>{r.delete(s)}},peek(){return t}};return fe?(o._debugInfo={label:n,createdAt:Date.now(),updateCount:0,subs:r},fe(o,n)):pe.length<Ye&&pe.push({ref:new WeakRef(o),label:n,createdAt:Date.now(),subs:r}),o}function me(e){let n=k(void 0);return R(()=>{n.value=e()}),{_t4:!0,get value(){return n.value},set value(t){throw new Error("[tina4] computed signals are read-only")},_subscribe(t){return n._subscribe(t)},peek(){return n.peek()}}}function R(e){let n=!1,t=[],r=[],o=()=>{for(let l of r)l();r=[]},s=()=>{if(n)return;for(let h of t)h();t=[],o();let l=L,d=q,f=$;L=s,q=t,$=r;try{e()}finally{L=l,q=d,$=f}};s();let i=()=>{n=!0;for(let l of t)l();t=[],o()};return $&&$.push(i),J&&J.push(i),i}function V(e){z++;try{e()}finally{if(z--,z===0){let n=[...Y];Y.clear();let t;for(let r of n)try{r()}catch(o){t===void 0&&(t=o)}if(t!==void 0)throw t}}}function I(e){return e!==null&&typeof e=="object"&&e._t4===!0}var he=new WeakMap,ee="t4:";function ve(e,...n){let t=he.get(e);if(!t){t=document.createElement("template");let i="";for(let l=0;l<e.length;l++)i+=e[l],l<n.length&&(ot(i)?i+=`__t4_${l}__`:i+=`<!--${ee}${l}-->`);t.innerHTML=i,he.set(e,t)}let r=t.content.cloneNode(!0),o=et(r);for(let{marker:i,index:l}of o)nt(i,n[l]);let s=tt(r);for(let i of s)rt(i,n);return r}function et(e){let n=[];return ne(e,t=>{if(t.nodeType===8){let r=t.data;if(r&&r.startsWith(ee)){let o=parseInt(r.slice(ee.length),10);n.push({marker:t,index:o})}}}),n}function tt(e){let n=[];return ne(e,t=>{t.nodeType===1&&n.push(t)}),n}function ne(e,n){let t=e.childNodes;for(let r=0;r<t.length;r++){let o=t[r];n(o),ne(o,n)}}function nt(e,n){let t=e.parentNode;if(t)if(I(n)){let r=document.createTextNode("");t.replaceChild(r,e),R(()=>{r.data=String(n.value??"")})}else if(typeof n=="function"){let r=document.createComment("");t.replaceChild(r,e);let o=[],s=[];R(()=>{for(let w of s)w();s=[];let i=[],l=B();O(i);let d=n();O(l),s=i;for(let w of o)w.parentNode?.removeChild(w);o=[];let f=te(d),h=r.parentNode;if(h)for(let w of f)h.insertBefore(w,r),o.push(w)})}else if(ye(n))t.replaceChild(n,e);else if(n instanceof Node)t.replaceChild(n,e);else if(Array.isArray(n)){let r=document.createDocumentFragment();for(let o of n){let s=te(o);for(let i of s)r.appendChild(i)}t.replaceChild(r,e)}else{let r=document.createTextNode(String(n??""));t.replaceChild(r,e)}}function rt(e,n){let t=[];for(let r of Array.from(e.attributes)){let o=r.name,s=r.value;if(o.startsWith("@")){let l=o.slice(1),d=s.match(/__t4_(\d+)__/);if(d){let f=n[parseInt(d[1],10)];typeof f=="function"&&e.addEventListener(l,h=>V(()=>f(h)))}t.push(o);continue}if(o.startsWith("?")){let l=o.slice(1),d=s.match(/__t4_(\d+)__/);if(d){let f=n[parseInt(d[1],10)];if(I(f)){let h=f;R(()=>{h.value?e.setAttribute(l,""):e.removeAttribute(l)})}else typeof f=="function"?R(()=>{f()?e.setAttribute(l,""):e.removeAttribute(l)}):f&&e.setAttribute(l,"")}t.push(o);continue}if(o.startsWith(".")){let l=o.slice(1),d=s.match(/__t4_(\d+)__/);if(d){let f=n[parseInt(d[1],10)];I(f)?R(()=>{e[l]=f.value}):typeof f=="function"?R(()=>{e[l]=f()??""}):e[l]=f}t.push(o);continue}let i=s.match(/__t4_(\d+)__/);if(i){let l=n[parseInt(i[1],10)];if(I(l)){let d=l;R(()=>{e.setAttribute(o,String(d.value??""))})}else typeof l=="function"?R(()=>{e.setAttribute(o,String(l()??""))}):e.setAttribute(o,String(l??""))}}for(let r of t)e.removeAttribute(r)}function te(e){if(e==null||e===!1)return[];if(ye(e))return Array.from(e.childNodes);if(e instanceof Node)return[e];if(Array.isArray(e)){let n=[];for(let t of e)n.push(...te(t));return n}return[document.createTextNode(String(e))]}function ye(e){return e!=null&&typeof e=="object"&&e.nodeType===11}function ot(e){let n=!1,t=!1,r=!1;for(let o=0;o<e.length;o++){let s=e[o];s==="<"&&!n&&!t&&(r=!0),s===">"&&!n&&!t&&(r=!1),r&&(s==='"'&&!n&&(t=!t),s==="'"&&!t&&(n=!n))}return r}var be=null,Se=null;var P=class extends HTMLElement{constructor(){super();this._props={};this._rendered=!1;this._disposeRender=null;this._innerDisposers=[];let t=this.constructor;this._root=t.shadow?this.attachShadow({mode:"open"}):this;for(let[r,o]of Object.entries(t.props))this._props[r]=k(this._coerce(this.getAttribute(r),o))}static get observedAttributes(){return Object.keys(this.props)}connectedCallback(){if(this._rendered)return;this._rendered=!0;let t=this.constructor,r=null;if(t.styles&&t.shadow&&this._root instanceof ShadowRoot){let o=document.createElement("style");o.textContent=t.styles,this._root.appendChild(o),r=o}this._disposeRender=R(()=>{this._innerDisposers.splice(0).forEach(d=>d());let o=[],s=B();O(o);let i=this.render();O(s),this._innerDisposers=o;let l=Array.from(this._root.childNodes);for(let d of l)d!==r&&this._root.removeChild(d);i&&this._root.appendChild(i)}),this.onMount(),be&&be(this)}disconnectedCallback(){this._disposeRender&&(this._disposeRender(),this._disposeRender=null),this._innerDisposers.splice(0).forEach(t=>t()),this.onUnmount(),Se&&Se(this)}attributeChangedCallback(t,r,o){let i=this.constructor.props[t];i&&this._props[t]&&(this._props[t].value=this._coerce(o,i))}prop(t){if(!this._props[t])throw new Error(`[tina4] Prop '${t}' not declared in static props of <${this.tagName.toLowerCase()}>`);return this._props[t]}emit(t,r){this.dispatchEvent(new CustomEvent(t,{bubbles:!0,composed:!0,...r}))}onMount(){}onUnmount(){}_coerce(t,r){return r===Boolean?t!==null:r===Number?t!==null?Number(t):0:t??""}};P.props={},P.styles="",P.shadow=!0;var oe=[],D=null,U="history",st=!1,j=[],re=[],ke=0;function Te(e,n){let t=[],r;e==="*"?r=".*":r=e.replace(/\{(\w+)\}/g,(s,i)=>(t.push(i),"([^/]+)"));let o=new RegExp(`^${r}$`);typeof n=="function"?oe.push({pattern:e,regex:o,paramNames:t,handler:n}):oe.push({pattern:e,regex:o,paramNames:t,handler:n.handler,guard:n.guard})}function G(e,n){if(U==="hash")if(n?.replace){let t=new URL(location.href);t.hash="#"+e,history.replaceState(null,"",t.toString()),H()}else location.hash="#"+e;else n?.replace?history.replaceState(null,"",e):history.pushState(null,"",e),H()}function H(){if(!D)return;let e=performance.now(),n=++ke,t=U==="hash"?location.hash.slice(1)||"/":location.pathname;for(let r of oe){let o=t.match(r.regex);if(!o)continue;let s={};if(r.paramNames.forEach((d,f)=>{s[d]=decodeURIComponent(o[f+1])}),r.guard){let d=r.guard();if(d===!1)return;if(typeof d=="string"){G(d,{replace:!0});return}}re.splice(0).forEach(d=>d()),D.innerHTML="";let i=[];O(i);let l=r.handler(s);if(l instanceof Promise)l.then(d=>{if(O(null),n!==ke){for(let h of i)h();return}we(D,d),re=i;let f=performance.now()-e;for(let h of j)h({path:t,params:s,pattern:r.pattern,durationMs:f})});else{O(null),we(D,l),re=i;let d=performance.now()-e;for(let f of j)f({path:t,params:s,pattern:r.pattern,durationMs:d})}return}}function we(e,n){n instanceof DocumentFragment||n instanceof Node?e.replaceChildren(n):typeof n=="string"?e.innerHTML=n:n!=null&&e.replaceChildren(document.createTextNode(String(n)))}var Ce={start(e){if(D=document.querySelector(e.target),!D)throw new Error(`[tina4] Router target '${e.target}' not found in DOM`);U=e.mode??"history",st=!0,window.addEventListener("popstate",H),U==="hash"&&window.addEventListener("hashchange",H),document.addEventListener("click",n=>{if(n.metaKey||n.ctrlKey||n.shiftKey||n.altKey)return;let t=n.target.closest("a[href]");if(!t||t.origin!==location.origin||t.hasAttribute("target")||t.hasAttribute("download")||t.getAttribute("rel")?.includes("external"))return;n.preventDefault();let r=U==="hash"?t.getAttribute("href"):t.pathname;G(r)}),H()},on(e,n){return j.push(n),()=>{let t=j.indexOf(n);t>=0&&j.splice(t,1)}}};var x={baseUrl:"",auth:!1,tokenKey:"tina4_token",headers:{}},se=[],ie=[],it=0;function ae(){try{return localStorage.getItem(x.tokenKey)}catch{return null}}function at(e){try{localStorage.setItem(x.tokenKey,e)}catch{}}function Ee(e,n){let t=Object.entries(n).map(([r,o])=>`${encodeURIComponent(r)}=${encodeURIComponent(String(o))}`).join("&");return e+(e.includes("?")?"&":"?")+t}async function Re(e,n){e._url=n,e._requestId=++it;for(let l of se){let d=l(e);d&&(e=d)}let t=await fetch(n,e),r=t.headers.get("FreshToken");r&&at(r);let o=t.headers.get("Content-Type")??"",s;o.includes("json")?s=await t.json():s=await t.text();let i={status:t.status,data:s,ok:t.ok,headers:t.headers,_requestId:e._requestId};for(let l of ie){let d=l(i);d&&(i=d)}if(!t.ok)throw i;return i.data}async function F(e,n,t,r){let o={method:e,credentials:"same-origin",headers:{"Content-Type":"application/json",...x.headers}};if(x.auth){let s=ae();s&&(o.headers.Authorization=`Bearer ${s}`)}if(t!==void 0&&e!=="GET"){let s=typeof t=="object"&&t!==null?{...t}:t;if(x.auth&&typeof s=="object"&&s!==null){let i=ae();i&&(s.formToken=i)}o.body=JSON.stringify(s)}return r?.headers&&Object.assign(o.headers,r.headers),r?.params&&(n=Ee(n,r.params)),Re(o,x.baseUrl+n)}var _e={configure(e){Object.assign(x,e)},get(e,n){return F("GET",e,void 0,n)},post(e,n,t){return F("POST",e,n,t)},put(e,n,t){return F("PUT",e,n,t)},patch(e,n,t){return F("PATCH",e,n,t)},delete(e,n){return F("DELETE",e,void 0,n)},async graphql(e,n,t,r){return F("POST",e,{query:n,variables:t||{}},r)},async upload(e,n,t){let r={method:"POST",headers:{...x.headers},body:n};if(delete r.headers["Content-Type"],delete r.headers["content-type"],x.auth){let o=ae();o&&(r.headers.Authorization=`Bearer ${o}`)}return t?.headers&&Object.assign(r.headers,t.headers),t?.params&&(e=Ee(e,t.params)),Re(r,x.baseUrl+e)},intercept(e,n){e==="request"?se.push(n):ie.push(n)},_reset(){x.baseUrl="",x.auth=!1,x.tokenKey="tina4_token",x.headers={},se.length=0,ie.length=0}};function lt(e){let n=e.cacheStrategy??"network-first",t=JSON.stringify(e.precache??[]),r=e.offlineRoute?`'${e.offlineRoute}'`:"null";return`
|
|
2
|
-
const CACHE = 'tina4-v1';
|
|
3
|
-
const PRECACHE = ${t};
|
|
4
|
-
const OFFLINE = ${r};
|
|
5
|
-
|
|
6
|
-
self.addEventListener('install', (e) => {
|
|
7
|
-
e.waitUntil(
|
|
8
|
-
caches.open(CACHE).then((c) => c.addAll(PRECACHE)).then(() => self.skipWaiting())
|
|
9
|
-
);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
self.addEventListener('activate', (e) => {
|
|
13
|
-
e.waitUntil(self.clients.claim());
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
self.addEventListener('fetch', (e) => {
|
|
17
|
-
const req = e.request;
|
|
18
|
-
if (req.method !== 'GET') return;
|
|
19
|
-
|
|
20
|
-
${n==="cache-first"?`
|
|
21
|
-
e.respondWith(
|
|
22
|
-
caches.match(req).then((cached) => cached || fetch(req).then((res) => {
|
|
23
|
-
const clone = res.clone();
|
|
24
|
-
caches.open(CACHE).then((c) => c.put(req, clone));
|
|
25
|
-
return res;
|
|
26
|
-
})).catch(() => OFFLINE ? caches.match(OFFLINE) : new Response('Offline', { status: 503 }))
|
|
27
|
-
);`:n==="stale-while-revalidate"?`
|
|
28
|
-
e.respondWith(
|
|
29
|
-
caches.match(req).then((cached) => {
|
|
30
|
-
const fetched = fetch(req).then((res) => {
|
|
31
|
-
caches.open(CACHE).then((c) => c.put(req, res.clone()));
|
|
32
|
-
return res;
|
|
33
|
-
});
|
|
34
|
-
return cached || fetched;
|
|
35
|
-
}).catch(() => OFFLINE ? caches.match(OFFLINE) : new Response('Offline', { status: 503 }))
|
|
36
|
-
);`:`
|
|
37
|
-
e.respondWith(
|
|
38
|
-
fetch(req).then((res) => {
|
|
39
|
-
const clone = res.clone();
|
|
40
|
-
caches.open(CACHE).then((c) => c.put(req, clone));
|
|
41
|
-
return res;
|
|
42
|
-
}).catch(() => caches.match(req).then((cached) =>
|
|
43
|
-
cached || (OFFLINE ? caches.match(OFFLINE) : new Response('Offline', { status: 503 }))
|
|
44
|
-
))
|
|
45
|
-
);`}
|
|
46
|
-
});
|
|
47
|
-
`.trim()}function xe(e){let n={name:e.name,short_name:e.shortName??e.name,start_url:"/",display:e.display??"standalone",background_color:e.backgroundColor??"#ffffff",theme_color:e.themeColor??"#000000"};return e.icon&&(n.icons=[{src:e.icon,sizes:"192x192",type:"image/png"},{src:e.icon,sizes:"512x512",type:"image/png"}]),n}var Me={register(e){let n=xe(e),t=new Blob([JSON.stringify(n)],{type:"application/json"}),r=document.createElement("link");r.rel="manifest",r.href=URL.createObjectURL(t),document.head.appendChild(r);let o=document.querySelector('meta[name="theme-color"]');o||(o=document.createElement("meta"),o.name="theme-color",document.head.appendChild(o)),o.content=e.themeColor??"#000000","serviceWorker"in navigator&&(e.swUrl?navigator.serviceWorker.register(e.swUrl).catch(s=>{console.warn("[tina4] Service worker registration failed:",s)}):navigator.serviceWorker.register("/sw.js").catch(()=>{console.info("[tina4] No service worker at /sw.js. Use pwa.generateServiceWorker() to create one, or pass swUrl in config.")}))},generateServiceWorker(e){return lt(e)},generateManifest(e){return xe(e)}};var ct={reconnect:!0,reconnectDelay:1e3,reconnectMaxDelay:3e4,reconnectAttempts:1/0,protocols:[],token:""};function ut(e){let n=Array.isArray(e.protocols)?e.protocols:e.protocols?[e.protocols]:[];return e.token?["bearer",e.token,...n]:e.protocols}function dt(e,n={}){let t={...ct,...n},r=k("connecting"),o=k(!1),s=k(null),i=k(null),l=k(0),d={message:[],open:[],close:[],error:[]},f=null,h=!1,w=t.reconnectDelay,u=null,a=0;function c(m){if(typeof m!="string")return m;try{return JSON.parse(m)}catch{return m}}function g(){r.value=a>0?"reconnecting":"connecting";try{f=new WebSocket(e,ut(t))}catch{r.value="closed",o.value=!1;return}f.onopen=()=>{r.value="open",o.value=!0,i.value=null,a=0,w=t.reconnectDelay,l.value=0;for(let m of d.open)m()},f.onmessage=m=>{let T=c(m.data);s.value=T;for(let _ of d.message)_(T)},f.onclose=m=>{r.value="closed",o.value=!1;for(let T of d.close)T(m.code,m.reason);!h&&t.reconnect&&a<t.reconnectAttempts&&y()},f.onerror=m=>{i.value=m;for(let T of d.error)T(m)}}function y(){a++,l.value=a,r.value="reconnecting",u=setTimeout(()=>{u=null,g()},w),w=Math.min(w*2,t.reconnectMaxDelay)}let C={status:r,connected:o,lastMessage:s,error:i,reconnectCount:l,send(m){if(!f||f.readyState!==WebSocket.OPEN)throw new Error("[tina4] WebSocket is not connected");let T=typeof m=="string"?m:JSON.stringify(m);f.send(T)},on(m,T){return d[m].push(T),()=>{let _=d[m],A=_.indexOf(T);A>=0&&_.splice(A,1)}},pipe(m,T){let _=A=>{m.value=T(A,m.value)};return C.on("message",_)},close(m,T){h=!0,u&&(clearTimeout(u),u=null),f&&f.close(m??1e3,T??""),r.value="closed",o.value=!1}};return g(),C}var W={connect:dt};var ft={mode:"eventsource",method:"GET",headers:{},body:void 0,reconnect:!0,reconnectDelay:1e3,reconnectMaxDelay:3e4,reconnectAttempts:1/0,events:[],json:!0};function gt(e,n={}){let t={...ft,...n},r=k("connecting"),o=k(!1),s=k(null),i=k(null),l=k(null),d=k(0),f={message:[],open:[],close:[],error:[]},h=null,w=null,u=!1,a=t.reconnectDelay,c=null,g=0;function y(p){if(!t.json||typeof p!="string")return p;try{return JSON.parse(p)}catch{return p}}function C(p,b){s.value=p,i.value=b;for(let E of f.message)E(p,b??void 0)}function m(){r.value="open",o.value=!0,l.value=null,g=0,a=t.reconnectDelay,d.value=0;for(let p of f.open)p()}function T(){r.value="closed",o.value=!1;for(let p of f.close)p();!u&&t.reconnect&&g<t.reconnectAttempts&&Q()}function _(p){l.value=p;for(let b of f.error)b(p)}function A(){r.value=g>0?"reconnecting":"connecting";try{h=new EventSource(e)}catch{r.value="closed",o.value=!1;return}h.onopen=()=>m(),h.onmessage=p=>{C(y(p.data),null)};for(let p of t.events)h.addEventListener(p,b=>{C(y(b.data),p)});h.onerror=p=>{_(p),h&&h.readyState===2&&(h=null,T())}}function K(){r.value=g>0?"reconnecting":"connecting",w=new AbortController;let p={method:t.method,headers:t.headers,signal:w.signal};t.body!==void 0&&(p.body=typeof t.body=="string"?t.body:JSON.stringify(t.body)),fetch(e,p).then(async b=>{if(!b.ok){_(new Error(`[tina4] SSE fetch ${b.status}`)),T();return}m();let E=b.body.getReader(),M=new TextDecoder,N="";for(;;){let{done:Ke,value:ze}=await E.read();if(Ke)break;N+=M.decode(ze,{stream:!0});let ue=N.split(`
|
|
48
|
-
`);N=ue.pop();for(let Je of ue){let de=Je.trim();de&&C(y(de),null)}}let ce=N.trim();ce&&C(y(ce),null),w=null,T()}).catch(b=>{b.name!=="AbortError"&&(w=null,_(b),T())})}function Q(){g++,d.value=g,r.value="reconnecting",c=setTimeout(()=>{c=null,S()},a),a=Math.min(a*2,t.reconnectMaxDelay)}function S(){t.mode==="fetch"?K():A()}let v={status:r,connected:o,lastMessage:s,lastEvent:i,error:l,reconnectCount:d,on(p,b){return f[p].push(b),()=>{let E=f[p],M=E.indexOf(b);M>=0&&E.splice(M,1)}},pipe(p,b){let E=M=>{p.value=b(M,p.value)};return v.on("message",E)},close(){u=!0,c&&(clearTimeout(c),c=null),h&&(h.close(),h=null),w&&(w.abort(),w=null),r.value="closed",o.value=!1}};return S(),v}var Oe={connect:gt};async function Z(e="/api/rtc/config"){let n=await fetch(e);if(!n.ok)throw new Error(`[tina4] rtc config fetch failed: ${n.status}`);return n.json()}function pt(){let e=window.location;return`${e.protocol==="https:"?"wss:":"ws:"}//${e.host}`}function Ae(e){return/^wss?:\/\//.test(e)?e:pt()+(e.startsWith("/")?e:"/"+e)}function mt(){let e=globalThis.crypto;if(e&&"randomUUID"in e)return e.randomUUID().slice(0,8);let n="";for(let t=0;t<8;t++)n+=Math.floor(16*(.5+t)).toString(16);return n+Date.now().toString(16).slice(-4)}async function ht(e,n={}){let t=k("connecting"),r=k(null),o=k([]),s=k(!1),i=k(null),l=mt(),d=n.config??await Z(n.configUrl),f=n.iceServers??d.iceServers??[],h=n.signallingUrl??d.signalling??"/ws/rtc",w=Ae(h.includes("{room}")?h.replace("{room}",e):`${h}/${e}`),u=null;n.media instanceof MediaStream?u=n.media:n.media!==!1&&(u=await navigator.mediaDevices.getUserMedia(n.media??{audio:!0,video:!0})),r.value=u;let a=u?.getVideoTracks()[0]??null,c=new Map,g=W.connect(w);function y(){o.value=[...c.entries()].map(([S,v])=>({id:S,stream:v.stream}))}function C(S){try{g.send({...S,from:l})}catch{}}function m(S){let v=c.get(S);if(v)return v;let p=new RTCPeerConnection({iceServers:f}),b={pc:p,polite:l<S,makingOffer:!1,ignoreOffer:!1,stream:null};if(c.set(S,b),u)for(let E of u.getTracks())p.addTrack(E,u);return p.onnegotiationneeded=async()=>{try{b.makingOffer=!0,await p.setLocalDescription(),C({type:"desc",to:S,description:p.localDescription})}catch(E){i.value=E}finally{b.makingOffer=!1}},p.onicecandidate=({candidate:E})=>{E&&C({type:"ice",to:S,candidate:E})},p.ontrack=({streams:E})=>{b.stream=E[0]??null,y()},p.onconnectionstatechange=()=>{["failed","closed"].includes(p.connectionState)?T(S):p.connectionState==="connected"&&(t.value="connected")},y(),b}function T(S){let v=c.get(S);if(v){try{v.pc.close()}catch{}c.delete(S),y()}}async function _(S){let v=S,p=v.from;if(!p||p===l||v.to&&v.to!==l)return;if(v.type==="hello"){m(p),C({type:"welcome",to:p});return}if(v.type==="welcome"){m(p);return}if(v.type==="bye"){T(p);return}let b=m(p),E=b.pc;if(v.type==="desc"){let M=v.description,N=M.type==="offer"&&(b.makingOffer||E.signalingState!=="stable");if(b.ignoreOffer=!b.polite&&N,b.ignoreOffer)return;await E.setRemoteDescription(M),M.type==="offer"&&(await E.setLocalDescription(),C({type:"desc",to:p,description:E.localDescription}))}else if(v.type==="ice")try{await E.addIceCandidate(v.candidate)}catch(M){b.ignoreOffer||(i.value=M)}}g.on("message",S=>{_(S)}),g.on("open",()=>{C({type:"hello"})});async function A(S){if(S)for(let{pc:v}of c.values()){let p=v.getSenders().find(b=>b.track?.kind==="video");p&&await p.replaceTrack(S)}}async function K(){await A(a),s.value=!1}async function Q(){let v=(await navigator.mediaDevices.getDisplayMedia({video:!0})).getVideoTracks()[0];await A(v),v.onended=()=>{K()},s.value=!0}return{status:t,localStream:r,peers:o,screenSharing:s,error:i,id:l,shareScreen:Q,stopScreen:K,toggleAudio(S){let v=u?.getAudioTracks()[0];return v?(v.enabled=S??!v.enabled,v.enabled):!1},toggleVideo(S){let v=u?.getVideoTracks()[0];return v?(v.enabled=S??!v.enabled,v.enabled):!1},leave(){C({type:"bye"});for(let S of[...c.keys()])T(S);if(u)for(let S of u.getTracks())S.stop();g.close(),t.value="closed"}}}function vt(e,n={}){let t=k([]),r=k([]),o=k([]),s=new Map,i=n.typingTimeout??3e3,l=n.url??"/ws/chat",d=Ae(l.includes("{channel}")?l.replace("{channel}",String(e)):`${l}/${e}`),f=W.connect(d,{token:n.token});function h(a){o.value.includes(a)||(o.value=[...o.value,a]);let c=s.get(a);c&&clearTimeout(c),s.set(a,setTimeout(()=>{o.value=o.value.filter(g=>g!==a),s.delete(a)},i))}f.on("message",a=>{let c=a;switch(c.type){case"message":t.value=[...t.value,c.message];break;case"presence":c.event==="roster"?r.value=c.users??[]:c.event==="join"&&c.user_id?r.value=[...new Set([...r.value,c.user_id])]:c.event==="leave"&&(r.value=r.value.filter(g=>g!==c.user_id));break;case"typing":c.user_id&&h(c.user_id);break}});let w=n.apiBase??"",u=n.messagesPath??"/api/channels/{id}/messages";return{status:f.status,connected:f.connected,messages:t,presence:r,typing:o,send(a,c){f.send({type:"message",body:a,thread_id:c??null})},sendTyping(){f.send({type:"typing"})},markRead(){f.send({type:"read"})},async history(a,c=50){let g=u.replace("{id}",String(e)),y=new URLSearchParams({limit:String(c)});a&&y.set("before",String(a));let C={};n.token&&(C.Authorization=`Bearer ${n.token}`);let m=await fetch(`${w}${g}?${y}`,{headers:C});if(!m.ok)throw new Error(`[tina4] chat history failed: ${m.status}`);let T=await m.json(),_=[...T].reverse();return t.value=[..._,...t.value],T},close(){for(let a of s.values())clearTimeout(a);s.clear(),f.close()}}}async function yt(e,n,t={}){let r=t.filesPath??"/api/files",o=new FormData;o.append("channel_id",String(e)),o.append("file",n,n.name??"file");let s={};t.token&&(s.Authorization=`Bearer ${t.token}`);let i=await fetch(`${t.apiBase??""}${r}`,{method:"POST",body:o,headers:s});if(!i.ok)throw new Error(`[tina4] file upload failed: ${i.status}`);return i.json()}async function bt(e,n={}){let t=/^https?:\/\//.test(e)?e:`${n.apiBase??""}${n.filesPath??"/api/files"}/${e}`,r={};n.token&&(r.Authorization=`Bearer ${n.token}`);let o=await fetch(t,{headers:r});if(!o.ok)throw new Error(`[tina4] file fetch failed: ${o.status}`);return URL.createObjectURL(await o.blob())}var Ie={config:Z,call:ht,chat:vt,upload:yt,fetchBlob:bt};var Pe={read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},Ne=/(token|password|passwd|secret|api[_-]?key|apikey|auth(?!or)|credential|jwt|bearer|otp|seed|private[_-]?key|session[_-]?id)/i,St=/^[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}$/,kt=/^[A-Za-z0-9+/_=-]{40,}$/,Le=new Set;function De(e,n){if(Ne.test(e))return`key name "${e}" looks like a credential`;if(typeof n=="string"){if(St.test(n))return"value looks like a JWT";if(n.length>=40&&kt.test(n))return"value looks like a long base64 / token"}if(n&&typeof n=="object"&&!Array.isArray(n)){for(let t of Object.keys(n))if(Ne.test(t))return`object contains a credential-shape field "${t}"`}return null}function Fe(e,n){Le.has(n)||(Le.add(n),console.warn(`[tina4 persist] ${e} (key: ${JSON.stringify(n)}). localStorage is XSS-readable and never appropriate for credentials, tokens, passwords, personal data, or secrets. See STORAGE.md.`))}function qe(e){if(typeof globalThis>"u")return null;try{let n=e==="local"?globalThis.localStorage:globalThis.sessionStorage;return!n||typeof n.getItem!="function"?null:n}catch{return null}}function Ue(e,n){let{key:t,storage:r="local",serializer:o=Pe,version:s=1,migrate:i,syncTabs:l=!1,silenceCredentialWarning:d=!1}=n;if(!t||typeof t!="string")throw new Error("[tina4 persist] options.key is required and must be a string");let f=o===Pe,h=qe(r);if(!h)return $e(e,()=>{},()=>{});try{let a=h.getItem(t);if(a!==null){let c,g;try{let y=JSON.parse(a);y&&typeof y=="object"&&"value"in y?(c=y.v,g=y.value):g=y}catch{g=f?a:o.read(a)}if(c===s||c===void 0){let y=f?g:o.read(typeof g=="string"?g:JSON.stringify(g));e.value=y}else if(i)try{e.value=i(g,c)}catch(y){console.warn(`[tina4 persist] migrate() threw for key "${t}":`,y)}else console.warn(`[tina4 persist] stored version ${c} does not match current ${s} for key "${t}", and no migrate() was provided. Discarding the stored value.`)}}catch(a){console.warn(`[tina4 persist] failed to read key "${t}":`,a)}if(!d){let a=De(t,e.peek());a&&Fe(a,t)}let w=R(()=>{let a=e.value;if(!d){let c=De(t,a);c&&Fe(c,t)}try{let g=JSON.stringify(f?{v:s,value:a}:{v:s,value:o.write(a)});h.setItem(t,g)}catch(c){console.warn(`[tina4 persist] failed to write key "${t}":`,c)}}),u=null;if(l&&typeof globalThis<"u"&&"addEventListener"in globalThis){let a=c=>{let g=c;if(g.storageArea===h&&g.key===t&&g.newValue!==null)try{let y=JSON.parse(g.newValue),C=y&&typeof y=="object"&&"v"in y?y.v:void 0,m=C!==void 0?y.value:y;C!==void 0&&C!==s&&i?e.value=i(m,C):(C===s||C===void 0)&&(e.value=f?m:o.read(typeof m=="string"?m:JSON.stringify(m)))}catch(y){console.warn(`[tina4 persist] failed to parse storage event for key "${t}":`,y)}};globalThis.addEventListener?.("storage",a),u=()=>{globalThis.removeEventListener?.("storage",a)}}return $e(e,()=>{try{h.removeItem(t)}catch(a){console.warn(`[tina4 persist] failed to clear key "${t}":`,a)}},()=>{w(),u&&u()})}function $e(e,n,t){return Object.assign(e,{clear:n,dispose:t})}function je(e,n="local"){let t=qe(n);if(t)for(let r of e)try{t.removeItem(r)}catch(o){console.warn(`[tina4 persist] failed to clear key "${r}":`,o)}}var wt=["ar","he","fa","ur","ps","dv","syr","ckb","yi"];function Tt(){return globalThis.navigator?.language||"en"}function He(e,n="",t={}){for(let[r,o]of Object.entries(e)){let s=n?`${n}.${r}`:r;if(o!==null&&typeof o=="object"&&!Array.isArray(o))He(o,s,t);else{let i=String(o);t[s]=i,r in t||(t[r]=i)}}return t}function Ct(e,n){return e.replace(/\{(\w+)\}/g,(t,r)=>Object.prototype.hasOwnProperty.call(n,r)?String(n[r]):t)}function le(e={}){let n=e.locale||Tt(),t=e.fallbackLocale||n,r=new Set([...wt,...e.rtlLocales||[]]),o=k(n,"i18n.locale"),s=new Map,i=new Map;function l(u,a){let c=He(a),g=s.get(u);s.set(u,g?{...g,...c}:c)}if(e.messages)for(let[u,a]of Object.entries(e.messages))l(u,a);function d(u,a){return s.get(u)?.[a]}function f(u,a){let c=`n|${u}|${JSON.stringify(a||{})}`,g=i.get(c);return g||(g=new Intl.NumberFormat(u,a),i.set(c,g)),g}function h(u,a){let c=`d|${u}|${JSON.stringify(a||{})}`,g=i.get(c);return g||(g=new Intl.DateTimeFormat(u,a),i.set(c,g)),g}function w(u,a){let c=`r|${u}|${JSON.stringify(a||{})}`,g=i.get(c);return g||(g=new Intl.RelativeTimeFormat(u,a),i.set(c,g)),g}return{locale:o,t(u,a){let c=o.value,g=d(c,u);return g===void 0&&t!==c&&(g=d(t,u)),g===void 0&&(g=u),a?Ct(g,a):g},setLocale(u){o.value=u},getLocale(){return o.value},addMessages:l,hasLocale(u){return s.has(u)},availableLocales(){return[...s.keys()].sort()},async loadMessages(u,a){let c=await fetch(a);if(!c.ok)throw new Error(`[tina4 i18n] failed to load "${u}" from ${a}: ${c.status}`);l(u,await c.json())},number(u,a){return f(o.value,a).format(u)},currency(u,a,c){return f(o.value,{style:"currency",currency:a,...c}).format(u)},date(u,a){let c=u instanceof Date?u:new Date(u);return h(o.value,a).format(c)},relativeTime(u,a,c){return w(o.value,c||{numeric:"auto"}).format(u,a)},isRTL(){return r.has(o.value.split("-")[0].toLowerCase())},dir(){return this.isRTL()?"rtl":"ltr"}}}var We=le();return Xe(Et);})();
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/gallery/error-overlay/meta.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/public/images/tina4-logo-icon.webp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/public/js/tina4-dev-admin.min.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/public/swagger/oauth2-redirect.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/queue_backends/kafka_backend.py
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/queue_backends/mongo_backend.py
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/queue_backends/rabbitmq_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/realtime/models/ChannelMember.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/session_handlers/memcached_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/session_handlers/redis_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/session_handlers/valkey_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/docker/poetry/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/templates/docker/python/Dockerfile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.107 → tina4_python-3.13.109}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|