Files
securebit-chat/.htaccess
T
lockbitchat 98d42ac2fb
CodeQL Analysis / Analyze CodeQL (push) Canceled after 0s
Deploy Application / deploy (push) Canceled after 0s
Mirror to Codeberg / mirror (push) Canceled after 0s
Mirror to PrivacyGuides / mirror (push) Canceled after 0s
feat(i18n): nine languages, each at its own address; release v6.3.0
The site now speaks German, French, Spanish, Ukrainian, Russian, Chinese,
Korean and Hindi alongside English — 623 strings per language, 5,607
translations, covering the landing page, the key exchange, the chat, group
calls and every error along the way.

Each locale is a real page at a real URL (/de/, /fr/, …), generated at build
time from locales/*.json. That is the whole point: the app is client-rendered,
so a language that only swaps strings at runtime has no address for a crawler
to index and no link anyone can share. Each page carries its own canonical, a
reciprocal hreflang cluster and translated schema.org.

The URL decides the language, always. A stored preference only applies where
the address does not say, and nothing ever redirects on Accept-Language —
that is how sites become invisible to search engines outside one country.

robots.txt and sitemap.xml did not exist before; both are generated now.

Bugs found on the way, each with a test that would have caught it:
  - partner logos used page-relative paths and 404'd from any /xx/ page
  - post-build stamped ?v= only into the root shell, leaving locales behind
  - "Back online" appeared on every page load, not just after being offline
  - update timestamps were hard-coded to US format for every reader
  - t() threw on a partial window, taking whole components down with it
2026-08-29 12:46:15 -04:00

249 lines
8.3 KiB
ApacheConf

# SecureBit.chat - Apache Configuration
# Comprehensive caching configuration for forced updates
# ============================================
# MIME TYPES - MUST BE FIRST (before other rules)
# ============================================
# Critical: Set MIME types BEFORE any other rules to ensure correct Content-Type headers
<IfModule mod_mime.c>
# JavaScript modules - explicit order matters
AddType application/javascript .jsx
AddType application/javascript .mjs
AddType application/javascript .js
AddType application/json .json
# Fonts
AddType font/woff .woff
AddType font/woff2 .woff2
AddType application/font-woff .woff
AddType application/font-woff2 .woff2
# Service Worker
AddType application/manifest+json .webmanifest
</IfModule>
# Force Content-Type headers (override any server defaults)
<IfModule mod_headers.c>
# All JavaScript files including JSX - CRITICAL for ES modules
<FilesMatch "\.(js|mjs|jsx)$">
Header always set Content-Type "application/javascript; charset=utf-8"
</FilesMatch>
</IfModule>
# Enable mod_rewrite
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
</IfModule>
# ============================================
# CRITICAL FILES - NO CACHING
# ============================================
# meta.json - versioning file (never cache)
<FilesMatch "meta\.json$">
<IfModule mod_headers.c>
Header set Cache-Control "no-cache, no-store, must-revalidate, max-age=0"
Header set Pragma "no-cache"
Header set Expires "0"
Header set X-Content-Type-Options "nosniff"
</IfModule>
</FilesMatch>
# HTML files - always fresh
<FilesMatch "\.(html|htm)$">
<IfModule mod_headers.c>
Header set Cache-Control "no-cache, no-store, must-revalidate, max-age=0"
Header set Pragma "no-cache"
Header set Expires "0"
# Remove ETag for validation
Header unset ETag
FileETag None
</IfModule>
</FilesMatch>
# Service Worker - no cache
<FilesMatch "sw\.js$">
<IfModule mod_headers.c>
Header set Cache-Control "no-cache, no-store, must-revalidate, max-age=0"
Header set Pragma "no-cache"
Header set Expires "0"
Header set Service-Worker-Allowed "/"
</IfModule>
</FilesMatch>
# manifest.json - no cache
<FilesMatch "manifest\.json$">
<IfModule mod_headers.c>
Header set Cache-Control "no-cache, no-store, must-revalidate, max-age=0"
Header set Pragma "no-cache"
Header set Expires "0"
</IfModule>
</FilesMatch>
# ============================================
# STATIC RESOURCES - AGGRESSIVE CACHING
# ============================================
# JavaScript files in dist/ - no cache (for updates)
<FilesMatch "^dist/.*\.(js|mjs)$">
<IfModule mod_headers.c>
Header set Cache-Control "no-cache, no-store, must-revalidate, max-age=0"
Header set Pragma "no-cache"
Header set Expires "0"
Header set X-Content-Type-Options "nosniff"
</IfModule>
</FilesMatch>
# JavaScript files with hashes in other locations - long cache
<FilesMatch "\.(js|mjs)$">
<IfModule mod_headers.c>
# Files with hashes in name - cache for one year
Header set Cache-Control "public, max-age=31536000, immutable"
Header set X-Content-Type-Options "nosniff"
</IfModule>
</FilesMatch>
# The dictionary is imported by a bare path from a plain browser module, so nothing
# busts its cache. It must revalidate or translations freeze at whatever a visitor
# first loaded.
<FilesMatch "^(index|generated)\.js$">
<IfModule mod_headers.c>
Header set Cache-Control "no-cache, must-revalidate"
</IfModule>
</FilesMatch>
# CSS files - long cache
<FilesMatch "\.css$">
<IfModule mod_headers.c>
Header set Cache-Control "public, max-age=31536000, immutable"
</IfModule>
</FilesMatch>
# Images - long cache
<FilesMatch "\.(jpg|jpeg|png|gif|webp|svg|ico)$">
<IfModule mod_headers.c>
Header set Cache-Control "public, max-age=31536000, immutable"
</IfModule>
</FilesMatch>
# Fonts - long cache
<FilesMatch "\.(woff|woff2|ttf|otf|eot)$">
<IfModule mod_headers.c>
Header set Cache-Control "public, max-age=31536000, immutable"
Header set Access-Control-Allow-Origin "*"
</IfModule>
</FilesMatch>
# Audio/Video - long cache
<FilesMatch "\.(mp3|mp4|webm|ogg)$">
<IfModule mod_headers.c>
Header set Cache-Control "public, max-age=31536000, immutable"
</IfModule>
</FilesMatch>
# Crawler-facing files - short cache so search engines re-read them
<FilesMatch "^(robots\.txt|sitemap\.xml)$">
<IfModule mod_headers.c>
Header set Cache-Control "public, max-age=3600"
</IfModule>
</FilesMatch>
# ============================================
# SECURITY
# ============================================
# XSS Protection
<IfModule mod_headers.c>
Header set X-XSS-Protection "1; mode=block"
Header set X-Content-Type-Options "nosniff"
Header set Referrer-Policy "strict-origin-when-cross-origin"
Header set X-Frame-Options "DENY"
# Force HTTPS (2 years + preload) to close the first-visit SSL-strip window.
Header set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
# Restrict powerful features; camera + microphone kept for QR scanning and calls.
Header set Permissions-Policy "camera=(self), microphone=(self), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()"
</IfModule>
# Content Security Policy (frame-ancestors and report-uri only work in HTTP headers, not meta tags)
<IfModule mod_headers.c>
Header set Content-Security-Policy "frame-ancestors 'none'; report-uri /csp-report; report-to csp-endpoint;"
</IfModule>
# ============================================
# GZIP COMPRESSION
# ============================================
<IfModule mod_deflate.c>
# Compress text files
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/json application/xml
# Compress fonts
AddOutputFilterByType DEFLATE font/woff font/woff2 application/font-woff application/font-woff2
</IfModule>
# ============================================
# CLOUDFLARE RULES
# ============================================
# Cloudflare can cache static files, but should not cache:
# - meta.json
# - index.html
# - sw.js
# - manifest.json
# These rules are applied at Cloudflare Page Rules level
# (see CLOUDFLARE_SETUP.md documentation)
# ============================================
# SPA FALLBACK
# ============================================
# If file not found, redirect to index.html (for SPA routing)
<IfModule mod_rewrite.c>
# Locale shells first: a deep link under /de/ must fall back to the German page,
# not the English one. Rewritten by scripts/build-i18n.js from locales/site.json.
# BEGIN generated locale shells
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^de(/.*)?$ /de/index.html [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^fr(/.*)?$ /fr/index.html [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^es(/.*)?$ /es/index.html [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^uk(/.*)?$ /uk/index.html [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ru(/.*)?$ /ru/index.html [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^zh(/.*)?$ /zh/index.html [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ko(/.*)?$ /ko/index.html [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^hi(/.*)?$ /hi/index.html [L]
# END generated locale shells
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/meta\.json$
RewriteCond %{REQUEST_URI} !^/sw\.js$
RewriteCond %{REQUEST_URI} !^/manifest\.json$
RewriteRule ^(.*)$ /index.html [L]
</IfModule>
# ============================================
# LOGGING (optional)
# ============================================
# Uncomment for debugging
# LogLevel rewrite:trace3