tina4-python 3.13.107__tar.gz → 3.13.108__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.
Files changed (163) hide show
  1. {tina4_python-3.13.107 → tina4_python-3.13.108}/PKG-INFO +3 -4
  2. {tina4_python-3.13.107 → tina4_python-3.13.108}/README.md +2 -3
  3. {tina4_python-3.13.107 → tina4_python-3.13.108}/pyproject.toml +1 -1
  4. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/__init__.py +1 -1
  5. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/cache/__init__.py +35 -0
  6. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/database/adapter.py +58 -13
  7. tina4_python-3.13.108/tina4_python/public/js/tina4js.min.js +48 -0
  8. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/service/__init__.py +10 -0
  9. tina4_python-3.13.107/tina4_python/public/js/tina4js.min.js +0 -48
  10. {tina4_python-3.13.107 → tina4_python-3.13.108}/.gitignore +0 -0
  11. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/CLAUDE.md +0 -0
  12. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/HtmlElement.py +0 -0
  13. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/Testing.py +0 -0
  14. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/ai/__init__.py +0 -0
  15. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/ai/client.py +0 -0
  16. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/api/__init__.py +0 -0
  17. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/auth/__init__.py +0 -0
  18. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/cli/__init__.py +0 -0
  19. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/container/__init__.py +0 -0
  20. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/context/__init__.py +0 -0
  21. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/context/chunker.py +0 -0
  22. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/core/__init__.py +0 -0
  23. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/core/cache.py +0 -0
  24. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/core/constants.py +0 -0
  25. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/core/events.py +0 -0
  26. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/core/middleware.py +0 -0
  27. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/core/port_takeover.py +0 -0
  28. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/core/rate_limiter.py +0 -0
  29. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/core/request.py +0 -0
  30. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/core/response.py +0 -0
  31. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/core/router.py +0 -0
  32. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/core/server.py +0 -0
  33. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/crud/__init__.py +0 -0
  34. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/database/__init__.py +0 -0
  35. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/database/connection.py +0 -0
  36. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/database/database_url.py +0 -0
  37. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/database/firebird.py +0 -0
  38. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/database/mongodb.py +0 -0
  39. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/database/mssql.py +0 -0
  40. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/database/mysql.py +0 -0
  41. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/database/odbc.py +0 -0
  42. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/database/postgres.py +0 -0
  43. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/database/sql_translator.py +0 -0
  44. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/database/sqlite.py +0 -0
  45. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/debug/__init__.py +0 -0
  46. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/debug/error_overlay.py +0 -0
  47. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/dev_admin/__init__.py +0 -0
  48. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/dev_admin/metrics.py +0 -0
  49. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/dev_admin/plan.py +0 -0
  50. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/dev_admin/project_index.py +0 -0
  51. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/docs.py +0 -0
  52. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/docstore/__init__.py +0 -0
  53. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/dotenv/__init__.py +0 -0
  54. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/env.py +0 -0
  55. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/frond/FROND.md +0 -0
  56. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/frond/__init__.py +0 -0
  57. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/frond/compiler.py +0 -0
  58. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/frond/engine.py +0 -0
  59. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/frond/parser.py +0 -0
  60. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/gallery/auth/meta.json +0 -0
  61. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  62. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/gallery/database/meta.json +0 -0
  63. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  64. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/gallery/error-overlay/meta.json +0 -0
  65. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  66. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/gallery/orm/meta.json +0 -0
  67. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  68. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  69. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/gallery/queue/meta.json +0 -0
  70. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  71. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/gallery/rest-api/meta.json +0 -0
  72. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  73. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/gallery/templates/meta.json +0 -0
  74. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  75. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  76. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/graphql/__init__.py +0 -0
  77. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/i18n/__init__.py +0 -0
  78. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/mcp/__init__.py +0 -0
  79. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/mcp/protocol.py +0 -0
  80. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/mcp/tools.py +0 -0
  81. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/messenger/__init__.py +0 -0
  82. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/migration/__init__.py +0 -0
  83. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/migration/runner.py +0 -0
  84. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/mqtt/__init__.py +0 -0
  85. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/mqtt/message.py +0 -0
  86. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/orm/__init__.py +0 -0
  87. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/orm/fields.py +0 -0
  88. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/orm/model.py +0 -0
  89. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/orm/point.py +0 -0
  90. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/public/__feedback/widget.js +0 -0
  91. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/public/css/tina4.css +0 -0
  92. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/public/css/tina4.min.css +0 -0
  93. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/public/favicon.ico +0 -0
  94. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/public/images/logo.svg +0 -0
  95. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  96. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/public/js/frond.js +0 -0
  97. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/public/js/frond.min.js +0 -0
  98. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  99. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/public/js/tina4.min.js +0 -0
  100. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/public/swagger/index.html +0 -0
  101. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  102. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/query_builder/__init__.py +0 -0
  103. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/queue/__init__.py +0 -0
  104. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/queue/amqp_url.py +0 -0
  105. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/queue/job.py +0 -0
  106. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/queue/kafka_backend.py +0 -0
  107. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/queue/lite_backend.py +0 -0
  108. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/queue/mongo_backend.py +0 -0
  109. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/queue/rabbitmq_backend.py +0 -0
  110. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/queue_backends/__init__.py +0 -0
  111. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/queue_backends/kafka_backend.py +0 -0
  112. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/queue_backends/mongo_backend.py +0 -0
  113. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  114. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/realtime/__init__.py +0 -0
  115. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/realtime/models/Attachment.py +0 -0
  116. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/realtime/models/Channel.py +0 -0
  117. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/realtime/models/ChannelMember.py +0 -0
  118. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/realtime/models/Message.py +0 -0
  119. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/realtime/models/Workspace.py +0 -0
  120. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/realtime/models/__init__.py +0 -0
  121. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/realtime/storage.py +0 -0
  122. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/seeder/__init__.py +0 -0
  123. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/session/__init__.py +0 -0
  124. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/session_handlers/__init__.py +0 -0
  125. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/session_handlers/memcached_handler.py +0 -0
  126. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  127. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/session_handlers/redis_handler.py +0 -0
  128. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/session_handlers/valkey_handler.py +0 -0
  129. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/sso.py +0 -0
  130. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/swagger/__init__.py +0 -0
  131. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/templates/components/crud.twig +0 -0
  132. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  133. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  134. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/templates/docker/python/Dockerfile +0 -0
  135. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  136. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/templates/errors/302.twig +0 -0
  137. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/templates/errors/401.twig +0 -0
  138. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/templates/errors/403.twig +0 -0
  139. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/templates/errors/404.twig +0 -0
  140. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/templates/errors/500.twig +0 -0
  141. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/templates/errors/502.twig +0 -0
  142. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/templates/errors/503.twig +0 -0
  143. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/templates/errors/base.twig +0 -0
  144. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/templates/frontend/README.md +0 -0
  145. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/templates/readme.md +0 -0
  146. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/test/__init__.py +0 -0
  147. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/test_client/__init__.py +0 -0
  148. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  149. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  150. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  151. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  152. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  153. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  154. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  155. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  156. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  157. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  158. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  159. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  160. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/validator/__init__.py +0 -0
  161. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/websocket/__init__.py +0 -0
  162. {tina4_python-3.13.107 → tina4_python-3.13.108}/tina4_python/websocket/backplane.py +0 -0
  163. {tina4_python-3.13.107 → tina4_python-3.13.108}/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.107
3
+ Version: 3.13.108
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
- 98 built-in features. Zero dependencies. One import, everything works.
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/badge/tests-2%2C281%20passing-brightgreen" alt="Tests">
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
- 98 built-in features. Zero dependencies. One import, everything works.
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/badge/tests-2%2C281%20passing-brightgreen" alt="Tests">
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>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tina4-python"
3
- version = "3.13.107"
3
+ version = "3.13.108"
4
4
  description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
@@ -51,7 +51,7 @@ def _resolve_version() -> str:
51
51
  #
52
52
  # test_version_constant.py now asserts this literal equals the pyproject
53
53
  # version, so the release bump cannot leave it behind again.
54
- return "3.13.107"
54
+ return "3.13.108"
55
55
 
56
56
 
57
57
  __version__ = _resolve_version()
@@ -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 value, rounded UP to whole seconds.
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. Rounding UP matters: a driver that fired EARLY — at 2s for a
70
- configured 2.5s — would raise its own raw error before our bound was
71
- reached, and the caller would see a bare driver message instead of one
72
- naming the variable that caused it.
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.ceil(seconds))
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
- The invariant that makes this work: the driver's option is always >= our
109
- bound (:func:`driver_connect_timeout_seconds` rounds UP), and our clock
110
- starts BEFORE the call, so the driver's own timeout cannot expire before
111
- ``elapsed`` has reached our bound. Break either half and a bare driver
112
- message reaches the caller instead of ours.
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
- elapsed = time.monotonic() - started
120
- if seconds is not None and elapsed >= seconds:
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);})();