forked from Rosetta/landing
Compare commits
10 Commits
e8c221bda4
...
b96ad82cf7
| Author | SHA1 | Date | |
|---|---|---|---|
| b96ad82cf7 | |||
| 5278509046 | |||
| 026f69b179 | |||
| 94bc883205 | |||
| 12f6384856 | |||
| bff945e701 | |||
| 354c8e5489 | |||
|
|
4bc65f71cf | ||
| aea0b94598 | |||
| 7af3c46bfe |
@@ -1,11 +1,37 @@
|
|||||||
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 serverDirectory = resolve(root, 'dist', 'server');
|
const serverDirectory = resolve(root, 'dist', 'server');
|
||||||
|
const appShell = resolve(outputDirectory, 'index.html');
|
||||||
|
const browserRoutes = [
|
||||||
|
'encryption',
|
||||||
|
'privacy',
|
||||||
|
'proto',
|
||||||
|
'proto/docs',
|
||||||
|
'rcc',
|
||||||
|
'support',
|
||||||
|
'terms',
|
||||||
|
];
|
||||||
|
|
||||||
await mkdir(serverDirectory, { recursive: true });
|
await mkdir(serverDirectory, { recursive: true });
|
||||||
await copyFile(
|
await copyFile(
|
||||||
resolve(root, 'worker', 'index.js'),
|
resolve(root, 'worker', 'index.js'),
|
||||||
resolve(serverDirectory, 'index.js'),
|
resolve(serverDirectory, 'index.js'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Emit both clean-URL and directory-index shells for every client-side route.
|
||||||
|
// The directory copy also supports basic Nginx static configurations that do
|
||||||
|
// not rewrite missing paths to the root application shell.
|
||||||
|
await Promise.all(
|
||||||
|
browserRoutes.map(async (route) => {
|
||||||
|
const routeFile = resolve(outputDirectory, `${route}.html`);
|
||||||
|
const routeDirectory = resolve(outputDirectory, route);
|
||||||
|
|
||||||
|
await mkdir(dirname(routeFile), { recursive: true });
|
||||||
|
await mkdir(routeDirectory, { recursive: true });
|
||||||
|
await copyFile(appShell, routeFile);
|
||||||
|
await copyFile(appShell, resolve(routeDirectory, 'index.html'));
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|||||||
19
nginx.conf
Normal file
19
nginx.conf
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
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 / {
|
||||||
|
add_header Cache-Control "no-cache" always;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,57 +2,56 @@ version: 3
|
|||||||
description: Add avatars and more CDN safety, remove transport server
|
description: Add avatars and more CDN safety, remove transport server
|
||||||
|
|
||||||
enums:
|
enums:
|
||||||
#Тип вложния
|
#тип вложния
|
||||||
AttachmentType:
|
AttachmentType:
|
||||||
type: int8
|
type: int8
|
||||||
values:
|
values:
|
||||||
- id: 0
|
- id: 0
|
||||||
name: IMAGE #Изображение
|
name: IMAGE #изображение
|
||||||
- id: 1
|
- id: 1
|
||||||
name: MESSAGES #Сообщения (forwarded/reply)
|
name: MESSAGES #сообщения (forwarded/reply)
|
||||||
- id: 2
|
- id: 2
|
||||||
name: FILE #Файл
|
name: FILE #файл
|
||||||
- id: 4
|
- id: 4
|
||||||
name: CALL #Звонок
|
name: CALL #звонок
|
||||||
- id: 5
|
- id: 5
|
||||||
name: VOICE #Голосовое сообщение
|
name: VOICE #голосовое сообщение
|
||||||
- id: 6
|
- id: 6
|
||||||
name: VIDEO_CIRCLE #Видео сообщение (кружок)
|
name: VIDEO_CIRCLE #видео сообщение (кружок)
|
||||||
- id: 7
|
- id: 7
|
||||||
name: VIDEO #Видео
|
name: VIDEO #видео
|
||||||
|
|
||||||
#Решение по девайсу, принять или отклонить вход
|
#решение по девайсу, принять или отклонить вход
|
||||||
DeviceSolution:
|
DeviceSolution:
|
||||||
type: int8
|
type: int8
|
||||||
values:
|
values:
|
||||||
- id: 0
|
- id: 0
|
||||||
name: ACCEPT #Принять
|
name: ACCEPT #принять
|
||||||
- id: 1
|
- id: 1
|
||||||
name: DECLINE #Отклонить
|
name: DECLINE #отклонить
|
||||||
|
|
||||||
#Статус хэндшейка
|
#статус хэндшейка
|
||||||
HandshakeStage:
|
HandshakeStage:
|
||||||
type: int8
|
type: int8
|
||||||
values:
|
values:
|
||||||
- id: 0
|
- id: 0
|
||||||
name: COMPLETED #Пройден
|
name: COMPLETED #пройден
|
||||||
- id: 1
|
- id: 1
|
||||||
name: NEED_DEVICE_VERIFICATION #Не пройден (приложение рендерит UI подтвердите с другого устройства)
|
name: NEED_DEVICE_VERIFICATION #не пройден (приложение рендерит UI подтвердите с другого устройства)
|
||||||
|
|
||||||
#Статус в группе
|
#статус в группе
|
||||||
GroupStatus:
|
GroupStatus:
|
||||||
type: int8
|
type: int8
|
||||||
values:
|
values:
|
||||||
- id: 0
|
- id: 0
|
||||||
name: JOINED #В группе
|
name: JOINED #в группе
|
||||||
- id: 1
|
- id: 1
|
||||||
name: INVALID #Не валидная группа
|
name: INVALID #не валидная группа
|
||||||
- id: 2
|
- id: 2
|
||||||
name: NOT_JOINED #Можно зайт
|
name: NOT_JOINED #можно зайт
|
||||||
- id: 3
|
- id: 3
|
||||||
name: BANNED #Заблокирован
|
name: BANNED #заблокирован
|
||||||
|
|
||||||
#
|
|
||||||
NotificationAction:
|
NotificationAction:
|
||||||
type: int8
|
type: int8
|
||||||
values:
|
values:
|
||||||
|
|||||||
@@ -8,10 +8,16 @@
|
|||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://rosetta.im/proto/</loc>
|
<loc>https://rosetta.im/proto/</loc>
|
||||||
<lastmod>2026-07-30</lastmod>
|
<lastmod>2026-08-01</lastmod>
|
||||||
<changefreq>weekly</changefreq>
|
<changefreq>weekly</changefreq>
|
||||||
<priority>0.9</priority>
|
<priority>0.9</priority>
|
||||||
</url>
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://rosetta.im/rcc/</loc>
|
||||||
|
<lastmod>2026-08-01</lastmod>
|
||||||
|
<changefreq>monthly</changefreq>
|
||||||
|
<priority>0.8</priority>
|
||||||
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://rosetta.im/proto/docs/</loc>
|
<loc>https://rosetta.im/proto/docs/</loc>
|
||||||
<lastmod>2026-07-31</lastmod>
|
<lastmod>2026-07-31</lastmod>
|
||||||
|
|||||||
@@ -18,14 +18,20 @@ async function exists(path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function capitalizeFirstLetter(value) {
|
||||||
|
return value.trim().replace(/\p{L}/u, (letter) => letter.toLocaleUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
function cleanComment(...comments) {
|
function cleanComment(...comments) {
|
||||||
return comments
|
const description = comments
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join('\n')
|
.join('\n')
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map((line) => line.replace(/^\s*#?\s?/, '').trim())
|
.map((line) => line.replace(/^\s*#?\s?/, '').trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' ');
|
.join(' ');
|
||||||
|
|
||||||
|
return capitalizeFirstLetter(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapFields(node) {
|
function mapFields(node) {
|
||||||
@@ -128,7 +134,7 @@ async function readLayer(path) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
version: Number(document.get('version')),
|
version: Number(document.get('version')),
|
||||||
description: String(document.get('description') || ''),
|
description: capitalizeFirstLetter(String(document.get('description') || '')),
|
||||||
source: basename(path),
|
source: basename(path),
|
||||||
enums: mapNamedCollection(document.get('enums', true), 'enum'),
|
enums: mapNamedCollection(document.get('enums', true), 'enum'),
|
||||||
types: mapNamedCollection(document.get('types', true), 'type'),
|
types: mapNamedCollection(document.get('types', true), 'type'),
|
||||||
|
|||||||
41
src/App.tsx
41
src/App.tsx
@@ -1,7 +1,7 @@
|
|||||||
import '@mantine/core/styles.css';
|
import '@mantine/core/styles.css';
|
||||||
|
|
||||||
import { Center, createTheme, Loader, MantineProvider } from '@mantine/core';
|
import { Button, Center, createTheme, Loader, MantineProvider, Stack, Text, Title } from '@mantine/core';
|
||||||
import { lazy, Suspense } from 'react';
|
import { Component, lazy, Suspense, type ReactNode } from 'react';
|
||||||
import { Route, Routes } from 'react-router-dom';
|
import { Route, Routes } from 'react-router-dom';
|
||||||
import { ScrollToTop } from './components/ScrollToTop/ScrollToTop';
|
import { ScrollToTop } from './components/ScrollToTop/ScrollToTop';
|
||||||
import './style.css';
|
import './style.css';
|
||||||
@@ -18,6 +18,9 @@ const ProtoPage = lazy(() =>
|
|||||||
const ProtoDocsPage = lazy(() =>
|
const ProtoDocsPage = lazy(() =>
|
||||||
import('./pages/ProtoPage').then((module) => ({ default: module.ProtoDocsPage })),
|
import('./pages/ProtoPage').then((module) => ({ default: module.ProtoDocsPage })),
|
||||||
);
|
);
|
||||||
|
const RccPage = lazy(() =>
|
||||||
|
import('./pages/RccPage').then((module) => ({ default: module.RccPage })),
|
||||||
|
);
|
||||||
const SupportPage = lazy(() =>
|
const SupportPage = lazy(() =>
|
||||||
import('./pages/SupportPage').then((module) => ({ default: module.SupportPage })),
|
import('./pages/SupportPage').then((module) => ({ default: module.SupportPage })),
|
||||||
);
|
);
|
||||||
@@ -39,9 +42,41 @@ const theme = createTheme({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
class PageErrorBoundary extends Component<
|
||||||
|
{ children: ReactNode },
|
||||||
|
{ hasError: boolean }
|
||||||
|
> {
|
||||||
|
state = { hasError: false };
|
||||||
|
|
||||||
|
static getDerivedStateFromError() {
|
||||||
|
return { hasError: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
return (
|
||||||
|
<Center mih="100vh" p="xl">
|
||||||
|
<Stack align="center" gap="md" maw={460}>
|
||||||
|
<Title order={2} ta="center">
|
||||||
|
This page could not be loaded
|
||||||
|
</Title>
|
||||||
|
<Text c="dimmed" ta="center">
|
||||||
|
Rosetta may have been updated while this tab was open. Reload the page to continue.
|
||||||
|
</Text>
|
||||||
|
<Button onClick={() => window.location.reload()}>Reload page</Button>
|
||||||
|
</Stack>
|
||||||
|
</Center>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<MantineProvider theme={theme}>
|
<MantineProvider theme={theme}>
|
||||||
|
<PageErrorBoundary>
|
||||||
<ScrollToTop />
|
<ScrollToTop />
|
||||||
<Suspense
|
<Suspense
|
||||||
fallback={
|
fallback={
|
||||||
@@ -55,11 +90,13 @@ export default function App() {
|
|||||||
<Route path="/encryption" element={<EncryptionPage />} />
|
<Route path="/encryption" element={<EncryptionPage />} />
|
||||||
<Route path="/proto" element={<ProtoPage />} />
|
<Route path="/proto" element={<ProtoPage />} />
|
||||||
<Route path="/proto/docs" element={<ProtoDocsPage />} />
|
<Route path="/proto/docs" element={<ProtoDocsPage />} />
|
||||||
|
<Route path="/rcc" element={<RccPage />} />
|
||||||
<Route path="/support" element={<SupportPage />} />
|
<Route path="/support" element={<SupportPage />} />
|
||||||
<Route path="/privacy" element={<PrivacyPage />} />
|
<Route path="/privacy" element={<PrivacyPage />} />
|
||||||
<Route path="/terms" element={<TermsPage />} />
|
<Route path="/terms" element={<TermsPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
</PageErrorBoundary>
|
||||||
</MantineProvider>
|
</MantineProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
.page {
|
.page {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-9));
|
background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-9));
|
||||||
}
|
}
|
||||||
|
|
||||||
.shell {
|
.shell {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
padding-top: 72px;
|
padding-top: 72px;
|
||||||
padding-bottom: 96px;
|
padding-bottom: 96px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
padding: clamp(30px, 5vw, 60px);
|
padding: clamp(30px, 5vw, 60px);
|
||||||
@@ -21,6 +25,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.intro {
|
.intro {
|
||||||
|
min-width: 0;
|
||||||
max-width: 760px;
|
max-width: 760px;
|
||||||
padding-bottom: 36px;
|
padding-bottom: 36px;
|
||||||
border-bottom: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
|
border-bottom: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
|
||||||
@@ -69,6 +74,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
|
min-width: 0;
|
||||||
margin-top: 44px;
|
margin-top: 44px;
|
||||||
padding-top: 36px;
|
padding-top: 36px;
|
||||||
border-top: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
|
border-top: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ interface LegalPageProps {
|
|||||||
interface LegalSectionProps {
|
interface LegalSectionProps {
|
||||||
title: string;
|
title: string;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
anchorId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LegalPage({
|
export function LegalPage({
|
||||||
@@ -58,9 +59,9 @@ export function LegalPage({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LegalSection({ title, children }: LegalSectionProps) {
|
export function LegalSection({ title, children, anchorId }: LegalSectionProps) {
|
||||||
return (
|
return (
|
||||||
<Box component="section" className={classes.section}>
|
<Box component="section" id={anchorId} className={classes.section}>
|
||||||
<Title order={2} className={classes.sectionTitle}>
|
<Title order={2} className={classes.sectionTitle}>
|
||||||
{title}
|
{title}
|
||||||
</Title>
|
</Title>
|
||||||
|
|||||||
@@ -5,15 +5,42 @@ export function ScrollToTop() {
|
|||||||
const { pathname, hash } = useLocation();
|
const { pathname, hash } = useLocation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const frame = window.requestAnimationFrame(() => {
|
|
||||||
if (hash) {
|
if (hash) {
|
||||||
const target = document.getElementById(decodeURIComponent(hash.slice(1)));
|
let targetId: string;
|
||||||
if (target) {
|
|
||||||
target.scrollIntoView();
|
try {
|
||||||
return;
|
targetId = decodeURIComponent(hash.slice(1));
|
||||||
}
|
} catch {
|
||||||
|
targetId = hash.slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scrollToTarget = () => {
|
||||||
|
const target = document.getElementById(targetId);
|
||||||
|
|
||||||
|
if (!target) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.scrollIntoView({ block: 'start' });
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (scrollToTarget()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
if (scrollToTarget()) {
|
||||||
|
observer.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
const frame = window.requestAnimationFrame(() => {
|
||||||
window.scrollTo({ top: 0, left: 0, behavior: 'instant' });
|
window.scrollTo({ top: 0, left: 0, behavior: 'instant' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export function SiteFooter() {
|
|||||||
return (
|
return (
|
||||||
<footer className={classes.footer}>
|
<footer className={classes.footer}>
|
||||||
<Container>
|
<Container>
|
||||||
<SimpleGrid cols={{ base: 1, sm: 3 }} spacing="xl">
|
<SimpleGrid cols={{ base: 1, xs: 2, md: 4 }} spacing="xl">
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Group gap="sm">
|
<Group gap="sm">
|
||||||
<RosettaLogo size={34} />
|
<RosettaLogo size={34} />
|
||||||
@@ -33,14 +33,23 @@ export function SiteFooter() {
|
|||||||
<Anchor component={Link} to="/#apps" c="dimmed" size="sm">
|
<Anchor component={Link} to="/#apps" c="dimmed" size="sm">
|
||||||
Apps
|
Apps
|
||||||
</Anchor>
|
</Anchor>
|
||||||
<Anchor component={Link} to="/encryption" c="dimmed" size="sm">
|
<Anchor component={Link} to="/support" c="dimmed" size="sm">
|
||||||
Encryption
|
Support
|
||||||
</Anchor>
|
</Anchor>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Text fw={600} size="sm">
|
||||||
|
Developers
|
||||||
|
</Text>
|
||||||
<Anchor component={Link} to="/proto" c="dimmed" size="sm">
|
<Anchor component={Link} to="/proto" c="dimmed" size="sm">
|
||||||
Proto
|
Proto
|
||||||
</Anchor>
|
</Anchor>
|
||||||
<Anchor component={Link} to="/support" c="dimmed" size="sm">
|
<Anchor component={Link} to="/encryption" c="dimmed" size="sm">
|
||||||
Support
|
Encryption
|
||||||
|
</Anchor>
|
||||||
|
<Anchor component={Link} to="/rcc" c="dimmed" size="sm">
|
||||||
|
RCC
|
||||||
</Anchor>
|
</Anchor>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -2758,35 +2758,35 @@ export const protoLayers = [
|
|||||||
"name": "preview",
|
"name": "preview",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "превью (для фото blurhash)"
|
"description": "Превью (для фото blurhash)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"name": "objectId",
|
"name": "objectId",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "обьект на cdn"
|
"description": "Обьект на cdn"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"name": "type",
|
"name": "type",
|
||||||
"type": "ProfileMediaType",
|
"type": "ProfileMediaType",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "тип медиа"
|
"description": "Тип медиа"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 4,
|
||||||
"name": "timestamp",
|
"name": "timestamp",
|
||||||
"type": "int64",
|
"type": "int64",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "дата публикации"
|
"description": "Дата публикации"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"name": "mediaKey",
|
"name": "mediaKey",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "ключ шифрования от медиа"
|
"description": "Ключ шифрования от медиа"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -2863,42 +2863,42 @@ export const protoLayers = [
|
|||||||
"name": "username",
|
"name": "username",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "имя для поиска"
|
"description": "Имя для поиска"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"name": "title",
|
"name": "title",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "отображаемое имя пользователя"
|
"description": "Отображаемое имя пользователя"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"name": "publicKey",
|
"name": "publicKey",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "публичный ключ"
|
"description": "Публичный ключ"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 4,
|
||||||
"name": "verified",
|
"name": "verified",
|
||||||
"type": "int32",
|
"type": "int32",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "верификация"
|
"description": "Верификация"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"name": "networkStatus",
|
"name": "networkStatus",
|
||||||
"type": "NetworkStatus",
|
"type": "NetworkStatus",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "онлайн пользователь или нет"
|
"description": "Онлайн пользователь или нет"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 6,
|
"id": 6,
|
||||||
"name": "avatar",
|
"name": "avatar",
|
||||||
"type": "ProfileMedia",
|
"type": "ProfileMedia",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"description": "аватар пользователя если есть"
|
"description": "Аватар пользователя если есть"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -2912,42 +2912,42 @@ export const protoLayers = [
|
|||||||
"name": "username",
|
"name": "username",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "имя для поиска"
|
"description": "Имя для поиска"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"name": "title",
|
"name": "title",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "отображаемое имя пользователя"
|
"description": "Отображаемое имя пользователя"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"name": "publicKey",
|
"name": "publicKey",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "публичный ключ"
|
"description": "Публичный ключ"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 4,
|
||||||
"name": "verified",
|
"name": "verified",
|
||||||
"type": "int32",
|
"type": "int32",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "верификация"
|
"description": "Верификация"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"name": "networkStatus",
|
"name": "networkStatus",
|
||||||
"type": "NetworkStatus",
|
"type": "NetworkStatus",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "онлайн пользователь или нет"
|
"description": "Онлайн пользователь или нет"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 6,
|
"id": 6,
|
||||||
"name": "media",
|
"name": "media",
|
||||||
"type": "ProfileMedia[]",
|
"type": "ProfileMedia[]",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"description": "все активное медиа (включая будущие истории)"
|
"description": "Все активное медиа (включая будущие истории)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -3361,7 +3361,7 @@ export const protoLayers = [
|
|||||||
"name": "server",
|
"name": "server",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"description": "выбранный транспортный сервер"
|
"description": "Выбранный транспортный сервер"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -3690,42 +3690,42 @@ export const protoLayers = [
|
|||||||
"name": "size",
|
"name": "size",
|
||||||
"type": "uint32",
|
"type": "uint32",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "размер загружаемого файла (декларируемый)"
|
"description": "Размер загружаемого файла (декларируемый)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"name": "objectId",
|
"name": "objectId",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"description": "выделенный сервером objectId"
|
"description": "Выделенный сервером objectId"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"name": "chunkLength",
|
"name": "chunkLength",
|
||||||
"type": "uint32",
|
"type": "uint32",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"description": "размер чанка"
|
"description": "Размер чанка"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 4,
|
||||||
"name": "chunks",
|
"name": "chunks",
|
||||||
"type": "uint32",
|
"type": "uint32",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"description": "количество чанков"
|
"description": "Количество чанков"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"name": "host",
|
"name": "host",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"description": "хост, где сохранен файл"
|
"description": "Хост, где сохранен файл"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 6,
|
"id": 6,
|
||||||
"name": "ttl",
|
"name": "ttl",
|
||||||
"type": "uint32",
|
"type": "uint32",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"description": "ttl в секундах, после которого файл будет удален с CDN и с таблицы, по умолчанию 7 дней, не допускается использование слишком больших значений"
|
"description": "Ttl в секундах, после которого файл будет удален с CDN и с таблицы, по умолчанию 7 дней, не допускается использование слишком больших значений"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -3740,35 +3740,35 @@ export const protoLayers = [
|
|||||||
"name": "size",
|
"name": "size",
|
||||||
"type": "uint32",
|
"type": "uint32",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"description": "размер файла"
|
"description": "Размер файла"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"name": "objectId",
|
"name": "objectId",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "выделенный сервером objectId"
|
"description": "Выделенный сервером objectId"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"name": "chunkLength",
|
"name": "chunkLength",
|
||||||
"type": "uint32",
|
"type": "uint32",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"description": "размер чанка"
|
"description": "Размер чанка"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 4,
|
||||||
"name": "chunks",
|
"name": "chunks",
|
||||||
"type": "uint32",
|
"type": "uint32",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"description": "количество чанков"
|
"description": "Количество чанков"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"name": "host",
|
"name": "host",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"description": "сервер с которого скачивать файл"
|
"description": "Сервер с которого скачивать файл"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -3783,21 +3783,21 @@ export const protoLayers = [
|
|||||||
"name": "objectId",
|
"name": "objectId",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "выделенный сервером objectId при отправке пакета UploadCandidate"
|
"description": "Выделенный сервером objectId при отправке пакета UploadCandidate"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"name": "blurhash",
|
"name": "blurhash",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "превью аватара, строка blurhash"
|
"description": "Превью аватара, строка blurhash"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"name": "mediaKey",
|
"name": "mediaKey",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "ключ шифрования"
|
"description": "Ключ шифрования"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -3812,7 +3812,7 @@ export const protoLayers = [
|
|||||||
"name": "objectId",
|
"name": "objectId",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"optional": false,
|
"optional": false,
|
||||||
"description": "objectId аватара, аватар или фотография профиля должна при этом стоять у профиля отправителя пакета"
|
"description": "ObjectId аватара, аватар или фотография профиля должна при этом стоять у профиля отправителя пакета"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
36
src/main.tsx
36
src/main.tsx
@@ -2,6 +2,42 @@ import { createRoot } from 'react-dom/client'
|
|||||||
import { BrowserRouter } from 'react-router-dom'
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
|
|
||||||
|
const PRELOAD_RELOAD_KEY = 'rosetta:preload-reload-at'
|
||||||
|
const PRELOAD_RELOAD_COOLDOWN = 10_000
|
||||||
|
|
||||||
|
if (import.meta.env.PROD) {
|
||||||
|
window.addEventListener('vite:preloadError', (event) => {
|
||||||
|
let lastReload = 0
|
||||||
|
|
||||||
|
try {
|
||||||
|
lastReload = Number(window.sessionStorage.getItem(PRELOAD_RELOAD_KEY) || 0)
|
||||||
|
} catch {
|
||||||
|
// Storage can be unavailable in restricted browser modes.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Date.now() - lastReload < PRELOAD_RELOAD_COOLDOWN) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.sessionStorage.setItem(PRELOAD_RELOAD_KEY, String(Date.now()))
|
||||||
|
} catch {
|
||||||
|
// Reload recovery still works when storage is unavailable.
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.reload()
|
||||||
|
})
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
try {
|
||||||
|
window.sessionStorage.removeItem(PRELOAD_RELOAD_KEY)
|
||||||
|
} catch {
|
||||||
|
// Nothing to clear when storage is unavailable.
|
||||||
|
}
|
||||||
|
}, PRELOAD_RELOAD_COOLDOWN)
|
||||||
|
}
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
|
|||||||
@@ -31,9 +31,9 @@
|
|||||||
|
|
||||||
.callout {
|
.callout {
|
||||||
margin: 18px 0 26px;
|
margin: 18px 0 26px;
|
||||||
border: 1px solid light-dark(var(--mantine-color-blue-2), var(--mantine-color-blue-9));
|
border: 1px solid light-dark(var(--mantine-color-red-3), var(--mantine-color-red-9));
|
||||||
border-left: 3px solid var(--mantine-color-blue-6);
|
border-left: 3px solid var(--mantine-color-red-6);
|
||||||
background: light-dark(var(--mantine-color-blue-0), rgba(25, 113, 194, 0.12));
|
background: light-dark(var(--mantine-color-red-0), rgba(250, 82, 82, 0.1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.vectorGroup {
|
.vectorGroup {
|
||||||
@@ -58,17 +58,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.marker {
|
.marker {
|
||||||
padding: 1px 3px;
|
padding: 0 0.14em;
|
||||||
background: light-dark(var(--mantine-color-blue-1), rgba(25, 113, 194, 0.24));
|
border-radius: 2px;
|
||||||
color: inherit;
|
background: light-dark(rgba(250, 82, 82, 0.13), rgba(250, 82, 82, 0.2));
|
||||||
|
color: light-dark(var(--mantine-color-red-9), var(--mantine-color-red-2));
|
||||||
|
font-weight: inherit;
|
||||||
|
box-decoration-break: clone;
|
||||||
|
-webkit-box-decoration-break: clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result {
|
.result {
|
||||||
margin: 18px 0;
|
margin: 18px 0;
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
border-left: 3px solid var(--mantine-color-blue-6);
|
border-left: 3px solid var(--mantine-color-red-6);
|
||||||
background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7));
|
background: light-dark(var(--mantine-color-red-0), rgba(250, 82, 82, 0.1));
|
||||||
color: light-dark(var(--mantine-color-blue-9), var(--mantine-color-blue-2));
|
color: light-dark(var(--mantine-color-red-9), var(--mantine-color-red-2));
|
||||||
font-size: var(--mantine-font-size-sm);
|
font-size: var(--mantine-font-size-sm);
|
||||||
font-weight: 650;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,10 +185,11 @@ keyEnvelope = Base64(iv || AES-CBC(wrappingKey, messageSecret) || ephemeral.publ
|
|||||||
</Box>
|
</Box>
|
||||||
<LegalText>
|
<LegalText>
|
||||||
The result contains 13 encrypted content bytes followed by the 16-byte
|
The result contains 13 encrypted content bytes followed by the 16-byte
|
||||||
Poly1305 authentication tag.{' '}
|
Poly1305 authentication tag. Any change to either part causes{' '}
|
||||||
<Text component="span" className={classes.marker}>
|
<Text component="span" className={classes.marker}>
|
||||||
Any change to either part causes authenticated decryption to fail.
|
authenticated decryption to fail
|
||||||
</Text>
|
</Text>
|
||||||
|
.
|
||||||
</LegalText>
|
</LegalText>
|
||||||
|
|
||||||
<Title order={3} className={classes.subheading}>
|
<Title order={3} className={classes.subheading}>
|
||||||
|
|||||||
@@ -126,6 +126,14 @@ export function PrivacyPage() {
|
|||||||
some requests may require you to provide information that helps us verify
|
some requests may require you to provide information that helps us verify
|
||||||
control of the relevant account or keys.
|
control of the relevant account or keys.
|
||||||
</LegalText>
|
</LegalText>
|
||||||
|
<LegalText>
|
||||||
|
For step-by-step deletion instructions, including what data may remain, see
|
||||||
|
the{' '}
|
||||||
|
<Anchor component={Link} to="/terms#account-deletion">
|
||||||
|
Account Deletion section of the Terms of Use
|
||||||
|
</Anchor>
|
||||||
|
.
|
||||||
|
</LegalText>
|
||||||
</LegalSection>
|
</LegalSection>
|
||||||
|
|
||||||
<LegalSection title="8. Children">
|
<LegalSection title="8. Children">
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
.protoCode {
|
.protoCode {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
margin: 18px 0;
|
margin: 18px 0;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-5));
|
border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-5));
|
||||||
@@ -23,18 +26,37 @@
|
|||||||
font-weight: 650;
|
font-weight: 650;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.important {
|
||||||
|
padding: 0 0.14em;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: light-dark(rgba(250, 82, 82, 0.13), rgba(250, 82, 82, 0.2));
|
||||||
|
color: light-dark(var(--mantine-color-red-9), var(--mantine-color-red-2));
|
||||||
|
font-weight: inherit;
|
||||||
|
box-decoration-break: clone;
|
||||||
|
-webkit-box-decoration-break: clone;
|
||||||
|
}
|
||||||
|
|
||||||
.layerExamples {
|
.layerExamples {
|
||||||
|
min-width: 0;
|
||||||
margin: 18px 0 24px;
|
margin: 18px 0 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layerExamples > * {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.layerExamples .protoCode {
|
.layerExamples .protoCode {
|
||||||
height: calc(100% - 30px);
|
height: calc(100% - 30px);
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.docsLinks {
|
||||||
|
margin-top: 44px;
|
||||||
|
gap: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
.docsLink {
|
.docsLink {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 44px;
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
font-size: var(--mantine-font-size-sm);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@@ -51,6 +73,8 @@
|
|||||||
|
|
||||||
.docsGrid {
|
.docsGrid {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
grid-template-columns: 220px minmax(0, 1fr);
|
grid-template-columns: 220px minmax(0, 1fr);
|
||||||
gap: 44px;
|
gap: 44px;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
@@ -59,6 +83,7 @@
|
|||||||
.sidebar {
|
.sidebar {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 92px;
|
top: 92px;
|
||||||
|
min-width: 0;
|
||||||
max-height: calc(100vh - 112px);
|
max-height: calc(100vh - 112px);
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -67,8 +92,10 @@
|
|||||||
|
|
||||||
.sidebar a {
|
.sidebar a {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
|
max-width: 100%;
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar a:hover {
|
.sidebar a:hover {
|
||||||
@@ -77,6 +104,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.layerDescription {
|
.layerDescription {
|
||||||
|
min-width: 0;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-5));
|
border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-5));
|
||||||
border-radius: var(--mantine-radius-xs);
|
border-radius: var(--mantine-radius-xs);
|
||||||
@@ -85,7 +113,9 @@
|
|||||||
|
|
||||||
.layerDescription code {
|
.layerDescription code {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.documentation {
|
.documentation {
|
||||||
@@ -100,6 +130,7 @@
|
|||||||
|
|
||||||
.schemaSection {
|
.schemaSection {
|
||||||
scroll-margin-top: 96px;
|
scroll-margin-top: 96px;
|
||||||
|
min-width: 0;
|
||||||
margin-top: 64px;
|
margin-top: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +143,7 @@
|
|||||||
|
|
||||||
.definition {
|
.definition {
|
||||||
scroll-margin-top: 96px;
|
scroll-margin-top: 96px;
|
||||||
|
min-width: 0;
|
||||||
margin-top: 38px;
|
margin-top: 38px;
|
||||||
padding-top: 32px;
|
padding-top: 32px;
|
||||||
border-top: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-5));
|
border-top: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-5));
|
||||||
@@ -126,6 +158,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.yamlDetails {
|
.yamlDetails {
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +173,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.yamlBlock {
|
.yamlBlock {
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
padding: 12px 12px 16px 18px;
|
padding: 12px 12px 16px 18px;
|
||||||
border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-5));
|
border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-5));
|
||||||
@@ -148,6 +184,9 @@
|
|||||||
|
|
||||||
.yamlBlock code {
|
.yamlBlock code {
|
||||||
display: block;
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0));
|
color: light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0));
|
||||||
@@ -162,6 +201,12 @@
|
|||||||
border-radius: var(--mantine-radius-xs);
|
border-radius: var(--mantine-radius-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tableScroll {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.table th {
|
.table th {
|
||||||
background: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-7));
|
background: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-7));
|
||||||
}
|
}
|
||||||
@@ -179,7 +224,7 @@
|
|||||||
|
|
||||||
@media (max-width: 62em) {
|
@media (max-width: 62em) {
|
||||||
.docsGrid {
|
.docsGrid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ function FieldTable({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table.ScrollContainer minWidth={620}>
|
<Table.ScrollContainer minWidth={620} className={classes.tableScroll}>
|
||||||
<Table verticalSpacing="sm" horizontalSpacing="md" className={classes.table}>
|
<Table verticalSpacing="sm" horizontalSpacing="md" className={classes.table}>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
@@ -285,7 +285,7 @@ function EnumDefinition({ item, layer }: { item: ProtoEnum; layer: ProtoLayer })
|
|||||||
{item.description || `Enumeration ${item.name} in protocol layer ${layer.version}.`}
|
{item.description || `Enumeration ${item.name} in protocol layer ${layer.version}.`}
|
||||||
</Text>
|
</Text>
|
||||||
<HiddenYaml value={item.yaml} />
|
<HiddenYaml value={item.yaml} />
|
||||||
<Table.ScrollContainer minWidth={520}>
|
<Table.ScrollContainer minWidth={520} className={classes.tableScroll}>
|
||||||
<Table verticalSpacing="sm" horizontalSpacing="md" className={classes.table}>
|
<Table verticalSpacing="sm" horizontalSpacing="md" className={classes.table}>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
@@ -517,6 +517,56 @@ export class RccGeneratedPacketRegistry {
|
|||||||
</Code>
|
</Code>
|
||||||
</LegalSection>
|
</LegalSection>
|
||||||
|
|
||||||
|
<LegalSection title="RCC: one schema contract, two generation modes">
|
||||||
|
<LegalText>
|
||||||
|
Rosetta Code Compiler (RCC) is the build-time compiler behind the protocol.
|
||||||
|
It reads every <Code>layer.N.yml</Code> snapshot, turns the YAML into a
|
||||||
|
typed protocol model, validates that the history is safe, and emits source
|
||||||
|
code for the selected language. Applications import generated models and
|
||||||
|
codecs; they do not parse the YAML at runtime.
|
||||||
|
</LegalText>
|
||||||
|
<Code block className={classes.protoCode}>
|
||||||
|
{`proto/layer.*.yml
|
||||||
|
│
|
||||||
|
├─ parse packets, reusable types, enums and comments
|
||||||
|
├─ validate references, required fields and stable field IDs
|
||||||
|
│
|
||||||
|
├─ mode=server → all layers + version-aware codecs + registry
|
||||||
|
└─ mode=client → latest layer + latest codecs + version constant`}
|
||||||
|
</Code>
|
||||||
|
<LegalList>
|
||||||
|
<List.Item>
|
||||||
|
<strong>Server generation</strong> preserves the protocol timeline.
|
||||||
|
Java codecs receive the negotiated layer, include only fields active in
|
||||||
|
that layer, and expose the compiled minimum and maximum versions.
|
||||||
|
</List.Item>
|
||||||
|
<List.Item>
|
||||||
|
<strong>Client generation</strong> selects the{' '}
|
||||||
|
<Text component="span" className={classes.important}>
|
||||||
|
highest layer
|
||||||
|
</Text>
|
||||||
|
{' '}and creates the current packet, type, enum, codec, and registry
|
||||||
|
sources without carrying historical shapes into the application.
|
||||||
|
</List.Item>
|
||||||
|
<List.Item>
|
||||||
|
<strong>Schema validation</strong> fails the build for unknown types or
|
||||||
|
unsafe field-ID reuse, so incompatible protocol edits are caught before
|
||||||
|
generated sources reach a server or client package.
|
||||||
|
</List.Item>
|
||||||
|
<List.Item>
|
||||||
|
<strong>Generated documentation</strong> carries schema comments into
|
||||||
|
Javadoc or TSDoc, including field direction, wire type, optionality, and
|
||||||
|
accessor contracts.
|
||||||
|
</List.Item>
|
||||||
|
</LegalList>
|
||||||
|
<LegalText>
|
||||||
|
The RCC guide includes separate client and version-aware server examples.{' '}
|
||||||
|
Run <Code className={classes.important}>npx rcc --list-languages</Code> for
|
||||||
|
the authoritative list of language targets in the installed compiler
|
||||||
|
version.
|
||||||
|
</LegalText>
|
||||||
|
</LegalSection>
|
||||||
|
|
||||||
<LegalSection title="How an RCC YAML layer is structured">
|
<LegalSection title="How an RCC YAML layer is structured">
|
||||||
<LegalText>
|
<LegalText>
|
||||||
RCC reads versioned files named <Code>layer.N.yml</Code>. The top-level
|
RCC reads versioned files named <Code>layer.N.yml</Code>. The top-level
|
||||||
@@ -624,7 +674,7 @@ reader.getInt8(fieldId: 1) -> ResultCode.SUCCESS`}
|
|||||||
^^^^^ ^^^^^^^^
|
^^^^^ ^^^^^^^^
|
||||||
packet field 1 · INT8 · SUCCESS (0)`}
|
packet field 1 · INT8 · SUCCESS (0)`}
|
||||||
</Code>
|
</Code>
|
||||||
<Table.ScrollContainer minWidth={560}>
|
<Table.ScrollContainer minWidth={560} className={classes.tableScroll}>
|
||||||
<Table verticalSpacing="sm" horizontalSpacing="md" className={classes.comparisonTable}>
|
<Table verticalSpacing="sm" horizontalSpacing="md" className={classes.comparisonTable}>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
@@ -664,9 +714,14 @@ packet field 1 · INT8 · SUCCESS (0)`}
|
|||||||
</LegalText>
|
</LegalText>
|
||||||
</LegalSection>
|
</LegalSection>
|
||||||
|
|
||||||
|
<Group className={classes.docsLinks}>
|
||||||
<Anchor component={Link} to="/proto/docs" className={classes.docsLink}>
|
<Anchor component={Link} to="/proto/docs" className={classes.docsLink}>
|
||||||
See docs of PROTO →
|
See docs of PROTO →
|
||||||
</Anchor>
|
</Anchor>
|
||||||
|
<Anchor component={Link} to="/rcc" className={classes.docsLink}>
|
||||||
|
See docs of RCC →
|
||||||
|
</Anchor>
|
||||||
|
</Group>
|
||||||
</LegalPage>
|
</LegalPage>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
119
src/pages/RccPage.module.css
Normal file
119
src/pages/RccPage.module.css
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
.code {
|
||||||
|
margin: 18px 0;
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-5));
|
||||||
|
border-radius: var(--mantine-radius-xs);
|
||||||
|
background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7));
|
||||||
|
color: light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0));
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.65;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 22px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline > span:nth-child(odd) {
|
||||||
|
padding: 14px;
|
||||||
|
border: 1px solid light-dark(var(--mantine-color-blue-2), var(--mantine-color-blue-9));
|
||||||
|
border-radius: var(--mantine-radius-xs);
|
||||||
|
background: light-dark(var(--mantine-color-blue-0), rgba(25, 113, 194, 0.12));
|
||||||
|
color: light-dark(var(--mantine-color-blue-9), var(--mantine-color-blue-1));
|
||||||
|
font-size: var(--mantine-font-size-sm);
|
||||||
|
font-weight: 650;
|
||||||
|
line-height: 1.45;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline > span:nth-child(even) {
|
||||||
|
color: var(--mantine-color-blue-6);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 34px minmax(0, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepNumber {
|
||||||
|
display: grid;
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
place-items: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--mantine-color-blue-6);
|
||||||
|
color: var(--mantine-color-white);
|
||||||
|
font-size: var(--mantine-font-size-sm);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepBody {
|
||||||
|
min-width: 0;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
border-bottom: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
|
||||||
|
}
|
||||||
|
|
||||||
|
.step:last-child .stepBody {
|
||||||
|
padding-bottom: 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subheading {
|
||||||
|
margin: 3px 0 12px;
|
||||||
|
color: light-dark(var(--mantine-color-dark-8), var(--mantine-color-dark-0));
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.important {
|
||||||
|
padding: 0 0.14em;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: light-dark(rgba(250, 82, 82, 0.13), rgba(250, 82, 82, 0.2));
|
||||||
|
color: light-dark(var(--mantine-color-red-9), var(--mantine-color-red-2));
|
||||||
|
font-weight: inherit;
|
||||||
|
box-decoration-break: clone;
|
||||||
|
-webkit-box-decoration-break: clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note {
|
||||||
|
margin: 18px 0;
|
||||||
|
border: 1px solid light-dark(var(--mantine-color-red-3), var(--mantine-color-red-9));
|
||||||
|
border-left: 3px solid var(--mantine-color-red-6);
|
||||||
|
background: light-dark(var(--mantine-color-red-0), rgba(250, 82, 82, 0.1));
|
||||||
|
color: light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 48em) {
|
||||||
|
.pipeline {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline > span:nth-child(even) {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 36em) {
|
||||||
|
.step {
|
||||||
|
grid-template-columns: 28px minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepNumber {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
249
src/pages/RccPage.tsx
Normal file
249
src/pages/RccPage.tsx
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
import { Anchor, Box, Code, List, Paper, Text, Title } from '@mantine/core';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import {
|
||||||
|
LegalList,
|
||||||
|
LegalPage,
|
||||||
|
LegalSection,
|
||||||
|
LegalText,
|
||||||
|
} from '../components/LegalPage/LegalPage';
|
||||||
|
import { SEO } from '../components/SEO/SEO';
|
||||||
|
import classes from './RccPage.module.css';
|
||||||
|
|
||||||
|
const GITEA_REPOSITORY = 'https://git.rosetta.im/Rosetta/rcc';
|
||||||
|
|
||||||
|
function TechnicalCode({ children }: { children: string }) {
|
||||||
|
return (
|
||||||
|
<Code block className={classes.code}>
|
||||||
|
{children}
|
||||||
|
</Code>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Step({ number, title, children }: { number: number; title: string; children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<Box className={classes.step}>
|
||||||
|
<Text className={classes.stepNumber}>{number}</Text>
|
||||||
|
<Box className={classes.stepBody}>
|
||||||
|
<Title order={3} className={classes.subheading}>
|
||||||
|
{title}
|
||||||
|
</Title>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RccPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SEO
|
||||||
|
title="Rosetta Code Compiler (RCC)"
|
||||||
|
description="Install RCC and generate validated protocol models, codecs, and registries from versioned Rosetta YAML schemas."
|
||||||
|
canonical="https://rosetta.im/rcc/"
|
||||||
|
keywords="RCC, Rosetta Code Compiler, code generation, protocol codecs, YAML protocol"
|
||||||
|
/>
|
||||||
|
<LegalPage eyebrow="Developer tooling" title="Rosetta Code Compiler">
|
||||||
|
<LegalText lead>
|
||||||
|
RCC compiles Rosetta's versioned YAML protocol into typed models, codecs,
|
||||||
|
and packet registries. A server build can retain the{' '}
|
||||||
|
<Text component="span" className={classes.important}>
|
||||||
|
complete protocol history
|
||||||
|
</Text>
|
||||||
|
; a client build stays small by generating only the{' '}
|
||||||
|
<Text component="span" className={classes.important}>
|
||||||
|
latest layer
|
||||||
|
</Text>
|
||||||
|
.
|
||||||
|
</LegalText>
|
||||||
|
|
||||||
|
<LegalSection title="What RCC produces">
|
||||||
|
<LegalText>
|
||||||
|
RCC treats <Code>proto/layer.*.yml</Code> as the protocol's source of
|
||||||
|
truth. Before writing code, it resolves primitive, enum, and reusable type
|
||||||
|
references and rejects incompatible reuse of a field ID between layers.
|
||||||
|
</LegalText>
|
||||||
|
<Box className={classes.pipeline}>
|
||||||
|
<Text component="span">Versioned YAML schemas</Text>
|
||||||
|
<Text component="span" aria-hidden="true">→</Text>
|
||||||
|
<Text component="span">Parse + validate</Text>
|
||||||
|
<Text component="span" aria-hidden="true">→</Text>
|
||||||
|
<Text component="span">Models + codecs + registry</Text>
|
||||||
|
</Box>
|
||||||
|
<LegalList>
|
||||||
|
<List.Item>
|
||||||
|
Packets become typed packet classes; reusable schema types become model
|
||||||
|
classes; enums preserve their numeric wire values.
|
||||||
|
</List.Item>
|
||||||
|
<List.Item>
|
||||||
|
Codecs encode and decode TLV fields and verify required fields before
|
||||||
|
encoding and after decoding.
|
||||||
|
</List.Item>
|
||||||
|
<List.Item>
|
||||||
|
The generated registry maps packet IDs to codecs and exposes the
|
||||||
|
compiled protocol version or version range.
|
||||||
|
</List.Item>
|
||||||
|
<List.Item>
|
||||||
|
YAML comments become Javadoc or TSDoc for generated declarations and
|
||||||
|
accessors.
|
||||||
|
</List.Item>
|
||||||
|
</LegalList>
|
||||||
|
</LegalSection>
|
||||||
|
|
||||||
|
<LegalSection title="Install RCC from Rosetta Gitea">
|
||||||
|
<LegalText>
|
||||||
|
Add Rosetta's npm package registry to the project, then install RCC. The
|
||||||
|
registry setting can live in a project-level <Code>.npmrc</Code> so CI and
|
||||||
|
local builds resolve the same compiler package.
|
||||||
|
</LegalText>
|
||||||
|
<TechnicalCode>{`# .npmrc
|
||||||
|
registry=https://git.rosetta.im/api/packages/Rosetta/npm/
|
||||||
|
|
||||||
|
# configure read-only registry authentication
|
||||||
|
npm config set -- \\
|
||||||
|
'//git.rosetta.im/api/packages/Rosetta/npm/:_authToken' \\
|
||||||
|
'b5e64acbf1fdbe2d6d2bc1fac95333ef6f905fff'
|
||||||
|
|
||||||
|
# install the compiler
|
||||||
|
npm install rcc
|
||||||
|
|
||||||
|
# query languages from the installed RCC version
|
||||||
|
npx rcc --list-languages
|
||||||
|
|
||||||
|
# inspect the rest of the CLI
|
||||||
|
npx rcc --help`}</TechnicalCode>
|
||||||
|
<LegalText>
|
||||||
|
RCC source is hosted in the{' '}
|
||||||
|
<Anchor href={GITEA_REPOSITORY} target="_blank" rel="noreferrer">
|
||||||
|
Rosetta/rcc Gitea repository
|
||||||
|
</Anchor>
|
||||||
|
.{' '}
|
||||||
|
<Text component="span" className={classes.important}>
|
||||||
|
Pin the dependency version
|
||||||
|
</Text>
|
||||||
|
{' '}in the project lockfile so generated output remains reproducible.
|
||||||
|
</LegalText>
|
||||||
|
</LegalSection>
|
||||||
|
|
||||||
|
<LegalSection title="Generate from schemas, step by step">
|
||||||
|
<Step number={1} title="Create versioned schema layers">
|
||||||
|
<LegalText>
|
||||||
|
Put complete protocol snapshots in one directory and name them
|
||||||
|
<Code> layer.N.yml</Code>. Increment the top-level version, keep packet
|
||||||
|
and field IDs stable, and repeat definitions that remain part of the
|
||||||
|
protocol.
|
||||||
|
</LegalText>
|
||||||
|
<TechnicalCode>{`proto/
|
||||||
|
├── layer.1.yml
|
||||||
|
├── layer.2.yml
|
||||||
|
└── layer.3.yml`}</TechnicalCode>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step number={2} title="Choose client or server output">
|
||||||
|
<LegalText>
|
||||||
|
Use <Code>client</Code> for the current application package. RCC selects
|
||||||
|
the highest schema version. Use <Code>server</Code> when the runtime must
|
||||||
|
decode connections negotiated on older supported layers. Full
|
||||||
|
multi-version codec generation is currently implemented by the Java
|
||||||
|
backend.
|
||||||
|
</LegalText>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step number={3} title="Generate Java sources">
|
||||||
|
<LegalText>
|
||||||
|
Server generation combines all layers and emits version-aware codecs,
|
||||||
|
models, enums, and a registry with <Code>MIN_VERSION</Code> and
|
||||||
|
<Code> MAX_VERSION</Code>.
|
||||||
|
</LegalText>
|
||||||
|
<TechnicalCode>{`npx rcc \\
|
||||||
|
--mode server \\
|
||||||
|
--lang java \\
|
||||||
|
--protoDir ./proto \\
|
||||||
|
--outDir ./server/src/generated/java \\
|
||||||
|
--basePackage im.rosetta.network \\
|
||||||
|
--clean`}</TechnicalCode>
|
||||||
|
<LegalText>
|
||||||
|
For a Java client, change the mode and output directory. The generated
|
||||||
|
sources describe only the latest layer.
|
||||||
|
</LegalText>
|
||||||
|
<TechnicalCode>{`npx rcc \\
|
||||||
|
--mode client \\
|
||||||
|
--lang java \\
|
||||||
|
--protoDir ./proto \\
|
||||||
|
--outDir ./client/src/generated/java \\
|
||||||
|
--basePackage im.rosetta.network \\
|
||||||
|
--clean`}</TechnicalCode>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step number={4} title="Generate TypeScript sources">
|
||||||
|
<LegalText>
|
||||||
|
TypeScript is the current latest-layer client target. It produces
|
||||||
|
models, enums, codecs, and <Code>RccGeneratedPacketRegistry.VERSION</Code>.
|
||||||
|
</LegalText>
|
||||||
|
<TechnicalCode>{`npx rcc \\
|
||||||
|
--mode client \\
|
||||||
|
--lang typescript \\
|
||||||
|
--protoDir ./proto \\
|
||||||
|
--outDir ./client/src/generated/protocol \\
|
||||||
|
--clean`}</TechnicalCode>
|
||||||
|
<Paper className={classes.note} radius="sm" p="md">
|
||||||
|
<Text size="sm" lh={1.7}>
|
||||||
|
Always run{' '}
|
||||||
|
<Code className={classes.important}>npx rcc --list-languages</Code>{' '}
|
||||||
|
before configuring a generator. The installed compiler's output is
|
||||||
|
the source of truth for available language targets.
|
||||||
|
</Text>
|
||||||
|
</Paper>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step number={5} title="Run all targets from one prebuild config">
|
||||||
|
<LegalText>
|
||||||
|
Keep client and server generation together in
|
||||||
|
<Code> rcc-prebuild.yml</Code>. Paths are resolved relative to the
|
||||||
|
config file; <Code>clean: true</Code> clears only each configured output
|
||||||
|
directory.
|
||||||
|
</LegalText>
|
||||||
|
<TechnicalCode>{`watch: false
|
||||||
|
protoDir: ./proto
|
||||||
|
clean: true
|
||||||
|
|
||||||
|
compilers:
|
||||||
|
- name: TypeScript client
|
||||||
|
mode: client
|
||||||
|
lang: typescript
|
||||||
|
output: ./client/src/generated/protocol
|
||||||
|
|
||||||
|
- name: Java server
|
||||||
|
mode: server
|
||||||
|
lang: java
|
||||||
|
output: ./server/src/generated/java
|
||||||
|
basePackage: im.rosetta.network`}</TechnicalCode>
|
||||||
|
<TechnicalCode>{`npx rcc -f ./rcc-prebuild.yml`}</TechnicalCode>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step number={6} title="Compile and package the generated sources">
|
||||||
|
<LegalText>
|
||||||
|
Add the output directory to the language build, then run generation
|
||||||
|
before compilation in local development and CI. RCC writes source
|
||||||
|
files; the surrounding Gradle, npm, or application build is responsible
|
||||||
|
for compiling and publishing the final server or client package.
|
||||||
|
</LegalText>
|
||||||
|
</Step>
|
||||||
|
</LegalSection>
|
||||||
|
|
||||||
|
<LegalSection title="CLI reference">
|
||||||
|
<TechnicalCode>{`npx rcc --mode <client|server> [options]
|
||||||
|
npx rcc -f <filename.yml>
|
||||||
|
npx rcc --list-languages
|
||||||
|
|
||||||
|
--mode, -m client | server
|
||||||
|
--lang, -l language returned by --list-languages
|
||||||
|
--protoDir, -p schema directory (default: ./proto)
|
||||||
|
--outDir, -o generated source directory (default: ./generated)
|
||||||
|
--basePackage, -b Java base package (default: im.rosetta.generated)
|
||||||
|
--clean clean the output directory before generation
|
||||||
|
--file, -f prebuild YAML config`}</TechnicalCode>
|
||||||
|
</LegalSection>
|
||||||
|
</LegalPage>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -218,6 +218,60 @@ export function TermsPage() {
|
|||||||
Phone: <Anchor href="tel:+85228910030">+852 2891 0030</Anchor>
|
Phone: <Anchor href="tel:+85228910030">+852 2891 0030</Anchor>
|
||||||
</LegalText>
|
</LegalText>
|
||||||
</LegalSection>
|
</LegalSection>
|
||||||
|
|
||||||
|
<LegalSection title="20. Account Deletion" anchorId="account-deletion">
|
||||||
|
<LegalText>
|
||||||
|
You can permanently delete your Rosetta account in the app. This action
|
||||||
|
cannot be undone. Rosetta accounts use cryptographic identities, so no phone
|
||||||
|
number or email address is required to create or delete one.
|
||||||
|
</LegalText>
|
||||||
|
<LegalText>To delete the account currently signed in on an Android device:</LegalText>
|
||||||
|
<LegalList>
|
||||||
|
<List.Item>Open Rosetta and sign in to the account you want to remove.</List.Item>
|
||||||
|
<List.Item>Open Settings, then choose Safety.</List.Item>
|
||||||
|
<List.Item>Select Delete Account.</List.Item>
|
||||||
|
<List.Item>Review the confirmation and select Delete.</List.Item>
|
||||||
|
</LegalList>
|
||||||
|
<LegalText>
|
||||||
|
Deleting an account removes its local account record, message history,
|
||||||
|
conversations, cached profile media, and other account-scoped data from the
|
||||||
|
device where you complete the deletion. It also ends the signed-in session
|
||||||
|
for that account on that device.
|
||||||
|
</LegalText>
|
||||||
|
<LegalText>
|
||||||
|
Rosetta is designed so readable message history stays on users' devices,
|
||||||
|
not as a readable server-side message archive. Encrypted data in transit is
|
||||||
|
processed only as needed to deliver the service. Deletion does not remove
|
||||||
|
messages, attachments, or other data that other people have already received
|
||||||
|
and stored on their own devices.
|
||||||
|
</LegalText>
|
||||||
|
<LegalText>
|
||||||
|
Limited technical logs and support communications may be retained only for
|
||||||
|
as long as reasonably necessary for security, reliability, support, legal,
|
||||||
|
and operational purposes, as described in our{' '}
|
||||||
|
<Anchor component={Link} to="/privacy">
|
||||||
|
Privacy Policy
|
||||||
|
</Anchor>
|
||||||
|
.
|
||||||
|
</LegalText>
|
||||||
|
<LegalText>
|
||||||
|
If you cannot sign in or need help with an account-deletion request, email{' '}
|
||||||
|
<Anchor href="mailto:admin@rosetta.im?subject=Rosetta%20Account%20Deletion%20Request">
|
||||||
|
admin@rosetta.im
|
||||||
|
</Anchor>
|
||||||
|
. Include the public account identifier, if available, and enough detail for
|
||||||
|
us to locate the request. We may ask you to verify control of the account or
|
||||||
|
its keys before acting on a request. Never send your private key or recovery
|
||||||
|
phrase by email or to support.
|
||||||
|
</LegalText>
|
||||||
|
<LegalText>
|
||||||
|
For help, contact{' '}
|
||||||
|
<Anchor href="mailto:support@rosetta.im?subject=Rosetta%20Account%20Deletion%20Help">
|
||||||
|
support@rosetta.im
|
||||||
|
</Anchor>
|
||||||
|
.
|
||||||
|
</LegalText>
|
||||||
|
</LegalSection>
|
||||||
</LegalPage>
|
</LegalPage>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
function withFreshHtml(response) {
|
||||||
|
if (!response.headers.get('content-type')?.includes('text/html')) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = new Headers(response.headers);
|
||||||
|
headers.set('Cache-Control', 'no-cache');
|
||||||
|
|
||||||
|
return new Response(response.body, {
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const worker = {
|
const worker = {
|
||||||
async fetch(request, env) {
|
async fetch(request, env) {
|
||||||
const response = await env.ASSETS.fetch(request);
|
const response = await env.ASSETS.fetch(request);
|
||||||
@@ -7,11 +22,12 @@ const worker = {
|
|||||||
request.method !== 'GET' ||
|
request.method !== 'GET' ||
|
||||||
!request.headers.get('accept')?.includes('text/html')
|
!request.headers.get('accept')?.includes('text/html')
|
||||||
) {
|
) {
|
||||||
return response;
|
return withFreshHtml(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexUrl = new URL('/index.html', request.url);
|
const indexUrl = new URL('/index.html', request.url);
|
||||||
return env.ASSETS.fetch(new Request(indexUrl, request));
|
const indexResponse = await env.ASSETS.fetch(new Request(indexUrl, request));
|
||||||
|
return withFreshHtml(indexResponse);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user