webapp: add hashed minified asset pipeline
This commit is contained in:
@@ -52,6 +52,7 @@ _UNPATCHED_WIDGET_ORIGIN_SNIPPET = """ if (origin == 'https://telegram.org')
|
||||
_PATCHED_WIDGET_ORIGIN_SNIPPET = """ if (origin == 'https://telegram.org') {\n origin = default_origin;\n } else if (origin == 'https://telegram-js.azureedge.net' || origin == 'https://tg.dev') {\n origin = dev_origin;\n } else {\n origin = default_origin;\n }\n"""
|
||||
WEBAPP_CONFIG_PLACEHOLDER = "<!-- WEBAPP_CONFIG_SCRIPT -->"
|
||||
WEBAPP_I18N_PLACEHOLDER = "<!-- WEBAPP_I18N_SCRIPT -->"
|
||||
WEBAPP_JS_PLACEHOLDER = "<!-- WEBAPP_JS_SCRIPT -->"
|
||||
DEV_MOCK_START_MARKER = "<!-- WEBAPP_DEV_MOCK_START -->"
|
||||
DEV_MOCK_END_MARKER = "<!-- WEBAPP_DEV_MOCK_END -->"
|
||||
WEBAPP_RATE_LIMIT_WINDOW_SECONDS = 60
|
||||
@@ -117,6 +118,7 @@ def setup_subscription_webapp_routes(app: web.Application) -> None:
|
||||
app.router.add_get("/telegram-widget.js", telegram_widget_asset_route)
|
||||
app.router.add_get(WEBAPP_LOGO_PROXY_PATH, webapp_logo_route)
|
||||
app.router.add_get("/subscription_webapp.css", css_asset_route)
|
||||
app.router.add_get("/subscription_webapp.min.{asset_hash}.js", js_asset_route)
|
||||
app.router.add_get("/subscription_webapp.js", js_asset_route)
|
||||
app.router.add_post("/api/auth/token", auth_token_route)
|
||||
app.router.add_post("/api/auth/email/request", email_auth_request_route)
|
||||
@@ -523,12 +525,22 @@ def _normalize_telegram_login_widget_sdk(data: bytes) -> bytes:
|
||||
|
||||
|
||||
async def js_asset_route(request: web.Request) -> web.Response:
|
||||
return await _serve_template_asset(
|
||||
request,
|
||||
"subscription_webapp.js",
|
||||
"application/javascript",
|
||||
strip_dev_mock=True,
|
||||
asset_hash = request.match_info.get("asset_hash")
|
||||
filename = (
|
||||
f"subscription_webapp.min.{asset_hash}.js"
|
||||
if asset_hash
|
||||
else "subscription_webapp.js"
|
||||
)
|
||||
response = await _serve_template_asset(
|
||||
request,
|
||||
filename,
|
||||
"application/javascript",
|
||||
strip_dev_mock=not asset_hash,
|
||||
)
|
||||
response.headers["Cache-Control"] = (
|
||||
"public, max-age=31536000, immutable" if asset_hash else "no-cache"
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
async def index_route(request: web.Request) -> web.Response:
|
||||
@@ -571,6 +583,10 @@ async def index_route(request: web.Request) -> web.Response:
|
||||
+ "</script>"
|
||||
),
|
||||
)
|
||||
html = html.replace(
|
||||
WEBAPP_JS_PLACEHOLDER,
|
||||
f'<script src="./{_resolve_webapp_js_asset_name()}" defer></script>',
|
||||
)
|
||||
return web.Response(text=html, content_type="text/html", charset="utf-8")
|
||||
|
||||
|
||||
@@ -596,6 +612,19 @@ async def _serve_template_asset(
|
||||
return web.Response(text=text, content_type=content_type, charset="utf-8")
|
||||
|
||||
|
||||
def _resolve_webapp_js_asset_name() -> str:
|
||||
minified_assets = []
|
||||
for path in ASSET_DIR.glob("subscription_webapp.min.*.js"):
|
||||
try:
|
||||
minified_assets.append((path.stat().st_mtime, path.name))
|
||||
except OSError:
|
||||
continue
|
||||
if minified_assets:
|
||||
minified_assets.sort(reverse=True)
|
||||
return minified_assets[0][1]
|
||||
return "subscription_webapp.js"
|
||||
|
||||
|
||||
def _strip_marked_block(html: str, start_marker: str, end_marker: str) -> str:
|
||||
start = html.find(start_marker)
|
||||
if start == -1:
|
||||
@@ -667,7 +696,7 @@ async def auth_token_route(request: web.Request) -> web.Response:
|
||||
await session.commit()
|
||||
except Exception as exc:
|
||||
await session.rollback()
|
||||
logger.error("WebApp auth failed: %s", exc, exc_info=True)
|
||||
logger.exception("WebApp auth failed")
|
||||
return _json_error(500, "auth_failed", "Auth failed")
|
||||
|
||||
token = create_webapp_session_token(settings, int(authenticated_user_id))
|
||||
@@ -759,7 +788,7 @@ async def email_auth_verify_route(request: web.Request) -> web.Response:
|
||||
await session.commit()
|
||||
except Exception as exc:
|
||||
await session.rollback()
|
||||
logger.error("Email WebApp auth failed: %s", exc, exc_info=True)
|
||||
logger.exception("Email WebApp auth failed")
|
||||
return _json_error(500, "auth_failed", "Auth failed")
|
||||
|
||||
token = create_webapp_session_token(settings, int(db_user.user_id))
|
||||
@@ -919,7 +948,7 @@ async def account_email_verify_route(request: web.Request) -> web.Response:
|
||||
return _json_error(409, "account_merge_conflict", str(exc))
|
||||
except Exception as exc:
|
||||
await session.rollback()
|
||||
logger.error("Email account link failed: %s", exc, exc_info=True)
|
||||
logger.exception("Email account link failed")
|
||||
return _json_error(500, "link_failed", "Link failed")
|
||||
|
||||
token = create_webapp_session_token(settings, int(final_user_id))
|
||||
@@ -1043,7 +1072,7 @@ async def account_telegram_link_route(request: web.Request) -> web.Response:
|
||||
return _json_error(409, "account_merge_conflict", str(exc))
|
||||
except Exception as exc:
|
||||
await session.rollback()
|
||||
logger.error("Telegram account link failed: %s", exc, exc_info=True)
|
||||
logger.exception("Telegram account link failed")
|
||||
return _json_error(500, "link_failed", "Link failed")
|
||||
|
||||
token = create_webapp_session_token(settings, int(final_user_id))
|
||||
@@ -1106,7 +1135,7 @@ async def apply_promo_route(request: web.Request) -> web.Response:
|
||||
)
|
||||
except Exception as exc:
|
||||
await session.rollback()
|
||||
logger.error("WebApp promo apply failed: %s", exc, exc_info=True)
|
||||
logger.exception("WebApp promo apply failed")
|
||||
return _json_error(500, "promo_apply_failed", "Promo apply failed")
|
||||
|
||||
|
||||
@@ -1241,7 +1270,7 @@ async def _request_email_code(
|
||||
return web.json_response({"ok": True})
|
||||
except Exception as exc:
|
||||
await session.rollback()
|
||||
logger.error("Failed to send email verification code: %s", exc, exc_info=True)
|
||||
logger.exception("Failed to send email verification code")
|
||||
return _json_error(502, "email_send_failed", "Failed to send email")
|
||||
|
||||
|
||||
@@ -2015,7 +2044,7 @@ async def _create_yookassa_payment(
|
||||
)
|
||||
except Exception as exc:
|
||||
await session.rollback()
|
||||
logger.error("YooKassa WebApp payment failed: %s", exc, exc_info=True)
|
||||
logger.exception("YooKassa WebApp payment failed")
|
||||
return _json_error(502, "payment_failed", "Failed to create payment")
|
||||
|
||||
|
||||
@@ -2076,7 +2105,7 @@ async def _create_freekassa_payment(
|
||||
)
|
||||
except Exception as exc:
|
||||
await session.rollback()
|
||||
logger.error("FreeKassa WebApp payment failed: %s", exc, exc_info=True)
|
||||
logger.exception("FreeKassa WebApp payment failed")
|
||||
return _json_error(502, "payment_failed", "Failed to create payment")
|
||||
|
||||
|
||||
@@ -2152,7 +2181,7 @@ async def _create_platega_payment(
|
||||
)
|
||||
except Exception as exc:
|
||||
await session.rollback()
|
||||
logger.error("Platega WebApp payment failed: %s", exc, exc_info=True)
|
||||
logger.exception("Platega WebApp payment failed")
|
||||
return _json_error(502, "payment_failed", "Failed to create payment")
|
||||
|
||||
|
||||
@@ -2215,7 +2244,7 @@ async def _create_severpay_payment(
|
||||
)
|
||||
except Exception as exc:
|
||||
await session.rollback()
|
||||
logger.error("SeverPay WebApp payment failed: %s", exc, exc_info=True)
|
||||
logger.exception("SeverPay WebApp payment failed")
|
||||
return _json_error(502, "payment_failed", "Failed to create payment")
|
||||
|
||||
|
||||
@@ -2278,7 +2307,7 @@ async def _create_stars_payment(
|
||||
)
|
||||
except Exception as exc:
|
||||
await session.rollback()
|
||||
logger.error("Stars WebApp payment failed: %s", exc, exc_info=True)
|
||||
logger.exception("Stars WebApp payment failed")
|
||||
return _json_error(502, "payment_failed", "Failed to create invoice")
|
||||
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -291,6 +291,6 @@
|
||||
|
||||
<!-- WEBAPP_I18N_SCRIPT -->
|
||||
<!-- WEBAPP_CONFIG_SCRIPT -->
|
||||
<script src="./subscription_webapp.js" defer></script>
|
||||
<!-- WEBAPP_JS_SCRIPT -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -435,7 +435,7 @@ const MOCK = (() => {
|
||||
referral_bonus_explanation: 'Bonuses are awarded once for each invited user when they purchase a subscription.'
|
||||
}
|
||||
};
|
||||
const I18N = readJsonScript('i18n') || (MOCK && MOCK.i18n) || FALLBACK_I18N;
|
||||
const I18N = readJsonScript('i18n') || (MOCK && MOCK.i18n) || {};
|
||||
|
||||
const accent = CFG.primaryColor || '#00fe7a';
|
||||
document.documentElement.style.setProperty('--accent', accent);
|
||||
@@ -2440,14 +2440,14 @@ const MOCK = (() => {
|
||||
if (document.documentElement.lang) candidates.push(document.documentElement.lang);
|
||||
for (const raw of candidates) {
|
||||
const short = String(raw).toLowerCase().split('-')[0];
|
||||
if (I18N[short]) return short;
|
||||
if (I18N && I18N[short]) return short;
|
||||
}
|
||||
return 'ru';
|
||||
}
|
||||
|
||||
function setLanguage(lang) {
|
||||
const short = String(lang || '').toLowerCase().split('-')[0];
|
||||
if (!I18N[short]) return;
|
||||
if (I18N && Object.keys(I18N).length && !I18N[short]) return;
|
||||
state.langOverride = short;
|
||||
try { localStorage.setItem('rw_webapp_lang', short); } catch (e) { }
|
||||
applyI18n();
|
||||
@@ -2459,8 +2459,8 @@ const MOCK = (() => {
|
||||
}
|
||||
|
||||
function t(key, params = {}) {
|
||||
const table = I18N[getLanguage()] || I18N.ru;
|
||||
const fallback = I18N.ru[key] || key;
|
||||
const table = (I18N && I18N[getLanguage()]) || (I18N && I18N.ru) || {};
|
||||
const fallback = (I18N && I18N.ru && I18N.ru[key]) || key;
|
||||
const template = table[key] || fallback;
|
||||
return template.replace(/\{(\w+)\}/g, (_, name) => (
|
||||
Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : ''
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -228,7 +228,7 @@
|
||||
}
|
||||
|
||||
.lang-dropdown-item.is-active {
|
||||
@apply text-[var(--accent)];
|
||||
@apply bg-[var(--bg-card-hover)] text-[var(--text-primary)];
|
||||
}
|
||||
|
||||
.lang-dropdown-flag {
|
||||
|
||||
Reference in New Issue
Block a user