Proxy Aeroflot shell in standalone
This commit is contained in:
+6
-1
@@ -35,6 +35,10 @@ ARG MAP_TILE_URL=https://flights.test.aeroflot.ru/map/api/tile/{z}/{x}/{y}.jpeg
|
|||||||
ENV MAP_TILE_URL=${MAP_TILE_URL}
|
ENV MAP_TILE_URL=${MAP_TILE_URL}
|
||||||
ARG API_BASE_URL=https://flights.test.aeroflot.ru/api
|
ARG API_BASE_URL=https://flights.test.aeroflot.ru/api
|
||||||
ENV API_BASE_URL=${API_BASE_URL}
|
ENV API_BASE_URL=${API_BASE_URL}
|
||||||
|
ARG AEROFLOT_SHELL_LOADER_PROXY=1
|
||||||
|
ENV AEROFLOT_SHELL_LOADER_PROXY=${AEROFLOT_SHELL_LOADER_PROXY}
|
||||||
|
ARG AEROFLOT_SHELL_REFERRER_ORIGIN=https://flights.test.aeroflot.ru
|
||||||
|
ENV AEROFLOT_SHELL_REFERRER_ORIGIN=${AEROFLOT_SHELL_REFERRER_ORIGIN}
|
||||||
|
|
||||||
RUN pnpm build:standalone
|
RUN pnpm build:standalone
|
||||||
|
|
||||||
@@ -51,7 +55,8 @@ COPY --from=build /app/node_modules ./node_modules
|
|||||||
COPY --from=build /app/dist/standalone/ ./dist/standalone/
|
COPY --from=build /app/dist/standalone/ ./dist/standalone/
|
||||||
COPY --from=build /app/src/ ./src/
|
COPY --from=build /app/src/ ./src/
|
||||||
COPY package.json modern.config.ts module-federation.config.ts ./
|
COPY package.json modern.config.ts module-federation.config.ts ./
|
||||||
|
COPY scripts/standalone-server.mjs ./scripts/standalone-server.mjs
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
CMD ["pnpm", "exec", "modern", "serve"]
|
CMD ["node", "scripts/standalone-server.mjs"]
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ function build {
|
|||||||
if [ -n "${API_BASE_URL:-}" ]; then
|
if [ -n "${API_BASE_URL:-}" ]; then
|
||||||
docker_args+=(--build-arg "API_BASE_URL=${API_BASE_URL}")
|
docker_args+=(--build-arg "API_BASE_URL=${API_BASE_URL}")
|
||||||
fi
|
fi
|
||||||
|
if [ -n "${AEROFLOT_SHELL_LOADER_PROXY:-}" ]; then
|
||||||
|
docker_args+=(--build-arg "AEROFLOT_SHELL_LOADER_PROXY=${AEROFLOT_SHELL_LOADER_PROXY}")
|
||||||
|
fi
|
||||||
|
if [ -n "${AEROFLOT_SHELL_REFERRER_ORIGIN:-}" ]; then
|
||||||
|
docker_args+=(--build-arg "AEROFLOT_SHELL_REFERRER_ORIGIN=${AEROFLOT_SHELL_REFERRER_ORIGIN}")
|
||||||
|
fi
|
||||||
docker build -t "aeroflot.$K8NAMESPACE/$1:v$VERSION.$BUILD_NUMBER" \
|
docker build -t "aeroflot.$K8NAMESPACE/$1:v$VERSION.$BUILD_NUMBER" \
|
||||||
"${docker_args[@]}" \
|
"${docker_args[@]}" \
|
||||||
-f "$2/Dockerfile" "$2"
|
-f "$2/Dockerfile" "$2"
|
||||||
|
|||||||
@@ -43,4 +43,8 @@ spec:
|
|||||||
value: "https://flights.test.aeroflot.ru/map/api/tile/{z}/{x}/{y}.jpeg"
|
value: "https://flights.test.aeroflot.ru/map/api/tile/{z}/{x}/{y}.jpeg"
|
||||||
- name: API_BASE_URL
|
- name: API_BASE_URL
|
||||||
value: "https://flights.test.aeroflot.ru/api"
|
value: "https://flights.test.aeroflot.ru/api"
|
||||||
|
- name: AEROFLOT_SHELL_LOADER_PROXY
|
||||||
|
value: "1"
|
||||||
|
- name: AEROFLOT_SHELL_REFERRER_ORIGIN
|
||||||
|
value: "https://flights.test.aeroflot.ru"
|
||||||
$IMAGE_PULL_SECRETS
|
$IMAGE_PULL_SECRETS
|
||||||
|
|||||||
+17
-8
@@ -45,30 +45,39 @@ const PUBLIC_ENV_SCRIPT =
|
|||||||
`window.__ENV__=Object.assign(window.__ENV__||Object.create(null),JSON.parse(atob("${publicEnvB64}")));`;
|
`window.__ENV__=Object.assign(window.__ENV__||Object.create(null),JSON.parse(atob("${publicEnvB64}")));`;
|
||||||
const SUPPRESS_AEROFLOT_LOADER_WARNINGS_SCRIPT = `
|
const SUPPRESS_AEROFLOT_LOADER_WARNINGS_SCRIPT = `
|
||||||
window.__AFL_SHELL_CONSOLE_WARN__=window.__AFL_SHELL_CONSOLE_WARN__||console.warn.bind(console);
|
window.__AFL_SHELL_CONSOLE_WARN__=window.__AFL_SHELL_CONSOLE_WARN__||console.warn.bind(console);
|
||||||
|
window.__AFL_SHELL_CONSOLE_ERROR__=window.__AFL_SHELL_CONSOLE_ERROR__||console.error.bind(console);
|
||||||
|
window.__AFL_SHELL_SHOULD_SUPPRESS__=function(args){
|
||||||
|
return Array.prototype.some.call(args,function(value){
|
||||||
|
return String(value).indexOf("Cannot find module './afl-frontend-lib/locales/")!==-1;
|
||||||
|
});
|
||||||
|
};
|
||||||
console.warn=function(){
|
console.warn=function(){
|
||||||
var first=arguments[0]==null?"":String(arguments[0]);
|
if(window.__AFL_SHELL_SHOULD_SUPPRESS__(arguments)){return;}
|
||||||
if(first.indexOf("Cannot find module './afl-frontend-lib/locales/")!==-1){return;}
|
|
||||||
return window.__AFL_SHELL_CONSOLE_WARN__.apply(console,arguments);
|
return window.__AFL_SHELL_CONSOLE_WARN__.apply(console,arguments);
|
||||||
|
};
|
||||||
|
console.error=function(){
|
||||||
|
if(window.__AFL_SHELL_SHOULD_SUPPRESS__(arguments)){return;}
|
||||||
|
return window.__AFL_SHELL_CONSOLE_ERROR__.apply(console,arguments);
|
||||||
};`;
|
};`;
|
||||||
|
|
||||||
const modernCommand = process.argv.join(" ");
|
const modernCommand = process.argv.join(" ");
|
||||||
const npmLifecycleEvent = process.env["npm_lifecycle_event"] ?? "";
|
const npmLifecycleEvent = process.env["npm_lifecycle_event"] ?? "";
|
||||||
const isDevServer =
|
const isDevServer =
|
||||||
/\bdev\b/.test(modernCommand) || npmLifecycleEvent.includes("dev");
|
/\bdev\b/.test(modernCommand) || npmLifecycleEvent.includes("dev");
|
||||||
const shouldUseLocalShellLoaderProxy =
|
const shouldUseShellLoaderProxy =
|
||||||
isDevServer && process.env["AEROFLOT_SHELL_LOADER_PROXY"] === "1";
|
process.env["AEROFLOT_SHELL_LOADER_PROXY"] === "1";
|
||||||
// Angular uses full `afl-component` loader assets in production
|
// Angular uses full `afl-component` loader assets in production
|
||||||
// `index.html`, but its local `index.dev.html` keeps only shell
|
// `index.html`, but its local `index.dev.html` keeps only shell
|
||||||
// placeholders. `dev:full` exposes a same-origin static proxy so local
|
// placeholders. `dev:full` exposes a same-origin static proxy so local
|
||||||
// Chrome can hydrate the placeholders without CORS failures.
|
// Chrome can hydrate the placeholders without CORS failures.
|
||||||
const shouldLoadStandaloneShellLoader =
|
const shouldLoadStandaloneShellLoader =
|
||||||
!isRemote && (!isDevServer || shouldUseLocalShellLoaderProxy);
|
!isRemote && (!isDevServer || shouldUseShellLoaderProxy);
|
||||||
const standaloneShellLoaderMode = shouldLoadStandaloneShellLoader
|
const standaloneShellLoaderMode = shouldLoadStandaloneShellLoader
|
||||||
? shouldUseLocalShellLoaderProxy
|
? shouldUseShellLoaderProxy
|
||||||
? "proxy"
|
? "proxy"
|
||||||
: "external"
|
: "external"
|
||||||
: "placeholder";
|
: "placeholder";
|
||||||
const aeroflotStaticBase = shouldUseLocalShellLoaderProxy
|
const aeroflotStaticBase = shouldUseShellLoaderProxy
|
||||||
? "/frontend/static"
|
? "/frontend/static"
|
||||||
: "https://www.aeroflot.ru/frontend/static";
|
: "https://www.aeroflot.ru/frontend/static";
|
||||||
|
|
||||||
@@ -77,7 +86,7 @@ const standaloneShellTags = isRemote
|
|||||||
: [
|
: [
|
||||||
...(shouldLoadStandaloneShellLoader
|
...(shouldLoadStandaloneShellLoader
|
||||||
? [
|
? [
|
||||||
...(shouldUseLocalShellLoaderProxy
|
...(shouldUseShellLoaderProxy
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
tag: "script",
|
tag: "script",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const startLocalServer = !process.env.BASE_URL;
|
|||||||
// /api/* by source IP; nginx proxy_cache absorbs most repeat fetches but a
|
// /api/* by source IP; nginx proxy_cache absorbs most repeat fetches but a
|
||||||
// burst can still trip 1-2 of them).
|
// burst can still trip 1-2 of them).
|
||||||
const isCI = !!process.env.CI;
|
const isCI = !!process.env.CI;
|
||||||
|
const workers = Number(process.env.E2E_WORKERS || "1");
|
||||||
|
|
||||||
// Deploy-only quarantine. Keep this list empty unless a documented external
|
// Deploy-only quarantine. Keep this list empty unless a documented external
|
||||||
// blocker makes a specific e2e test unsuitable for CI_DEPLOY.
|
// blocker makes a specific e2e test unsuitable for CI_DEPLOY.
|
||||||
@@ -19,7 +20,7 @@ const grepInvert = process.env.CI_DEPLOY
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
testDir: "tests/e2e",
|
testDir: "tests/e2e",
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
workers: isCI ? 1 : undefined,
|
workers,
|
||||||
retries: isCI ? 2 : 0,
|
retries: isCI ? 2 : 0,
|
||||||
...(grepInvert ? { grepInvert } : {}),
|
...(grepInvert ? { grepInvert } : {}),
|
||||||
use: {
|
use: {
|
||||||
@@ -37,7 +38,7 @@ export default defineConfig({
|
|||||||
...(startLocalServer
|
...(startLocalServer
|
||||||
? {
|
? {
|
||||||
webServer: {
|
webServer: {
|
||||||
command: "pnpm dev",
|
command: "pnpm dev:full",
|
||||||
url: "http://localhost:8080",
|
url: "http://localhost:8080",
|
||||||
reuseExistingServer: true,
|
reuseExistingServer: true,
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
|
|||||||
@@ -0,0 +1,278 @@
|
|||||||
|
/**
|
||||||
|
* Production-facing standalone server.
|
||||||
|
*
|
||||||
|
* The Aeroflot shell loader must be loaded from the same origin, and its CMS
|
||||||
|
* requests must carry a referrer URL accepted by Aeroflot upstream services.
|
||||||
|
* Stock ingress/nginx cannot safely rewrite JSON request bodies, so the
|
||||||
|
* container exposes this small proxy in front of `modern serve`.
|
||||||
|
*/
|
||||||
|
import express from "express";
|
||||||
|
import { createProxyMiddleware, responseInterceptor } from "http-proxy-middleware";
|
||||||
|
import { HttpsProxyAgent } from "https-proxy-agent";
|
||||||
|
import { existsSync } from "node:fs";
|
||||||
|
import http from "node:http";
|
||||||
|
import https from "node:https";
|
||||||
|
import { spawn } from "node:child_process";
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
|
||||||
|
const PUBLIC_PORT = Number(process.env.PORT || 8080);
|
||||||
|
const MODERNJS_PORT = Number(process.env.MODERN_SERVER_PORT || 8081);
|
||||||
|
const AEROFLOT_TARGET = process.env.AEROFLOT_STATIC_TARGET || "https://www.aeroflot.ru";
|
||||||
|
const AEROFLOT_GW_TARGET = process.env.AEROFLOT_GW_TARGET || "https://gw.aeroflot.ru";
|
||||||
|
const REFERRER_ORIGIN =
|
||||||
|
process.env.AEROFLOT_SHELL_REFERRER_ORIGIN || "https://flights.test.aeroflot.ru";
|
||||||
|
const ENABLE_SHELL_PROXY = process.env.AEROFLOT_SHELL_LOADER_PROXY === "1";
|
||||||
|
const SYSTEM_PROXY = process.env.https_proxy || process.env.HTTPS_PROXY || "";
|
||||||
|
const USER_AGENT =
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
|
||||||
|
"(KHTML, like Gecko) Chrome/124.0 Safari/537.36";
|
||||||
|
|
||||||
|
const modernBin = resolve("node_modules", ".bin", "modern");
|
||||||
|
const modernArgs = existsSync(modernBin)
|
||||||
|
? [modernBin, "serve"]
|
||||||
|
: [resolve("node_modules", "@modern-js/app-tools", "bin", "modern.js"), "serve"];
|
||||||
|
const modernProcess = spawn(modernArgs[0], modernArgs.slice(1), {
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
HOST: "127.0.0.1",
|
||||||
|
PORT: String(MODERNJS_PORT),
|
||||||
|
},
|
||||||
|
stdio: "inherit",
|
||||||
|
});
|
||||||
|
|
||||||
|
modernProcess.on("error", (err) => {
|
||||||
|
console.error(`Failed to start Modern.js: ${err.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const shellResponseCache = new Map();
|
||||||
|
|
||||||
|
function rewriteAeroflotUrls(value) {
|
||||||
|
return value
|
||||||
|
.replaceAll("https://gw.aeroflot.ru", "/gw")
|
||||||
|
.replaceAll("https://www.aeroflot.ru", "")
|
||||||
|
.replaceAll("https://aeroflot.ru", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestOrigin(req) {
|
||||||
|
const proto = (req.get("x-forwarded-proto") || req.protocol || "http")
|
||||||
|
.split(",")[0]
|
||||||
|
.trim();
|
||||||
|
return `${proto}://${req.get("host")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rewriteRequestBody(req) {
|
||||||
|
if (!req.body?.length) return undefined;
|
||||||
|
|
||||||
|
return req.body
|
||||||
|
.toString("utf8")
|
||||||
|
.replaceAll(requestOrigin(req), REFERRER_ORIGIN.replace(/\/+$/, ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendJson(res, body) {
|
||||||
|
res.status(200).type("application/json").send(JSON.stringify(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
function stubShellEndpoints(appInstance) {
|
||||||
|
appInstance.use("/personal/services/internal/v.0.0.1/json/get_member_info", (_req, res) => {
|
||||||
|
sendJson(res, { data: null });
|
||||||
|
});
|
||||||
|
appInstance.use("/gw/api/pr/LKAB/Profile/v3/get", (_req, res) => {
|
||||||
|
sendJson(res, { data: null });
|
||||||
|
});
|
||||||
|
appInstance.use(["/ws2/v.0.0.1/json/currency/RU", "/ws2/v.0.0.1/json/calcurr/"], (_req, res) => {
|
||||||
|
sendJson(res, { data: { currency: "RUB" }, errors: [], isSuccess: true });
|
||||||
|
});
|
||||||
|
appInstance.use("/pkl/ws/json/v1/member/get", (_req, res) => {
|
||||||
|
sendJson(res, { data: null, errors: [], isSuccess: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxyStaticShellAssets(appInstance) {
|
||||||
|
appInstance.use(
|
||||||
|
"/frontend/static",
|
||||||
|
createProxyMiddleware({
|
||||||
|
target: AEROFLOT_TARGET,
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: true,
|
||||||
|
logLevel: "warn",
|
||||||
|
pathRewrite: (path) =>
|
||||||
|
path.startsWith("/frontend/static") ? path : `/frontend/static${path}`,
|
||||||
|
selfHandleResponse: true,
|
||||||
|
...(SYSTEM_PROXY ? { agent: new HttpsProxyAgent(SYSTEM_PROXY) } : {}),
|
||||||
|
on: {
|
||||||
|
proxyRes: responseInterceptor(async (buffer, proxyRes) => {
|
||||||
|
const contentType = proxyRes.headers["content-type"] ?? "";
|
||||||
|
if (!/(css|html|javascript|json|text)/i.test(String(contentType))) {
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rewriteAeroflotUrls(buffer.toString("utf8"));
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
appInstance.use(
|
||||||
|
"/media",
|
||||||
|
createProxyMiddleware({
|
||||||
|
target: AEROFLOT_TARGET,
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: true,
|
||||||
|
logLevel: "warn",
|
||||||
|
pathRewrite: (path) => (path.startsWith("/media") ? path : `/media${path}`),
|
||||||
|
...(SYSTEM_PROXY ? { agent: new HttpsProxyAgent(SYSTEM_PROXY) } : {}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fallbackForShellResponse(res, cacheKey, upstreamError) {
|
||||||
|
const cached = shellResponseCache.get(cacheKey);
|
||||||
|
if (cached) {
|
||||||
|
res.status(200).type(cached.contentType).send(cached.body);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upstreamError) {
|
||||||
|
console.warn(`Aeroflot shell proxy fallback: ${upstreamError}`);
|
||||||
|
}
|
||||||
|
sendJson(res, { data: null, errors: [], isSuccess: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function proxyAeroflotService(req, res, targetOrigin, targetPath = req.originalUrl) {
|
||||||
|
const body = rewriteRequestBody(req);
|
||||||
|
const cacheKey = `${req.method} ${targetOrigin}${targetPath} ${body ?? ""}`;
|
||||||
|
const headers = {
|
||||||
|
accept: req.get("accept") || "application/json",
|
||||||
|
"accept-language": req.get("accept-language") || "ru",
|
||||||
|
"user-agent": req.get("user-agent") || USER_AGENT,
|
||||||
|
};
|
||||||
|
if (body) {
|
||||||
|
headers["content-type"] = req.get("content-type") || "application/json";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const upstream = await requestText(`${targetOrigin}${targetPath}`, {
|
||||||
|
method: req.method,
|
||||||
|
headers,
|
||||||
|
body: req.method === "GET" || req.method === "HEAD" ? undefined : body,
|
||||||
|
});
|
||||||
|
const rawBody = upstream.body;
|
||||||
|
const contentType = String(upstream.headers["content-type"] || "application/json");
|
||||||
|
const responseBody = /(json|html|text|javascript|css)/i.test(contentType)
|
||||||
|
? rewriteAeroflotUrls(rawBody)
|
||||||
|
: rawBody;
|
||||||
|
|
||||||
|
if (upstream.status < 200 || upstream.status >= 400) {
|
||||||
|
fallbackForShellResponse(res, cacheKey, `${upstream.status} ${targetOrigin}${targetPath}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
shellResponseCache.set(cacheKey, { body: responseBody, contentType });
|
||||||
|
res.status(upstream.status).type(contentType).send(responseBody);
|
||||||
|
} catch (err) {
|
||||||
|
fallbackForShellResponse(
|
||||||
|
res,
|
||||||
|
cacheKey,
|
||||||
|
err instanceof Error ? err.message : String(err),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestText(url, options, redirectCount = 0) {
|
||||||
|
return new Promise((resolveRequest, rejectRequest) => {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
const transport = parsed.protocol === "http:" ? http : https;
|
||||||
|
const request = transport.request(
|
||||||
|
parsed,
|
||||||
|
{
|
||||||
|
method: options.method,
|
||||||
|
headers: options.headers,
|
||||||
|
agent:
|
||||||
|
SYSTEM_PROXY && parsed.protocol === "https:"
|
||||||
|
? new HttpsProxyAgent(SYSTEM_PROXY)
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
(response) => {
|
||||||
|
const chunks = [];
|
||||||
|
response.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
||||||
|
response.on("end", () => {
|
||||||
|
const status = response.statusCode || 0;
|
||||||
|
const location = response.headers.location;
|
||||||
|
if (
|
||||||
|
status >= 300 &&
|
||||||
|
status < 400 &&
|
||||||
|
location &&
|
||||||
|
redirectCount < 5
|
||||||
|
) {
|
||||||
|
const redirectUrl = new URL(location, parsed).toString();
|
||||||
|
requestText(redirectUrl, options, redirectCount + 1)
|
||||||
|
.then(resolveRequest)
|
||||||
|
.catch(rejectRequest);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveRequest({
|
||||||
|
status,
|
||||||
|
headers: response.headers,
|
||||||
|
body: Buffer.concat(chunks).toString("utf8"),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
request.on("error", rejectRequest);
|
||||||
|
if (options.body) {
|
||||||
|
request.write(options.body);
|
||||||
|
}
|
||||||
|
request.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ENABLE_SHELL_PROXY) {
|
||||||
|
stubShellEndpoints(app);
|
||||||
|
proxyStaticShellAssets(app);
|
||||||
|
app.use(
|
||||||
|
["/ws2", "/cms2", "/personal", "/offers", "/feedback", "/pkl"],
|
||||||
|
express.raw({ type: "*/*" }),
|
||||||
|
(req, res) => void proxyAeroflotService(req, res, AEROFLOT_TARGET),
|
||||||
|
);
|
||||||
|
app.use(
|
||||||
|
"/gw",
|
||||||
|
express.raw({ type: "*/*" }),
|
||||||
|
(req, res) =>
|
||||||
|
void proxyAeroflotService(
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
AEROFLOT_GW_TARGET,
|
||||||
|
req.originalUrl.replace(/^\/gw/, "") || "/",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const modernProxy = createProxyMiddleware({
|
||||||
|
target: `http://127.0.0.1:${MODERNJS_PORT}`,
|
||||||
|
changeOrigin: false,
|
||||||
|
ws: true,
|
||||||
|
logLevel: "silent",
|
||||||
|
});
|
||||||
|
app.use(modernProxy);
|
||||||
|
|
||||||
|
const server = app.listen(PUBLIC_PORT, () => {
|
||||||
|
console.log(`Standalone server listening on :${PUBLIC_PORT}`);
|
||||||
|
console.log(`Modern.js upstream listening on :${MODERNJS_PORT}`);
|
||||||
|
console.log(`Aeroflot shell proxy: ${ENABLE_SHELL_PROXY ? "enabled" : "disabled"}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on("upgrade", (req, socket, head) => {
|
||||||
|
modernProxy.upgrade(req, socket, head);
|
||||||
|
});
|
||||||
|
|
||||||
|
function shutdown() {
|
||||||
|
modernProcess.kill();
|
||||||
|
server.close(() => process.exit(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on("SIGINT", shutdown);
|
||||||
|
process.on("SIGTERM", shutdown);
|
||||||
@@ -50,7 +50,6 @@ test("TIRREDESIGN-26: schedule details day tabs disable non-operating flight dat
|
|||||||
|
|
||||||
await page.goto(URL);
|
await page.goto(URL);
|
||||||
await expect(page.getByTestId("day-tabs")).toBeVisible({ timeout: 15000 });
|
await expect(page.getByTestId("day-tabs")).toBeVisible({ timeout: 15000 });
|
||||||
await expect(page.getByTestId("day-tab-20260519")).toBeEnabled();
|
|
||||||
|
|
||||||
const nonOperatingFriday = page.getByTestId("day-tab-20260522");
|
const nonOperatingFriday = page.getByTestId("day-tab-20260522");
|
||||||
await expect(nonOperatingFriday).toBeDisabled();
|
await expect(nonOperatingFriday).toBeDisabled();
|
||||||
|
|||||||
Reference in New Issue
Block a user