Skip to content

UI лаунчера и SDK

Разделение ответственности

КомпонентГдеНазначение
resolveRegistrySlotSDKесть ли запись в local store
ExtensionRegistrySlotButtonSDKточечная установка для (registryId, key)
Диалог маркетплейсаЛаунчерглобальный каталог / поиск / установка
useExtensionMarketplaceDialogЛаунчероткрыть диалог из любого места
createExtensionMarketplaceClientSDKHTTP к сервису

Маркетплейс — диалог, а не отдельный экран: тот же UX-паттерн, что у «Управление расширениями».

Глобальный диалог маркетплейса

API открытия (лаунчер)

Контракт хоста (имена могут отличаться в вашей сборке):

ts
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 — список внутри того же диалога (подрежим выбора, без второго модального уровня по возможности).

Конфигурация

ini
VITE_EXTENSION_MARKETPLACE_URL=https://extensions.example/api

Клиент маркетплейса создаётся через SDK:

ts
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Тип
registryIdstring
registryKeystring (ключ слота; не путать с Vue key)
label?string
onResolved?callback после установки и reload

Поведение:

  1. resolveRegistrySlot → если present, слот не рендерится (или slot «уже установлено»).
  2. Если missing — кнопка «Добавить расширение…».
  3. Click → useExtensionMarketplaceDialog().open({ mode: 'slot', registryId, key }) (инжект через provide/inject или callback prop openMarketplace из 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Кнопка link127.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):

ts
async function installExtensionFromMarketplaceUrl(
  downloadUrl: string,
  extensionsRoot: string,
): Promise<{ directoryName: string }>;

Шаги: fetch → temp file (NW) → распаковка в каталог расширений → перезагрузка реестра.

Диаграмма