Skip to content

Commit ea17523

Browse files
authored
Merge pull request #5 from Eilodon/architecture-2.0
feat(security): Airlock proxy, Vault hardening, and Build optimization
2 parents ec55430 + 8f858a0 commit ea17523

9 files changed

Lines changed: 593 additions & 242 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ dist-ssr
2222
*.njsproj
2323
*.sln
2424
*.sw?
25+
.env

index.html

Lines changed: 85 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@
2323
background-color: #111827;
2424
}
2525
</style>
26-
26+
2727
<!-- Apple Touch Icon -->
28-
<link rel="apple-touch-icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🪷</text></svg>">
28+
<link rel="apple-touch-icon"
29+
href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🪷</text></svg>">
2930

3031
<!-- Optimization Hints -->
3132
<link rel="preconnect" href="https://aistudiocdn.com" crossorigin>
@@ -45,6 +46,20 @@
4546
<meta property="og:description" content="Đồng hành tâm linh với AI inspired by Thích Nhất Hạnh">
4647
<meta property="og:type" content="website">
4748

49+
<!-- Security Headers -->
50+
<meta http-equiv="Content-Security-Policy" content="
51+
default-src 'self';
52+
script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval';
53+
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
54+
font-src 'self' https://fonts.gstatic.com data:;
55+
img-src 'self' data: blob:;
56+
media-src 'self' blob:;
57+
connect-src 'self' https://generativelanguage.googleapis.com wss://generativelanguage.googleapis.com;
58+
worker-src 'self' blob:;
59+
base-uri 'self';
60+
form-action 'self';
61+
">
62+
4863
<link rel="icon"
4964
href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🪷</text></svg>">
5065

@@ -132,18 +147,24 @@
132147

133148
/* Enhanced animations */
134149
@keyframes glow {
135-
0%, 100% {
150+
151+
0%,
152+
100% {
136153
box-shadow: 0 0 20px rgba(0, 243, 255, 0.3);
137154
}
155+
138156
50% {
139157
box-shadow: 0 0 40px rgba(0, 243, 255, 0.6);
140158
}
141159
}
142160

143161
@keyframes float {
144-
0%, 100% {
162+
163+
0%,
164+
100% {
145165
transform: translateY(0px);
146166
}
167+
147168
50% {
148169
transform: translateY(-10px);
149170
}
@@ -153,24 +174,31 @@
153174
0% {
154175
background-position: -200% center;
155176
}
177+
156178
100% {
157179
background-position: 200% center;
158180
}
159181
}
160182

161183
@keyframes pulse-slow {
162-
0%, 100% {
184+
185+
0%,
186+
100% {
163187
opacity: 0.8;
164188
}
189+
165190
50% {
166191
opacity: 1;
167192
}
168193
}
169194

170195
@keyframes bounce-subtle {
171-
0%, 100% {
196+
197+
0%,
198+
100% {
172199
transform: translateY(0);
173200
}
201+
174202
50% {
175203
transform: translateY(-2px);
176204
}
@@ -180,6 +208,7 @@
180208
from {
181209
transform: rotate(0deg);
182210
}
211+
183212
to {
184213
transform: rotate(360deg);
185214
}
@@ -189,6 +218,7 @@
189218
0% {
190219
transform: translateX(-100%);
191220
}
221+
192222
100% {
193223
transform: translateX(100%);
194224
}
@@ -231,22 +261,24 @@
231261

232262
.no-scrollbar {
233263
-ms-overflow-style: none;
234-
/* scrollbar-width: none; */ /* Commented out for broader compatibility */
264+
/* scrollbar-width: none; */
265+
/* Commented out for broader compatibility */
235266
/* Fallback for browsers that don't support scrollbar-width */
236267
}
237-
268+
238269
/* Modern browsers that support scrollbar-width */
239270
@supports (scrollbar-width: none) {
240271
.no-scrollbar {
241272
scrollbar-width: none;
242273
}
243274
}
244-
275+
245276
/* Fallback for older browsers */
246277
@supports not (scrollbar-width: none) {
247278
.no-scrollbar::-webkit-scrollbar {
248279
display: none;
249280
}
281+
250282
.no-scrollbar {
251283
overflow: -moz-scrollbars-none;
252284
}
@@ -326,41 +358,51 @@
326358
}
327359

328360
/* Width utilities for progress bars */
329-
.w-0 { width: 0%; }
330-
.w-10 { width: 10%; }
331-
.w-20 { width: 20%; }
332-
.w-30 { width: 30%; }
333-
.w-40 { width: 40%; }
334-
.w-50 { width: 50%; }
335-
.w-60 { width: 60%; }
336-
.w-70 { width: 70%; }
337-
.w-80 { width: 80%; }
338-
.w-90 { width: 90%; }
339-
.w-100 { width: 100%; }
361+
.w-0 {
362+
width: 0%;
363+
}
364+
365+
.w-10 {
366+
width: 10%;
367+
}
368+
369+
.w-20 {
370+
width: 20%;
371+
}
372+
373+
.w-30 {
374+
width: 30%;
375+
}
376+
377+
.w-40 {
378+
width: 40%;
379+
}
380+
381+
.w-50 {
382+
width: 50%;
383+
}
384+
385+
.w-60 {
386+
width: 60%;
387+
}
388+
389+
.w-70 {
390+
width: 70%;
391+
}
392+
393+
.w-80 {
394+
width: 80%;
395+
}
396+
397+
.w-90 {
398+
width: 90%;
399+
}
400+
401+
.w-100 {
402+
width: 100%;
403+
}
340404
</style>
341-
<script type="importmap">
342-
{
343-
"imports": {
344-
"react/": "https://aistudiocdn.com/react@^19.2.1/",
345-
"react": "https://aistudiocdn.com/react@^19.2.1",
346-
"react-dom/": "https://aistudiocdn.com/react-dom@^19.2.1/",
347-
"react-dom": "https://aistudiocdn.com/react-dom@^19.2.1",
348-
"react-dom/client": "https://aistudiocdn.com/react-dom@^19.2.1/client",
349-
"scheduler": "https://aistudiocdn.com/scheduler@^0.23.2",
350-
"scheduler/": "https://aistudiocdn.com/scheduler@^0.23.2/",
351-
"@google/genai": "https://aistudiocdn.com/@google/genai@^1.32.0",
352-
"lucide-react": "https://aistudiocdn.com/lucide-react@^0.559.0",
353-
"three": "https://aistudiocdn.com/three@^0.170.0",
354-
"@react-three/fiber": "https://aistudiocdn.com/@react-three/fiber@^9.0.0-rc.3",
355-
"@react-three/drei": "https://aistudiocdn.com/@react-three/drei@^9.120.0",
356-
"tone": "https://aistudiocdn.com/tone@^14.7.77",
357-
"zustand": "https://esm.sh/zustand@^5.0.3?external=react",
358-
"vite": "https://esm.sh/vite@^7.2.7",
359-
"@vitejs/plugin-react": "https://esm.sh/@vitejs/plugin-react@^5.1.2",
360-
"onnxruntime-web": "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.17.1/dist/ort.es6.min.js"
361-
}
362-
}
363-
</script>
405+
364406
</head>
365407

366408
<body class="bg-gray-900 text-gray-100 h-screen w-screen overflow-hidden">

services/airlockClient.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { GoogleGenAI } from "@google/genai";
2+
3+
const DEFAULT_API_VERSION = "v1beta";
4+
const AIRLOCK_CLIENT_KEY = "AIRLOCK_CLIENT";
5+
6+
const normalizeBaseUrl = (raw: string): string => raw.replace(/\/+$/, "");
7+
8+
export const getAirlockBaseUrl = (): string => {
9+
const envUrl = import.meta.env.VITE_AIRLOCK_URL as string | undefined;
10+
if (envUrl && envUrl.trim().length > 0) {
11+
return normalizeBaseUrl(envUrl.trim());
12+
}
13+
return normalizeBaseUrl(window.location.origin);
14+
};
15+
16+
export const createAirlockClient = (): GoogleGenAI => {
17+
return new GoogleGenAI({
18+
apiKey: AIRLOCK_CLIENT_KEY,
19+
apiVersion: DEFAULT_API_VERSION,
20+
httpOptions: {
21+
baseUrl: getAirlockBaseUrl(),
22+
apiVersion: DEFAULT_API_VERSION,
23+
headers: {
24+
"x-airlock-client": "thay-ai-web",
25+
},
26+
},
27+
});
28+
};

services/audioContextManager.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,13 @@ class AudioContextManager {
4747
private async initializeContext(): Promise<AudioContext> {
4848
try {
4949
// Create new audio context with optimal settings
50-
const context = new (window.AudioContext || (window as any).webkitAudioContext)();
50+
// Attempt to force 24kHz for speech optimization (supported in modern browsers)
51+
const contextOptions: AudioContextOptions = {
52+
sampleRate: 24000,
53+
latencyHint: 'interactive'
54+
};
55+
56+
const context = new (window.AudioContext || (window as any).webkitAudioContext)(contextOptions);
5157

5258
// Resume context if suspended (common in mobile browsers)
5359
if (context.state === 'suspended') {
@@ -56,7 +62,8 @@ class AudioContextManager {
5662

5763
// Set optimal audio parameters for voice processing
5864
if (context.sampleRate !== 24000) {
59-
console.warn(`[AudioContext] Sample rate is ${context.sampleRate}, expected 24000`);
65+
console.warn(`[AudioContext] System enforced sample rate: ${context.sampleRate}Hz. Resampling may be required.`);
66+
// Note: Downstream processors (AudioWorklet) must handle resampling if necessary.
6067
}
6168

6269
// Add error handling

0 commit comments

Comments
 (0)