tina4-python 3.13.56__tar.gz → 3.13.57__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.56 → tina4_python-3.13.57}/PKG-INFO +1 -1
- {tina4_python-3.13.56 → tina4_python-3.13.57}/pyproject.toml +1 -1
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/__init__.py +7 -1
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/core/server.py +24 -4
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/orm/model.py +6 -1
- tina4_python-3.13.57/tina4_python/public/js/tina4js.min.js +48 -0
- tina4_python-3.13.57/tina4_python/realtime/__init__.py +522 -0
- tina4_python-3.13.57/tina4_python/realtime/models/Attachment.py +26 -0
- tina4_python-3.13.57/tina4_python/realtime/models/Channel.py +19 -0
- tina4_python-3.13.57/tina4_python/realtime/models/ChannelMember.py +20 -0
- tina4_python-3.13.57/tina4_python/realtime/models/Message.py +21 -0
- tina4_python-3.13.57/tina4_python/realtime/models/Workspace.py +12 -0
- tina4_python-3.13.57/tina4_python/realtime/models/__init__.py +22 -0
- tina4_python-3.13.57/tina4_python/realtime/storage.py +183 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/test_client/__init__.py +11 -0
- tina4_python-3.13.56/tina4_python/public/js/tina4js.min.js +0 -48
- {tina4_python-3.13.56 → tina4_python-3.13.57}/.gitignore +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/README.md +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/core/request.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/core/response.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/core/router.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/docstore/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/env.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/public/js/tina4-dev-admin.js +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.56 → tina4_python-3.13.57}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -8,7 +8,7 @@ Tina4 Python v3.0 — Zero-dependency, lightweight web framework.
|
|
|
8
8
|
|
|
9
9
|
One import, everything works.
|
|
10
10
|
"""
|
|
11
|
-
__version__ = "3.13.
|
|
11
|
+
__version__ = "3.13.57"
|
|
12
12
|
|
|
13
13
|
# ── Route decorators ──
|
|
14
14
|
from tina4_python.core.router import ( # noqa: E402, F401
|
|
@@ -83,6 +83,12 @@ from tina4_python.core.events import on, emit, once, off # noqa: E402, F401
|
|
|
83
83
|
# ── Email (Messenger) ──
|
|
84
84
|
from tina4_python.messenger import Messenger # noqa: E402, F401
|
|
85
85
|
|
|
86
|
+
# ── Real-time collaboration (WebRTC signalling + chat + files control plane) ──
|
|
87
|
+
from tina4_python.realtime import realtime, RtcMediaBackend, MeshBackend # noqa: E402, F401
|
|
88
|
+
from tina4_python.realtime.storage import ( # noqa: E402, F401
|
|
89
|
+
StorageBackend, LocalStorage, S3Storage,
|
|
90
|
+
)
|
|
91
|
+
|
|
86
92
|
# ── Inline testing (@tests + assertions for inline test cases) ──
|
|
87
93
|
# Class-based xUnit testing lives in tina4_python.test (a separate module).
|
|
88
94
|
# Keep both surfaces re-exported so users can write either style.
|
|
@@ -919,7 +919,7 @@ class _AsgiWebSocketConnection:
|
|
|
919
919
|
pass
|
|
920
920
|
|
|
921
921
|
|
|
922
|
-
async def _handle_dev_websocket(reader, writer, headers, path):
|
|
922
|
+
async def _handle_dev_websocket(reader, writer, headers, path, query_string: str = ""):
|
|
923
923
|
"""Handle WebSocket upgrade in the built-in dev server, dispatching to registered routes."""
|
|
924
924
|
route, params = Router.match_ws(path)
|
|
925
925
|
if route is None:
|
|
@@ -944,18 +944,38 @@ async def _handle_dev_websocket(reader, writer, headers, path):
|
|
|
944
944
|
writer.close()
|
|
945
945
|
return
|
|
946
946
|
|
|
947
|
-
#
|
|
947
|
+
# Per-route auth: a @secured() WS route needs a valid JWT on the upgrade
|
|
948
|
+
# (Authorization header, "bearer" subprotocol, or ?token=). Public by default.
|
|
949
|
+
# (This mirrors the ASGI path — the built-in server used to skip it, which
|
|
950
|
+
# both left secured WS routes unauthenticated AND left conn.auth unset.)
|
|
951
|
+
_ws_subproto = headers.get("sec-websocket-protocol", "")
|
|
952
|
+
_ws_payload, _ws_ok = ws_authorized(route, headers, query_string, _ws_subproto)
|
|
953
|
+
if not _ws_ok:
|
|
954
|
+
writer.write(b"HTTP/1.1 401 Unauthorized\r\n\r\n")
|
|
955
|
+
await writer.drain()
|
|
956
|
+
writer.close()
|
|
957
|
+
return
|
|
958
|
+
|
|
959
|
+
# Send upgrade response. Echo the `bearer` subprotocol when the client
|
|
960
|
+
# offered it (a browser sends the JWT as `Sec-WebSocket-Protocol: bearer,
|
|
961
|
+
# <jwt>` since it can't set an Authorization header); a browser fails the
|
|
962
|
+
# handshake if the server doesn't select one of the offered subprotocols.
|
|
948
963
|
accept = compute_accept_key(ws_key)
|
|
964
|
+
_proto_header = ""
|
|
965
|
+
if any(p.strip().lower() == "bearer" for p in _ws_subproto.split(",")):
|
|
966
|
+
_proto_header = "Sec-WebSocket-Protocol: bearer\r\n"
|
|
949
967
|
response_data = (
|
|
950
968
|
f"HTTP/1.1 101 Switching Protocols\r\n"
|
|
951
969
|
f"Upgrade: websocket\r\n"
|
|
952
970
|
f"Connection: Upgrade\r\n"
|
|
953
|
-
f"Sec-WebSocket-Accept: {accept}\r\n
|
|
971
|
+
f"Sec-WebSocket-Accept: {accept}\r\n"
|
|
972
|
+
f"{_proto_header}\r\n"
|
|
954
973
|
)
|
|
955
974
|
writer.write(response_data.encode())
|
|
956
975
|
await writer.drain()
|
|
957
976
|
|
|
958
977
|
ws = WebSocketConnection(reader, writer, path, headers, params)
|
|
978
|
+
ws.auth = _ws_payload
|
|
959
979
|
_ws_manager.add(ws)
|
|
960
980
|
|
|
961
981
|
handler = route["handler"]
|
|
@@ -2591,7 +2611,7 @@ def run(host: str | None = None, port: int | None = None, no_browser: bool = Fal
|
|
|
2591
2611
|
await writer.drain()
|
|
2592
2612
|
writer.close()
|
|
2593
2613
|
return
|
|
2594
|
-
await _handle_dev_websocket(reader, writer, _header_dict, path)
|
|
2614
|
+
await _handle_dev_websocket(reader, writer, _header_dict, path, qs)
|
|
2595
2615
|
return
|
|
2596
2616
|
|
|
2597
2617
|
# Read body
|
|
@@ -907,7 +907,12 @@ class ORM(metaclass=ORMMeta):
|
|
|
907
907
|
parts.append("AUTOINCREMENT")
|
|
908
908
|
if field_obj.required and not field_obj.primary_key:
|
|
909
909
|
parts.append("NOT NULL")
|
|
910
|
-
|
|
910
|
+
# Callable defaults (e.g. DateTimeField(default=lambda: datetime.now())) are
|
|
911
|
+
# resolved per-row at insert time (_resolve_default, issue #50); they must NOT
|
|
912
|
+
# be emitted into the CREATE TABLE DDL, where they stringify to an invalid
|
|
913
|
+
# `DEFAULT <function ...>` and silently fail table creation.
|
|
914
|
+
if field_obj.default is not None and not field_obj.auto_increment \
|
|
915
|
+
and kind != "JSONField" and not callable(field_obj.default):
|
|
911
916
|
default_val = field_obj.default
|
|
912
917
|
if isinstance(default_val, str):
|
|
913
918
|
parts.append(f"DEFAULT '{default_val}'")
|
|
@@ -0,0 +1,48 @@
|
|
|
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);})();
|