UI лаунчера и SDK
Разделение ответственности
| Компонент | Где | Назначение |
|---|---|---|
resolveRegistrySlot | SDK | есть ли запись в local store |
ExtensionRegistrySlotButton | SDK | точечная установка для (registryId, key) |
| Диалог маркетплейса | Лаунчер | глобальный каталог / поиск / установка |
useExtensionMarketplaceDialog | Лаунчер | открыть диалог из любого места |
createExtensionMarketplaceClient | SDK | HTTP к сервису |
Маркетплейс — диалог, а не отдельный экран: тот же UX-паттерн, что у «Управление расширениями».
Глобальный диалог маркетплейса
API открытия (лаунчер)
Контракт хоста (имена могут отличаться в вашей сборке):
export interface ExtensionMarketplaceDialogOpenOptions {
/** Начальный режим */
mode?: 'catalog' | 'slot';
/** Предзаполнение для mode=slot */
registryId?: string;
key?: string;
}
export function useExtensionMarketplaceDialog(): {
open: (options?: ExtensionMarketplaceDialogOpenOptions) => void;
close: () => void;
};Диалог регистрируется в системе модальных окон лаунчера при старте приложения.
Режимы диалога
catalog
- поле поиска
q; - фильтр по
registryId(опционально); - список
MarketplaceExtensionSummary; - кнопка «Установить» → скачать
downloadUrl→ установка архива → перезагрузка расширений.
slot
- заголовок: «Слот
{registryId}/{key}»; client.lookupSlot({ registryId, key });none/one/many— как в 04-sdk-shared-types.md;- при
many— список внутри того же диалога (подрежим выбора, без второго модального уровня по возможности).
Конфигурация
VITE_EXTENSION_MARKETPLACE_URL=https://extensions.example/apiКлиент маркетплейса создаётся через SDK:
import { createExtensionMarketplaceClient } from '@rynt/sdk/extension-marketplace';
export const extensionMarketplaceClient = createExtensionMarketplaceClient({
baseUrl: import.meta.env.VITE_EXTENSION_MARKETPLACE_URL,
getAuthToken: () => sessionStore.token, // для publish, если понадобится в UI
});Кнопка слота (SDK)
→ Пошаговый сценарий для авторов расширений: Расширяемые реестры (declare и extend).
ExtensionRegistrySlotButton — props:
| Prop | Тип |
|---|---|
registryId | string |
registryKey | string (ключ слота; не путать с Vue key) |
label? | string |
onResolved? | callback после установки и reload |
Поведение:
resolveRegistrySlot→ еслиpresent, слот не рендерится (или slot «уже установлено»).- Если
missing— кнопка «Добавить расширение…». - Click →
useExtensionMarketplaceDialog().open({ mode: 'slot', registryId, key })(инжект через provide/inject или callback propopenMarketplaceиз host).
SDK не импортирует диалог лаунчера напрямую (циклическая зависимость). Варианты:
- A (рекомендуется): prop
onInstallSlot?: (ctx: { registryId, key }) => void— хост передаёт() => marketplaceDialog.open({ mode: 'slot', … }). - B:
provide('ryntExtensionMarketplace', { openSlot })в корневом layout лаунчера.
Пример встраивания (чаты)
В компоненте сообщения чата: если тип сообщения не найден в store и не в builtin — кнопка с registryId = CHATS_MESSAGE_TYPES_REGISTRY_ID, key = payload.type.
В корневом layout лаунчера монтируется диалог маркетплейса и при необходимости provide для SDK-кнопок.
Управление расширениями
Диалог «Управление расширениями» — полноценный UI для локальных пакетов. Подробнее: Управление установленными.
| Возможность | Описание |
|---|---|
| Добавить файл | Установка .ryntextension / zip |
| Вкл/выкл | Папка *.disabled без удаления |
| Удалить | Диалог подтверждения |
| Иконка / автор | Из manifest.icon, manifest.author |
| Обновления | Блок «Обновления · N» + «Обновить все» (semver vs каталог) |
| Dev-hub | Кнопка link → 127.0.0.1:39217, бейдж Dev |
| Маркетплейс | Баннер «Больше расширений…» → catalog |
Диалог маркетплейса (master-detail)
catalog: поиск, список с iconUrl / authorName, клик → панель детали.
Деталь:
- иконка, имя, версия, автор;
listingDescriptionHtml(rich-описание);extensionDependencies;- слоты
extendsRegistries; - кнопка «Установить».
См. Метаданные listing.
Автообновление установленных расширений
По manifest.version (semver пакета), не по extensionApi.
- При старте (и по таймеру): для каждого установленного
idзапрос latest в каталоге. - Если
semver.gt(remote, local)и включено в настройках → скачатьdownloadUrl→ установить → перезагрузка реестра. - Сбой сети не блокирует лаунчер.
- В «Управление расширениями» — индикатор «доступно обновление» + ручная кнопка (тот же pipeline).
Установка из URL
Pipeline лаунчера (NW.js):
async function installExtensionFromMarketplaceUrl(
downloadUrl: string,
extensionsRoot: string,
): Promise<{ directoryName: string }>;Шаги: fetch → temp file (NW) → распаковка в каталог расширений → перезагрузка реестра.