Поделиться через


Создание и размещение расширения приложения

В этой статье показано, как создать расширение приложения Windows 10 и разместить его в приложении. Расширения приложений поддерживаются в приложениях UWP и упакованных классических приложениях.

Чтобы продемонстрировать, как создать расширение приложения, в этой статье используется XML-код манифеста пакета и фрагменты кода из примера кода расширения math. Этот пример является приложением UWP, но функции, показанные в примере, также применимы к упакованным классическим приложениям. Выполните следующие инструкции, чтобы приступить к работе с примером:

  • Скачайте и распакуйте пример кода расширения Math.
  • В Visual Studio 2019 откройте MathExtensionSample.sln. Задайте тип сборки x86 (Build>Configuration Manager, а затем измените платформу на x86 для обоих проектов).
  • Развертывание решения: сборка>решения для развертывания.

Общие сведения о расширениях приложений

В Windows 10 расширения приложений предоставляют функциональные возможности, аналогичные функциям подключаемых модулей, надстроек и надстроек на других платформах. Расширения приложений появились в юбилейном обновлении Windows 10 (версия 1607, сборка 10.0.14393).

Расширения приложений — это приложения UWP или упакованные классические приложения с объявлением расширения, которое позволяет им совместно использовать события содержимого и развертывания с хост-приложением. Приложение расширения может предоставлять несколько расширений.

Так как расширения приложений — это просто приложения UWP или упакованные классические приложения, они также могут быть полностью функциональными, расширениями узлов и предоставлять расширения другим приложениям без создания отдельных пакетов приложений.

При создании узла расширения приложения вы создадите возможность разработки экосистемы вокруг приложения, в которой другие разработчики могут улучшить свое приложение таким образом, чтобы вы не ожидали или не имели ресурсов. Рассмотрим расширения Microsoft Office, расширения Visual Studio, расширения браузера и т. д. Они создают более широкие возможности для тех приложений, которые выходят за рамки функциональных возможностей, с которыми они поставляется. Расширения могут добавлять в приложение значение и долголетие.

На высоком уровне для настройки связи расширения приложения необходимо выполнить следующие действия.

  1. Объявите приложение как узел расширения.
  2. Объявите приложение как расширение.
  3. Определите, следует ли реализовать расширение как службу приложений, фоновую задачу или другой способ.
  4. Определите способ взаимодействия узлов и его расширений.
  5. Используйте API Windows.ApplicationModel.AppExtensions в хост-приложении для доступа к расширениям.

Давайте посмотрим, как это сделать, проверив пример кода расширения математики, который реализует гипотетический калькулятор, к которому можно добавить новые функции с помощью расширений. В Microsoft Visual Studio 2019 загрузите MathExtensionSample.sln из примера кода.

Пример кода расширения математических вычислений

Объявление приложения в виде узла расширения

Приложение определяет себя как узел расширения приложения, объявляя <AppExtensionHost> элемент в файле Package.appxmanifest. См. файл Package.appxmanifest в проекте MathExtensionHost , чтобы узнать, как это сделать.

Package.appxmanifest в проекте MathExtensionHost

<Package
  ...
  xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
  IgnorableNamespaces="uap uap3 mp">
  ...
    <Applications>
      <Application Id="App" ... >
        ...
        <Extensions>
            <uap3:Extension Category="windows.appExtensionHost">
                <uap3:AppExtensionHost>
                  <uap3:Name>com.microsoft.mathext</uap3:Name>
                </uap3:AppExtensionHost>
          </uap3:Extension>
        </Extensions>
      </Application>
    </Applications>
    ...
</Package>

Обратите внимание на xmlns:uap3="http://..." наличие и наличие uap3 в IgnorableNamespaces. Это необходимо, так как мы используем пространство имен uap3.

<uap3:Extension Category="windows.appExtensionHost"> определяет это приложение как узел расширения.

Элемент Name в <uap3:AppExtensionHost> имени контракта расширения. Если расширение указывает то же имя контракта расширения, узел сможет найти его. По соглашению мы рекомендуем создать имя контракта расширения с помощью приложения или издателя, чтобы избежать потенциальных конфликтов с другими именами контрактов расширения.

Можно определить несколько узлов и нескольких расширений в одном приложении. В этом примере мы объявляем один узел. Расширение определяется в другом приложении.

Объявление приложения расширением

Приложение определяет себя как расширение приложения, объявляя <uap3:AppExtension> элемент в файле Package.appxmanifest . Откройте файл Package.appxmanifest в проекте MathExtension, чтобы узнать, как это сделать.

Package.appxmanifest в проекте MathExtension:

<Package
  ...
  xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
  IgnorableNamespaces="uap uap3 mp">
  ...
    <Applications>
      <Application Id="App" ... >
        ...
        <Extensions>
          ...
          <uap3:Extension Category="windows.appExtension">
            <uap3:AppExtension Name="com.microsoft.mathext"
                               Id="power"
                               DisplayName="x^y"
                               Description="Exponent"
                               PublicFolder="Public">
              <uap3:Properties>
                <Service>com.microsoft.powservice</Service>
              </uap3:Properties>
              </uap3:AppExtension>
          </uap3:Extension>
        </Extensions>
      </Application>
    </Applications>
    ...
</Package>

Опять же, обратите внимание на линию xmlns:uap3="http://..." и наличие uap3 в IgnorableNamespaces. Это необходимо, так как мы используем uap3 пространство имен.

<uap3:Extension Category="windows.appExtension"> определяет это приложение как расширение.

Значение <uap3:AppExtension> атрибутов выглядит следующим образом:

Атрибут Description Обязательное поле
Имя Это имя контракта расширения. При совпадении имени , объявленного в узле, этот узел сможет найти это расширение. ✔️
ИД Уникально идентифицирует это расширение. Так как может быть несколько расширений, использующих одно и то же имя контракта расширения (представьте, что приложение с краской, поддерживающее несколько расширений), можно использовать идентификатор, чтобы определить их друг от друга. Узлы расширений приложений могут использовать идентификатор для вывода о типе расширения. Например, можно использовать одно расширение, предназначенное для настольных компьютеров и другого для мобильных устройств, с идентификатором, который является разными. Для этого можно также использовать элемент Properties, приведенный ниже. ✔️
Отображаемое имя Можно использовать из ведущего приложения для идентификации расширения пользователю. Он можно запрашивать из и может использовать новую систему управления ресурсами (ms-resource:TokenName) для локализации. Локализованное содержимое загружается из пакета расширения приложения, а не ведущего приложения.
Description Можно использовать из ведущего приложения для описания расширения пользователю. Он можно запрашивать из и может использовать новую систему управления ресурсами (ms-resource:TokenName) для локализации. Локализованное содержимое загружается из пакета расширения приложения, а не ведущего приложения.
PublicFolder Имя папки относительно корневого каталога пакета, где можно совместно использовать содержимое с узлом расширения. По соглашению имя — Public, но вы можете использовать любое имя, соответствующее папке в расширении. ✔️

<uap3:Properties> — необязательный элемент, содержащий пользовательские метаданные, которые узлы могут читать во время выполнения. В примере кода расширение реализуется как служба приложений, чтобы узел должен получить имя этой службы приложений, чтобы он смог вызвать его. Имя службы приложений определяется в <элементе Service> , который мы определили (мы могли бы назвать его любым нужным). Узел в примере кода ищет это свойство во время выполнения, чтобы узнать имя службы приложений.

Определите, как будет реализовано расширение.

В сеансе сборки 2016 о расширениях приложений показано, как использовать общедоступную папку, общую между узлом и расширениями. В этом примере расширение реализуется файлом JavaScript, хранящимся в общедоступной папке, вызываемой узлом. Этот подход имеет преимущество упрощения, не требует компиляции и может поддерживать выполнение целевой страницы по умолчанию, которая содержит инструкции по расширению и ссылку на страницу Microsoft Store ведущего приложения. Дополнительные сведения см. в примере кода расширения приложения build 2016. В частности, см . проект InvertImageExtension и InvokeLoad() ExtensionManager.cs в проекте ExtensibilitySample .

В этом примере мы будем использовать службу приложений для реализации расширения. Службы приложений имеют следующие преимущества:

  • Если расширение завершается сбоем, оно не приведет к сбою ведущего приложения, так как ведущее приложение выполняется в собственном процессе.
  • Вы можете использовать выбранный язык для реализации службы. Он не должен соответствовать языку, используемому для реализации ведущего приложения.
  • Служба приложений имеет доступ к собственному контейнеру приложения, который может иметь различные возможности, отличные от возможностей узла.
  • Между данными в службе и хост-приложением существует изоляция.

Код службы приложений узла

Ниже приведен код узла, вызывающий службу приложений расширения:

ExtensionManager.cs в проекте MathExtensionHost

public async Task<double> Invoke(ValueSet message)
{
    if (Loaded)
    {
        try
        {
            // make the app service call
            using (var connection = new AppServiceConnection())
            {
                // service name is defined in appxmanifest properties
                connection.AppServiceName = _serviceName;
                // package Family Name is provided by the extension
                connection.PackageFamilyName = AppExtension.Package.Id.FamilyName;

                // open the app service connection
                AppServiceConnectionStatus status = await connection.OpenAsync();
                if (status != AppServiceConnectionStatus.Success)
                {
                    Debug.WriteLine("Failed App Service Connection");
                }
                else
                {
                    // Call the app service
                    AppServiceResponse response = await connection.SendMessageAsync(message);
                    if (response.Status == AppServiceResponseStatus.Success)
                    {
                        ValueSet answer = response.Message as ValueSet;
                        if (answer.ContainsKey("Result")) // When our app service returns "Result", it means it succeeded
                        {
                            return (double)answer["Result"];
                        }
                    }
                }
            }
        }
        catch (Exception)
        {
             Debug.WriteLine("Calling the App Service failed");
        }
    }
    return double.NaN; // indicates an error from the app service
}

Это типичный код для вызова службы приложений. Дополнительные сведения о реализации и вызове службы приложений см. в статье "Создание и использование службы приложений".

Важно отметить, как определяется имя службы приложений. Так как узел не имеет сведений о реализации расширения, расширение должно указать имя службы приложений. В примере кода расширение объявляет имя службы приложений в файле в элементе <uap3:Properties> :

Package.appxmanifest в проекте MathExtension

    ...
    <uap3:Extension Category="windows.appExtension">
      <uap3:AppExtension ...>
        <uap3:Properties>
          <Service>com.microsoft.powservice</Service>
        </uap3:Properties>
        </uap3:AppExtension>
    </uap3:Extension>

Вы можете определить собственный XML-код в элементе <uap3:Properties> . В этом случае мы определим имя службы приложений, чтобы узел смог его использовать при вызове расширения.

Когда узел загружает расширение, код, как это, извлекает имя службы из свойств, определенных в package.appxmanifest расширения:

Update() в ExtensionManager.cs в проекте MathExtensionHost

...
var properties = await ext.GetExtensionPropertiesAsync() as PropertySet;

...
#region Update Properties
// update app service information
_serviceName = null;
if (_properties != null)
{
   if (_properties.ContainsKey("Service"))
   {
       PropertySet serviceProperty = _properties["Service"] as PropertySet;
       this._serviceName = serviceProperty["#text"].ToString();
   }
}
#endregion

С именем службы приложений, хранящейся в _serviceNameней, узел может использовать его для вызова службы приложений.

Для вызова службы приложений также требуется имя семейства пакета, содержащего службу приложений. К счастью, API расширения приложения предоставляет эти сведения, полученные в строке: connection.PackageFamilyName = AppExtension.Package.Id.FamilyName;

Определение способа взаимодействия узла и расширения

Службы приложений используют ValueSet для обмена информацией. Как автор узла, вам нужно придумать протокол для взаимодействия с расширениями, которые являются гибкими. В примере кода это означает учет расширений, которые могут занять 1, 2 или более аргументов в будущем.

В этом примере протокол для аргументов — это ValueSet , содержащий пары "значение ключа" с именем "Arg" + номер аргумента, например Arg1 и Arg2. Узел передает все аргументы в ValueSet, а расширение использует те, которые он нуждается. Если расширение может вычислить результат, узел ожидает , что ValueSet , возвращенный из расширения, имеет ключ с именем Result , который содержит значение вычисления. Если этот ключ отсутствует, узел предполагает, что расширение не может завершить вычисление.

Код службы приложений расширения

В примере кода служба приложений расширения не реализуется как фоновая задача. Вместо этого она использует модель единой службы приложений proc, в которой служба приложений выполняется в том же процессе, что и приложение расширения, на котором он размещен. Этот процесс по-прежнему отличается от ведущего приложения, предоставляя преимущества разделения процессов, при этом некоторые преимущества производительности позволяют избежать межпроцессного взаимодействия между процессом расширения и фоновым процессом, реализующим службу приложений. См. раздел "Преобразование службы приложений для запуска в том же процессе, что и ведущее приложение ", чтобы увидеть разницу между службой приложений, которая выполняется в качестве фоновой задачи и в том же процессе.

Система делает это OnBackgroundActivate() при активации службы приложений. Этот код настраивает обработчики событий для обработки фактического вызова службы приложений при возникновении (OnAppServiceRequestReceived()), а также для обработки событий хранения, таких как получение отложенного объекта, обрабатывающего событие отмены или закрытого события.

App.xaml.cs в проекте MathExtension.

protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
    base.OnBackgroundActivated(args);

    if ( _appServiceInitialized == false ) // Only need to setup the handlers once
    {
        _appServiceInitialized = true;

        IBackgroundTaskInstance taskInstance = args.TaskInstance;
        taskInstance.Canceled += OnAppServicesCanceled;

        AppServiceTriggerDetails appService = taskInstance.TriggerDetails as AppServiceTriggerDetails;
        _appServiceDeferral = taskInstance.GetDeferral();
        _appServiceConnection = appService.AppServiceConnection;
        _appServiceConnection.RequestReceived += OnAppServiceRequestReceived;
        _appServiceConnection.ServiceClosed += AppServiceConnection_ServiceClosed;
    }
}

Код, выполняющий работу расширения, находится в OnAppServiceRequestReceived(). Эта функция вызывается при вызове службы приложений для выполнения вычисления. Он извлекает необходимые значения из ValueSet. Если он может выполнить вычисление, он помещает результат в ключ с именем Result в valueSet, возвращаемом узлу. Помните, что в соответствии с протоколом, определенным для того, как этот узел и его расширения будут взаимодействовать, наличие ключа результата будет указывать на успешность; в противном случае сбой.

App.xaml.cs в проекте MathExtension.

private async void OnAppServiceRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
    // Get a deferral because we use an awaitable API below (SendResponseAsync()) to respond to the message
    // and we don't want this call to get cancelled while we are waiting.
    AppServiceDeferral messageDeferral = args.GetDeferral();
    ValueSet message = args.Request.Message;
    ValueSet returnMessage = new ValueSet();

    double? arg1 = Convert.ToDouble(message["arg1"]);
    double? arg2 = Convert.ToDouble(message["arg2"]);
    if (arg1.HasValue && arg2.HasValue)
    {
        returnMessage.Add("Result", Math.Pow(arg1.Value, arg2.Value)); // For this sample, the presence of a "Result" key will mean the call succeeded
    }

    await args.Request.SendResponseAsync(returnMessage);
    messageDeferral.Complete();
}

Управление расширениями

Теперь, когда мы узнали, как реализовать связь между узлом и его расширениями, давайте посмотрим, как узел находит расширения, установленные в системе, и реагирует на добавление и удаление пакетов, содержащих расширения.

Microsoft Store предоставляет расширения в виде пакетов. AppExtensionCatalog находит установленные пакеты, содержащие расширения, соответствующие имени контракта расширения узла, и предоставляет события, которые возникают при установке или удалении пакета расширения приложения, относящегося к узлу.

В примере ExtensionManager кода класс (определенный в ExtensionManager.cs в проекте MathExtensionHost ) упаковывает логику для загрузки расширений и реагирования на установку и удаление пакета расширения.

Конструктор ExtensionManager используется AppExtensionCatalog для поиска расширений приложения в системе с тем же именем контракта расширения, что и узел:

ExtensionManager.cs в проекте MathExtensionHost.

public ExtensionManager(string extensionContractName)
{
   // catalog & contract
   ExtensionContractName = extensionContractName;
   _catalog = AppExtensionCatalog.Open(ExtensionContractName);
   ...
}

При установке ExtensionManager пакета расширения собирает сведения о расширениях в пакете с тем же именем контракта расширения, что и узел. Установка может представлять собой обновление, в котором обновляются сведения о затронутом расширении. При удалении пакета расширения удаляются сведения о затронутых расширениях, чтобы пользователь знал, ExtensionManager какие расширения больше не доступны.

Класс Extension (определенный в ExtensionManager.cs в проекте MathExtensionHost ) был создан для примера кода для доступа к идентификатору расширения, описанию, логотипу и конкретным приложениям, таким как включение расширения пользователем.

Чтобы сказать, что расширение загружено (см Load() . в ExtensionManager.cs) означает, что состояние пакета хорошо и что мы получили его идентификатор, логотип, описание и общедоступную папку (которую мы не используем в этом примере, чтобы показать, как это получить). Сам пакет расширения не загружается.

Концепция выгрузки используется для отслеживания того, какие расширения больше не должны отображаться пользователю.

Предоставляет ExtensionManager экземпляры коллекции Extension , чтобы расширения, их имена, описания и логотипы могли быть привязаны к пользовательскому интерфейсу. Страница ExtensionsTab привязывается к этой коллекции и предоставляет пользовательский интерфейс для включения и отключения расширений, а также их удаления.

Пример пользовательского интерфейса вкладки расширений

При удалении расширения система предложит пользователю убедиться, что он хочет удалить пакет, содержащий расширение (и, возможно, содержит другие расширения). Если пользователь согласен, пакет удаляется и ExtensionManager удаляет расширения в удаленном пакете из списка расширений, доступных для ведущего приложения.

Удаление пользовательского интерфейса

Отладка расширений и узлов приложений

Часто узел расширения и расширение не являются частью одного решения. В этом случае для отладки узла и расширения:

  1. Загрузите главный проект в одном экземпляре Visual Studio.
  2. Загрузите расширение в другом экземпляре Visual Studio.
  3. Запустите ведущее приложение в отладчике.
  4. Запустите приложение расширения в отладчике. (Если вы хотите развернуть расширение, а не отлаживать его, чтобы протестировать событие установки пакета узла, сделайте это.Вместо этого создайте > решение развертывания.

Теперь вы сможете получить точки останова в узле и расширении. При запуске отладки самого приложения расширения появится пустое окно для приложения. Если вы не хотите видеть пустое окно, вы можете изменить параметры отладки для проекта расширения, чтобы не запустить приложение, а вместо этого выполнить отладку при запуске (щелкните правой кнопкой мыши проект расширения, выберите>> "Отладка свойств" не запускайте, но отладите мой код при запуске) Вам по-прежнему потребуется начать отладку (F5) проекта расширения, но он будет ждать, пока узел активирует расширение, а затем ваши точки останова в расширении будут поражены.

Отладка примера кода

В примере кода узел и расширение находятся в одном решении. Выполните следующую отладку:

  1. Убедитесь, что MathExtensionHost является проектом запуска (щелкните правой кнопкой мыши проект MathExtensionHost, нажмите кнопку "Задать как проект StartUp").
  2. Поместите точку Invoke останова в ExtensionManager.cs в проекте MathExtensionHost .
  3. F5 для запуска проекта MathExtensionHost .
  4. Поместите точку OnAppServiceRequestReceived останова в App.xaml.cs в проекте MathExtension .
  5. Начните отладку проекта MathExtension (щелкните правой кнопкой мыши проект MathExtension, отладочный > запуск нового экземпляра), который развернет его и активирует событие установки пакета на узле.
  6. В приложении MathExtensionHost перейдите на страницу вычислений и щелкните x^y, чтобы активировать расширение. Точка Invoke() останова сначала попадает, и вы увидите, что выполняется вызов службы приложений расширений. OnAppServiceRequestReceived() Затем метод в расширении будет достигнут, и вы увидите, что служба приложений вычисляет результат и возвращает его.

Устранение неполадок расширений, реализованных как служба приложений

Если узел расширения не удается подключиться к службе приложений для расширения, убедитесь, что <uap:AppService Name="..."> атрибут соответствует тому, что вы помещаете в <Service> элемент. Если они не соответствуют, имя службы, которое предоставляет расширение, не будет совпадать с именем службы приложений, которое вы реализовали, и узел не сможет активировать расширение.

Package.appxmanifest в проекте MathExtension:

<Extensions>
   <uap:Extension Category="windows.appService">
     <uap:AppService Name="com.microsoft.sqrtservice" />      <!-- This must match the contents of <Service>...</Service> -->
   </uap:Extension>
   <uap3:Extension Category="windows.appExtension">
     <uap3:AppExtension Name="com.microsoft.mathext" Id="sqrt" DisplayName="Sqrt(x)" Description="Square root" PublicFolder="Public">
       <uap3:Properties>
         <Service>com.microsoft.powservice</Service>   <!-- this must match <uap:AppService Name=...> -->
       </uap3:Properties>
     </uap3:AppExtension>
   </uap3:Extension>
</Extensions>   

Контрольный список основных сценариев для тестирования

При создании узла расширения и готовности к тестированию того, насколько хорошо он поддерживает расширения, ниже приведены некоторые основные сценарии.

  • Запустите узел и разверните приложение расширения
    • Получает ли узел новые расширения, которые приходят во время выполнения?
  • Разверните приложение расширения, а затем разверните и запустите узел.
    • Получает ли узел существующие расширения?
  • Запустите узел и удалите приложение расширения.
    • Правильно ли обнаруживает удаление узла?
  • Запустите узел и обновите приложение расширения до более новой версии.
    • Правильно ли узел выбирает изменения и выгружает старые версии расширения?

Дополнительные сценарии для тестирования:

  • Запустите узел, переместите приложение расширения на съемный носитель, удалите носитель
    • Обнаруживает ли узел изменение состояния пакета и отключает ли расширение?
  • Запустите узел, а затем повредите приложение расширения (сделайте его недопустимым, подписанным по-разному и т. д.)
    • Обнаруживает ли узел измененное расширение и правильно ли обрабатывает его?
  • Запустите узел, а затем разверните приложение расширения с недопустимым содержимым или свойствами
    • Обнаруживает ли узел недопустимое содержимое и обрабатывает его правильно?

Рекомендации по проектированию

  • Укажите пользовательский интерфейс, показывающий пользователя, какие расширения доступны, и позволяет им включить или отключить их. Вы также можете добавить глифы для расширений, которые становятся недоступными, так как пакет переходит в автономный режим и т. д.
  • Укажите пользователю, где они могут получить расширения. Возможно, страница расширения может предоставить поисковый запрос Microsoft Store, который открывает список расширений, которые можно использовать с приложением.
  • Рассмотрим, как уведомить пользователя о добавлении и удалении расширений. При установке нового расширения можно создать уведомление и пригласить пользователя включить его. Расширения должны быть отключены по умолчанию, чтобы пользователи управлялись.

Как расширения приложений отличаются от необязательных пакетов

Ключевым различием между необязательными пакетами и расширениями приложений являются открытые экосистемы и закрытые экосистемы, а также зависимый пакет и независимый пакет.

Расширения приложений участвуют в открытой экосистеме. Если ваше приложение может размещать расширения приложений, любой пользователь может написать расширение для вашего узла до тех пор, пока они соответствуют вашему методу передачи или получения информации из расширения. Это отличается от необязательных пакетов, которые участвуют в закрытой экосистеме, где издатель решает, кто может сделать необязательный пакет, который можно использовать с приложением.

Расширения приложений являются независимыми пакетами и могут быть автономными приложениями. Они не могут иметь зависимость развертывания от другого приложения. Дополнительные пакеты зависят от основного пакета, то есть не могут работать без него.

Пакет расширения для игры будет хорошим кандидатом на необязательный пакет, потому что он тесно привязан к игре, он не может работать независимо от игры, и вы можете не хотите, чтобы пакеты расширения были созданы только любым разработчиком в экосистеме.

Если в той же игре были настраиваемые надстройки пользовательского интерфейса или тематические элементы, то расширение приложения может быть хорошим выбором, так как приложение, предоставляющее расширение, может работать самостоятельно, и любая 3-ая сторона может сделать их.

Замечания

В этом разделе приведены общие сведения о расширениях приложений. Важно отметить, что создание узла и его маркировка как таковое в файле Package.appxmanifest, создание расширения и маркировка его как таковое в файле Package.appxmanifest, определение способа реализации расширения (например, служба приложений, фоновая задача или другие средства), определение того, как узел будет взаимодействовать с расширениями, и использование API AppExtensions для доступа к расширениям и управления ими.