Skip to content

Commit bf33375

Browse files
committed
sw.js + v86
1 parent a68465a commit bf33375

24 files changed

Lines changed: 1893 additions & 54 deletions

eslint.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,8 @@ export default defineConfig([
1919
ecmaVersion: 2020,
2020
globals: globals.browser,
2121
},
22+
rules: {
23+
'prefer-const': 'error',
24+
},
2225
},
2326
]);

index.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,27 @@
1313
<meta charset="UTF-8" />
1414
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
1515
<title>RunWebBox</title>
16+
<script>
17+
if ('serviceWorker' in navigator) {
18+
window.addEventListener('load', function () {
19+
const swPath = location.host.includes('localhost')
20+
? '/RunWebBox/sw.js'
21+
: '/sw.js';
22+
navigator.serviceWorker
23+
.register(swPath, {
24+
scope: '/',
25+
})
26+
.then(
27+
function (registration) {
28+
console.log('ServiceWorker registration successful');
29+
},
30+
function (err) {
31+
console.log('ServiceWorker registration failed: ', err);
32+
}
33+
);
34+
});
35+
}
36+
</script>
1637
</head>
1738
<body>
1839
<div id="root"></div>

package-lock.json

Lines changed: 41 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"eslint-plugin-react-hooks": "^5.2.0",
3535
"eslint-plugin-react-refresh": "^0.4.24",
3636
"globals": "^16.5.0",
37+
"monaco-editor": "^0.55.1",
3738
"postcss": "^8.5.6",
3839
"prettier": "^3.6.2",
3940
"tailwindcss": "^4.1.17",

public/sw.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Карта каналов для связи с клиентами
2+
const clientChannels = new Map();
3+
4+
self.addEventListener('install', event => {
5+
console.log('Service Worker installing');
6+
self.skipWaiting();
7+
});
8+
9+
self.addEventListener('activate', event => {
10+
console.log('Service Worker activating');
11+
event.waitUntil(self.clients.claim());
12+
});
13+
14+
self.addEventListener('fetch', event => {
15+
const url = new URL(event.request.url);
16+
const path = url.pathname;
17+
18+
// Игнорируем запросы к другим доменам
19+
if (url.origin !== location.origin) {
20+
return;
21+
}
22+
23+
// Игнорируем запросы к /RunWebBox и сам service worker
24+
if (path.startsWith('/RunWebBox') || path.includes('sw.js')) {
25+
return;
26+
}
27+
28+
// Обрабатываем все остальные запросы через главный поток
29+
event.respondWith(handleFetchViaMainThread(event));
30+
});
31+
32+
self.addEventListener('message', event => {
33+
const { type } = event.data;
34+
const ports = event.ports;
35+
36+
if (type === 'REGISTER_CLIENT' && ports && ports.length > 0) {
37+
const port = ports[0];
38+
const clientId = self.crypto.randomUUID();
39+
40+
console.log('Registering client:', clientId);
41+
42+
// Сохраняем порт для связи
43+
clientChannels.set(clientId, port);
44+
45+
// Настраиваем обработчики сообщений
46+
port.onmessage = msgEvent => {
47+
const { type, requestId, payload } = msgEvent.data;
48+
49+
if (type === 'FETCH_RESPONSE') {
50+
// Находим pending promise и разрешаем его
51+
const pendingRequest = pendingRequests.get(requestId);
52+
if (pendingRequest) {
53+
pendingRequest(payload);
54+
pendingRequests.delete(requestId);
55+
}
56+
}
57+
};
58+
59+
port.onmessageerror = error => {
60+
console.error('Message error from client:', error);
61+
};
62+
63+
// Удаляем канал при отключении
64+
port.addEventListener('close', () => {
65+
clientChannels.delete(clientId);
66+
});
67+
68+
// Отправляем подтверждение регистрации
69+
port.postMessage({
70+
type: 'CLIENT_REGISTERED',
71+
clientId,
72+
});
73+
}
74+
});
75+
76+
// Карта ожидающих запросов
77+
const pendingRequests = new Map();
78+
79+
async function handleFetchViaMainThread(event) {
80+
await self.clients.get(event.clientId);
81+
//if (!client) {
82+
// return new Response('Client not found', { status: 404 });
83+
//}
84+
85+
// Ищем активный канал связи
86+
const channels = Array.from(clientChannels.entries());
87+
if (channels.length === 0) {
88+
return new Response('No client channels available', { status: 503 });
89+
}
90+
91+
// Берем первый доступный канал
92+
const [clientId, port] = channels.at(-1);
93+
const requestId = self.crypto.randomUUID();
94+
95+
return new Promise(resolve => {
96+
// Сохраняем callback для разрешения promise
97+
pendingRequests.set(requestId, response => {
98+
const { response: body, status, headers, error } = response;
99+
100+
if (error) {
101+
resolve(new Response(error, { status: 500 }));
102+
return;
103+
}
104+
105+
resolve(
106+
new Response(body, {
107+
status: status || 200,
108+
headers: {
109+
'Content-Type': headers?.contentType || 'text/plain',
110+
'Cache-Control': 'no-cache, no-store, must-revalidate',
111+
},
112+
})
113+
);
114+
});
115+
116+
// Отправляем запрос в главный поток
117+
port.postMessage({
118+
type: 'FETCH_REQUEST',
119+
requestId,
120+
payload: {
121+
url: event.request.url,
122+
path: new URL(event.request.url).pathname,
123+
method: event.request.method,
124+
},
125+
});
126+
127+
// Таймаут на случай если ответ не придет
128+
setTimeout(() => {
129+
if (pendingRequests.has(requestId)) {
130+
pendingRequests.delete(requestId);
131+
resolve(new Response('Request timeout', { status: 504 }));
132+
}
133+
}, 10000);
134+
});
135+
}

src/components/BrowserPreview.tsx

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,66 @@
1-
import React from 'react';
1+
import React, { useRef, useState, useEffect } from 'react';
2+
import { useServiceWorker } from '../hooks/useServiceWorker';
23

34
const BrowserPreview: React.FC = () => {
5+
const iframeRef = useRef<HTMLIFrameElement>(null);
6+
const { isReady } = useServiceWorker();
7+
const [isLoading, setIsLoading] = useState(true);
8+
9+
const handleRefresh = () => {
10+
if (iframeRef.current) {
11+
setIsLoading(true);
12+
// Принудительное обновление с уникальным параметром чтобы избежать кеширования
13+
iframeRef.current.src = `/?t=${Date.now()}`;
14+
}
15+
};
16+
17+
// Автоматически перезагружаем iframe когда Service Worker готов
18+
useEffect(() => {
19+
if (isReady && iframeRef.current) {
20+
setIsLoading(true);
21+
iframeRef.current.src = `/?t=${Date.now()}`;
22+
}
23+
}, [isReady]);
24+
425
return (
526
<div className="h-full flex flex-col">
627
<div className="bg-zinc-800 px-4 py-2 border-b border-zinc-700 flex justify-between items-center">
728
<h3 className="font-medium">Browser Preview</h3>
829
<div className="flex space-x-2">
9-
<button className="px-2 py-1 bg-zinc-700 rounded text-sm hover:bg-zinc-600">
10-
Refresh
30+
<button
31+
className="px-2 py-1 bg-zinc-700 rounded text-sm hover:bg-zinc-600 disabled:opacity-50"
32+
onClick={handleRefresh}
33+
disabled={!isReady || isLoading}
34+
>
35+
{isLoading ? 'Loading...' : 'Refresh'}
1136
</button>
1237
</div>
1338
</div>
14-
<div className="flex-1 bg-white">
15-
<iframe
16-
src="about:blank"
17-
className="w-full h-full border-0"
18-
title="Browser Preview"
19-
/>
39+
40+
<div className="flex-1 bg-white relative">
41+
{!isReady ? (
42+
<div className="h-full flex items-center justify-center text-zinc-500">
43+
Initializing preview environment...
44+
</div>
45+
) : (
46+
<>
47+
{isLoading && (
48+
<div className="absolute inset-0 flex items-center justify-center bg-white bg-opacity-80 z-10">
49+
<div className="text-zinc-500">Loading preview...</div>
50+
</div>
51+
)}
52+
<iframe
53+
ref={iframeRef}
54+
src="/"
55+
className="w-full h-full border-0"
56+
title="Browser Preview"
57+
onLoad={() => {
58+
setIsLoading(false);
59+
}}
60+
sandbox="allow-scripts allow-same-origin"
61+
/>
62+
</>
63+
)}
2064
</div>
2165
</div>
2266
);

src/components/LeftSidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// components/LeftSidebar.tsx
22
import React from 'react';
33
import { useFileSystem } from '../hooks/useFileSystem';
4-
import type { FileItem } from '../contexts/FileSystemContext';
4+
import type { FileItem } from '../types/fileSystem';
55

66
const LeftSidebar: React.FC = () => {
77
const { fileSystem, openFile } = useFileSystem();

src/contexts/FileSystemContext.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
import { createContext, useContext, type ReactNode } from 'react';
2-
3-
export interface FileItem {
4-
id: string;
5-
name: string;
6-
type: 'file' | 'folder';
7-
content?: string;
8-
children?: FileItem[];
9-
}
2+
import type { FileItem } from '../types/fileSystem';
103

114
export interface FileSystemContextType {
125
fileSystem: FileItem | null;

src/hooks/useFileSystem.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
updateFileContent,
77
setActiveTab,
88
} from '../store/slices/fileSystemSlice';
9-
import { type FileItem } from '../store/slices/fileSystemSlice';
9+
import { type FileItem } from '../types/fileSystem';
1010

1111
export const useFileSystem = () => {
1212
//console.log(V86);

0 commit comments

Comments
 (0)