Версия протокола: 1.4
Изоляция: <iframe sandbox="allow-scripts">
Транспорт: window.postMessage (Двусторонний асинхронный канал с поддержкой Transferable Objects)
Плагины PeekIt выполняются в строгой изолированной песочнице Microsoft Edge WebView2.
Прямой доступ к диску, реестру Windows, сети и системным API для плагина заблокирован. Все взаимодействие с файлами и средой приложения происходит исключительно через протокол сообщений window.postMessage.
Все сообщения между хост-приложением PeekIt и плагином представляют собой сериализуемые JavaScript-объекты со следующей сигнатурой:
interface PeekItMessage<T = unknown> {
type: string; // Идентификатор события (например, "PEEKIT_INIT")
payload?: T; // Полезная нагрузка события
error?: string; // Сообщение об ошибке (если применимо)
}Из-за асинхронной загрузки iframe и хоста момент готовности сторон не детерминирован. Поэтому протокол использует надёжный цикл рукопожатия с периодическим оповещением о готовности:
sequenceDiagram
autonumber
participant P as Plugin (Iframe)
participant H as PeekIt Host (WebView2)
Note over P: Загрузка DOM и скриптов (Zero-TDZ)
loop Каждые 150-250 мс (readyInterval)
P->>H: postMessage({ type: "PEEKIT_READY" })
end
Note over H: Хост готов к передаче параметров
H->>P: postMessage({ type: "PEEKIT_INIT", payload: InitPayload })
Note over P: clearInterval(readyInterval)<br/>Применение темы и языка
P->>H: postMessage({ type: "PEEKIT_REQUEST_DATA" })
Note over H: Чтение файла с диска в ArrayBuffer
H->>P: postMessage({ type: "PEEKIT_DATA_RESPONSE", payload: FilePayload }, [buffer])
Note over P: Парсинг и рендеринг контента
opt Пользователь переключил тему в Windows
H->>P: postMessage({ type: "PEEKIT_THEME_CHANGED", payload: ThemePayload })
Note over P: Переключение CSS-токенов
end
opt Пользователь переключил язык в плагине
P->>H: postMessage({ type: "PEEKIT_LANGUAGE_CHANGED", payload: LangPayload })
Note over H: Синхронизация языка хоста
end
Caution
КРИТИЧЕСКИЙ АНТИПАТТЕРН (Бесконечный цикл):
Никогда не отправляйте PEEKIT_READY в ответ на получение PEEKIT_DATA_RESPONSE!
Хост PeekIt интерпретирует PEEKIT_READY как признак перезагрузки iframe и повторно отправляет PEEKIT_INIT. Это приводит к бесконечной перезагрузке данных, дерганию интерфейса и утечкам памяти.
Для отправки сообщения хост-приложению плагин вызывает:
window.parent.postMessage({ type: 'EVENT_NAME', payload: { ... } }, '*');Сигнал хосту о том, что DOM плагина сформирован и слушатель сообщений зарегистрирован.
- Периодичность: Отправляется интервалом (150–250 мс) до первого ответа
PEEKIT_INIT. - Payload: не требуется.
let readyInterval = setInterval(() => {
window.parent.postMessage({ type: 'PEEKIT_READY' }, '*');
}, 200);Запрос содержимого открытого файла. Отправляется строго после получения PEEKIT_INIT.
- Payload: не требуется.
window.parent.postMessage({ type: 'PEEKIT_REQUEST_DATA' }, '*');Уведомление хоста о ручном переключении языка пользователем через кнопку в интерфейсе плагина (двусторонняя синхронизация).
window.parent.postMessage({
type: 'PEEKIT_LANGUAGE_CHANGED',
payload: { language: 'en', locale: 'en' }
}, '*');Установка дополнительной информации в заголовок окна PeekIt (разрешение, число страниц, имя подфайла).
window.parent.postMessage({
type: 'SET_TITLE',
payload: { title: 'Схема.svg (1920×1080)' }
}, '*');Оповещение хоста о критической ошибке парсинга файла.
window.parent.postMessage({
type: 'PEEKIT_ERROR',
payload: { message: 'Файл поврежден или содержит неподдерживаемый формат данных' }
}, '*');Отправляется хостом в ответ на PEEKIT_READY. Передаёт конфигурацию среды.
interface InitPayload {
theme?: "dark" | "light" | "system";
isDark?: boolean;
accentColor?: string; // Например, "#0078D4"
language?: "ru" | "en";
locale?: "ru" | "en";
version?: string; // Версия PeekIt (например, "1.4.0")
filePath?: string; // Полный путь к открываемому файлу
}Передача содержимого файла в плагин. Для бинарных файлов хост передает ArrayBuffer через механизм Transferable Objects (zero-copy).
interface FileDataPayload {
name: string; // Имя файла ("model.stl")
path: string; // Полный путь на диске
size: number; // Размер в байтах
mimeType: string; // MIME-тип (например, "image/svg+xml")
// Бинарные данные или текстовая строка:
data: ArrayBuffer | Uint8Array | string;
// Для обратной совместимости дублируется в поле content:
content?: ArrayBuffer | string;
error?: string | null; // Ошибка чтения файла (если возникла на уровне хоста)
}Отправляется хостом при смене системной темы Windows на лету.
interface ThemePayload {
theme: "dark" | "light" | "system";
isDark: boolean;
accentColor?: string;
}Отправляется хостом при смене языка в глобальных настройках PeekIt.
interface LanguagePayload {
language: "ru" | "en";
locale: "ru" | "en";
}Ниже приведён эталонный код обработчика, обеспечивающий устойчивость к ошибкам инициализации:
// 1. Состояние
let readyInterval = null;
let currentLocale = 'ru';
let isDarkTheme = true;
// 2. Слушатель сообщений
window.addEventListener('message', (event) => {
const msg = event.data;
if (!msg || typeof msg !== 'object') return;
switch (msg.type) {
case 'PEEKIT_INIT': {
// Обязательно гасим интервал рукопожатия!
if (readyInterval) {
clearInterval(readyInterval);
readyInterval = null;
}
const p = msg.payload || {};
// Определение языка (каскад)
const lang = p.language || p.locale || 'ru';
applyLocale(lang);
// Определение темы
applyTheme(p.isDark !== undefined ? p.isDark : p.theme !== 'light');
// Запрашиваем данные файла
window.parent.postMessage({ type: 'PEEKIT_REQUEST_DATA' }, '*');
break;
}
case 'PEEKIT_DATA_RESPONSE': {
const p = msg.payload || {};
if (p.error) {
showError(p.error);
return;
}
renderFile(p.data || p.content, p);
break;
}
case 'PEEKIT_THEME_CHANGED': {
const p = msg.payload || {};
applyTheme(p.isDark !== undefined ? p.isDark : p.theme !== 'light');
break;
}
case 'PEEKIT_LANGUAGE_CHANGED':
case 'PEEKIT_LOCALE_CHANGED': {
const p = msg.payload || {};
applyLocale(p.language || p.locale);
break;
}
}
});
// 3. Старт рукопожатия
readyInterval = setInterval(() => {
window.parent.postMessage({ type: 'PEEKIT_READY' }, '*');
}, 200);