Фикс 404 при обновелнии страницы

This commit is contained in:
2026-07-31 06:30:45 -07:00
parent 7af3c46bfe
commit aea0b94598
3 changed files with 58 additions and 36 deletions

View File

@@ -1,5 +1,5 @@
import { copyFile, mkdir } from 'node:fs/promises'; import { copyFile, mkdir } from 'node:fs/promises';
import { resolve } from 'node:path'; import { dirname, resolve } from 'node:path';
const root = process.cwd(); const root = process.cwd();
const outputDirectory = resolve(root, 'dist'); const outputDirectory = resolve(root, 'dist');
@@ -20,13 +20,17 @@ await copyFile(
resolve(serverDirectory, 'index.js'), resolve(serverDirectory, 'index.js'),
); );
// Emit an extensionless HTML match for every client-side route. This keeps // Emit both clean-URL and directory-index shells for every client-side route.
// browser refreshes working even when the asset layer handles navigations // The directory copy also supports basic Nginx static configurations that do
// before the Worker gets a chance to return the SPA fallback. // not rewrite missing paths to the root application shell.
await Promise.all( await Promise.all(
browserRoutes.map(async (route) => { browserRoutes.map(async (route) => {
const routeFile = resolve(outputDirectory, `${route}.html`); const routeFile = resolve(outputDirectory, `${route}.html`);
await mkdir(resolve(routeFile, '..'), { recursive: true }); const routeDirectory = resolve(outputDirectory, route);
await mkdir(dirname(routeFile), { recursive: true });
await mkdir(routeDirectory, { recursive: true });
await copyFile(appShell, routeFile); await copyFile(appShell, routeFile);
await copyFile(appShell, resolve(routeDirectory, 'index.html'));
}), }),
); );

18
nginx.conf Normal file
View File

@@ -0,0 +1,18 @@
server {
listen 80;
listen [::]:80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location /assets/ {
try_files $uri =404;
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
try_files $uri $uri/ /index.html;
}
}

View File

@@ -2402,101 +2402,101 @@ export const protoLayers = [
{ {
"id": 0, "id": 0,
"name": "IMAGE", "name": "IMAGE",
"description": "" "description": "Изображение"
}, },
{ {
"id": 1, "id": 1,
"name": "MESSAGES", "name": "MESSAGES",
"description": "" "description": "Сообщения (forwarded/reply)"
}, },
{ {
"id": 2, "id": 2,
"name": "FILE", "name": "FILE",
"description": "" "description": "Файл"
}, },
{ {
"id": 4, "id": 4,
"name": "CALL", "name": "CALL",
"description": "" "description": "Звонок"
}, },
{ {
"id": 5, "id": 5,
"name": "VOICE", "name": "VOICE",
"description": "" "description": "Голосовое сообщение"
}, },
{ {
"id": 6, "id": 6,
"name": "VIDEO_CIRCLE", "name": "VIDEO_CIRCLE",
"description": "" "description": "Видео сообщение (кружок)"
}, },
{ {
"id": 7, "id": 7,
"name": "VIDEO", "name": "VIDEO",
"description": "" "description": "Видео"
} }
] ]
}, },
{ {
"name": "DeviceSolution", "name": "DeviceSolution",
"type": "int8", "type": "int8",
"description": "", "description": "Решение по девайсу, принять или отклонить вход",
"yaml": "DeviceSolution:\n type: int8\n values:\n - id: 0\n name: ACCEPT\n - id: 1\n name: DECLINE", "yaml": "DeviceSolution:\n type: int8\n values:\n - id: 0\n name: ACCEPT\n - id: 1\n name: DECLINE",
"values": [ "values": [
{ {
"id": 0, "id": 0,
"name": "ACCEPT", "name": "ACCEPT",
"description": "" "description": "Принять"
}, },
{ {
"id": 1, "id": 1,
"name": "DECLINE", "name": "DECLINE",
"description": "" "description": "Отклонить"
} }
] ]
}, },
{ {
"name": "HandshakeStage", "name": "HandshakeStage",
"type": "int8", "type": "int8",
"description": "", "description": "Статус хэндшейка",
"yaml": "HandshakeStage:\n type: int8\n values:\n - id: 0\n name: COMPLETED\n - id: 1\n name: NEED_DEVICE_VERIFICATION", "yaml": "HandshakeStage:\n type: int8\n values:\n - id: 0\n name: COMPLETED\n - id: 1\n name: NEED_DEVICE_VERIFICATION",
"values": [ "values": [
{ {
"id": 0, "id": 0,
"name": "COMPLETED", "name": "COMPLETED",
"description": "" "description": "Пройден"
}, },
{ {
"id": 1, "id": 1,
"name": "NEED_DEVICE_VERIFICATION", "name": "NEED_DEVICE_VERIFICATION",
"description": "" "description": "Не пройден (приложение рендерит UI подтвердите с другого устройства)"
} }
] ]
}, },
{ {
"name": "GroupStatus", "name": "GroupStatus",
"type": "int8", "type": "int8",
"description": "", "description": "Статус в группе",
"yaml": "GroupStatus:\n type: int8\n values:\n - id: 0\n name: JOINED\n - id: 1\n name: INVALID\n - id: 2\n name: NOT_JOINED\n - id: 3\n name: BANNED", "yaml": "GroupStatus:\n type: int8\n values:\n - id: 0\n name: JOINED\n - id: 1\n name: INVALID\n - id: 2\n name: NOT_JOINED\n - id: 3\n name: BANNED",
"values": [ "values": [
{ {
"id": 0, "id": 0,
"name": "JOINED", "name": "JOINED",
"description": "" "description": "В группе"
}, },
{ {
"id": 1, "id": 1,
"name": "INVALID", "name": "INVALID",
"description": "" "description": "Не валидная группа"
}, },
{ {
"id": 2, "id": 2,
"name": "NOT_JOINED", "name": "NOT_JOINED",
"description": "" "description": "Можно зайт"
}, },
{ {
"id": 3, "id": 3,
"name": "BANNED", "name": "BANNED",
"description": "" "description": "Заблокирован"
} }
] ]
}, },
@@ -2615,23 +2615,23 @@ export const protoLayers = [
{ {
"name": "WebRTCType", "name": "WebRTCType",
"type": "int8", "type": "int8",
"description": "", "description": "Тип WebRTC сигнала",
"yaml": "WebRTCType:\n type: int8\n values:\n - id: 0\n name: OFFER\n - id: 1\n name: ANSWER\n - id: 2\n name: ICE_CANDIDATE", "yaml": "WebRTCType:\n type: int8\n values:\n - id: 0\n name: OFFER\n - id: 1\n name: ANSWER\n - id: 2\n name: ICE_CANDIDATE",
"values": [ "values": [
{ {
"id": 0, "id": 0,
"name": "OFFER", "name": "OFFER",
"description": "" "description": "Offer"
}, },
{ {
"id": 1, "id": 1,
"name": "ANSWER", "name": "ANSWER",
"description": "" "description": "Answer"
}, },
{ {
"id": 2, "id": 2,
"name": "ICE_CANDIDATE", "name": "ICE_CANDIDATE",
"description": "" "description": "Estabilishment candidate"
} }
] ]
}, },
@@ -2654,22 +2654,22 @@ export const protoLayers = [
{ {
"id": 2, "id": 2,
"name": "INVALID", "name": "INVALID",
"description": "любая ошибка смысл которой зависит от контекста, например в случае с файловой системой - нет файла" "description": "Любая ошибка смысл которой зависит от контекста, например в случае с файловой системой - нет файла"
}, },
{ {
"id": 3, "id": 3,
"name": "USERNAME_TAKEN", "name": "USERNAME_TAKEN",
"description": "" "description": "Имя пользователя занято"
}, },
{ {
"id": 4, "id": 4,
"name": "OVERSIZE", "name": "OVERSIZE",
"description": "слишком большой размер файла" "description": "Слишком большой размер файла"
}, },
{ {
"id": 5, "id": 5,
"name": "OVERLIFE", "name": "OVERLIFE",
"description": "слишком большой TTL" "description": "Слишком большой TTL"
} }
] ]
}, },
@@ -2716,35 +2716,35 @@ export const protoLayers = [
"name": "id", "name": "id",
"type": "string", "type": "string",
"optional": false, "optional": false,
"description": "" "description": "Случайная строка"
}, },
{ {
"id": 2, "id": 2,
"name": "blob", "name": "blob",
"type": "string", "type": "string",
"optional": true, "optional": true,
"description": "" "description": "Только очень коротких типов"
}, },
{ {
"id": 3, "id": 3,
"name": "type", "name": "type",
"type": "AttachmentType", "type": "AttachmentType",
"optional": false, "optional": false,
"description": "" "description": "Тип вложения"
}, },
{ {
"id": 4, "id": 4,
"name": "preview", "name": "preview",
"type": "string", "type": "string",
"optional": false, "optional": false,
"description": "" "description": "Превью"
}, },
{ {
"id": 6, "id": 6,
"name": "objectId", "name": "objectId",
"type": "string", "type": "string",
"optional": false, "optional": false,
"description": "" "description": "Выданный сервером objectId от cdn"
} }
] ]
}, },