← назад к разделу

Web-приложение в Capacitor работает внутри WebView — по сути браузера без адресной строки. А браузер живёт в песочнице: он не может просто взять и открыть камеру или прочитать файл на устройстве. Для этого нужно вырваться за границу веба.

Механизм, который даёт этот выход — мост (bridge). Он позволяет TypeScript-коду вызывать нативный Swift или Kotlin, получить результат и продолжить работу как ни в чём не бывало.

Почему браузер не может напрямую

Стандартный браузер на телефоне умеет многое через web-API: getUserMedia для камеры, navigator.geolocation для координат, localStorage для данных. Но возможности ограничены тем, что разрешает браузерная платформа, а не операционная система.

Когда нужно что-то за пределами этого списка — например, тихое снятие фото без интерфейса браузера, запись в произвольный каталог файловой системы, нативный диалог выбора контакта или Bluetooth-устройства — стандартные web-API уже не помогают.

Как работает мост

Мост Capacitor — двусторонний канал между JS и нативом:

  1. TypeScript вызывает метод плагина.
  2. Вызов сериализуется и уходит в нативный слой — Swift на iOS, Kotlin на Android.
  3. Нативный код обращается к системному SDK.
  4. Результат возвращается обратно в JS как обычный промис.

Для вызывающего кода это выглядит как любой другой await:

import { Geolocation } from "@capacitor/geolocation";

const position = await Geolocation.getCurrentPosition();
const { latitude, longitude } = position.coords;

Вся машинерия моста скрыта за плагином — в TypeScript не видно ни Swift, ни Kotlin.

Готовые плагины

Команда Capacitor поддерживает набор официальных плагинов, которые покрывают большинство типовых задач. Они устанавливаются как обычные npm-пакеты:

npm install @capacitor/camera @capacitor/geolocation @capacitor/filesystem
npm install @capacitor/preferences @capacitor/device
npx cap sync

Команда npx cap sync копирует веб-ресурсы и подтягивает нативные зависимости плагинов в проекты iOS и Android — её нужно запускать после каждой установки нового плагина.

Основные плагины:

  • @capacitor/camera — съёмка и выбор фото из галереи;
  • @capacitor/geolocation — координаты и слежение за позицией;
  • @capacitor/filesystem — чтение и запись файлов на устройстве;
  • @capacitor/preferences — простое хранилище ключ-значение;
  • @capacitor/device — информация об устройстве и платформе.

Помимо официальных существует большая экосистема сторонних плагинов — для Bluetooth, биометрии, сканирования штрихкодов. При выборе стоит смотреть на совместимость с вашей версией Capacitor и активность репозитория.

Пример: снимок с камеры

import { Camera, CameraResultType } from "@capacitor/camera";

async function takePhoto(): Promise<string> {
  const photo = await Camera.getPhoto({
    quality: 90,
    resultType: CameraResultType.Uri,
  });
  return photo.webPath ?? "";
}

CameraResultType.Uri просит вернуть путь к файлу, а не тяжёлую строку в формате base64: значение photo.webPath можно сразу подставить в атрибут src элемента <img>. Возвращаемое значение типизировано — опечатка в имени поля поймается компилятором.

Вызов оборачивают в try/catch: пользователь может отменить съёмку или отказать в доступе, и в обоих случаях плагин выбросит ошибку.

Разрешения

Доступ к камере, геолокации и файловой системе требует согласия пользователя. Плагины, которым нужны разрешения, предоставляют единую пару методов: checkPermissions возвращает текущий статус, requestPermissions показывает системный диалог.

Запрашивать разрешения стоит лениво — в момент, когда возможность реально понадобилась, а не при запуске приложения:

import { Camera } from "@capacitor/camera";

async function ensureCameraAccess(): Promise<boolean> {
  const status = await Camera.checkPermissions();
  if (status.camera === "granted") return true;
  const requested = await Camera.requestPermissions();
  return requested.camera === "granted";
}

Кроме вызовов в коде, нужно прописать описания разрешений в нативных конфигурационных файлах:

  • iOS — ключ NSCameraUsageDescription в Info.plist;
  • Android — тег <uses-permission> в AndroidManifest.xml.

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

Свой плагин

Если нужного API нет ни в официальных, ни в сторонних плагинах, можно написать свой. Плагин состоит из трёх частей: TypeScript-интерфейс, реализация на Swift для iOS и реализация на Kotlin для Android.

TypeScript — объявляем интерфейс и регистрируем плагин:

import { registerPlugin } from "@capacitor/core";

export interface EchoPlugin {
  echo(options: { value: string }): Promise<{ value: string }>;
}

export const Echo = registerPlugin<EchoPlugin>("Echo");

iOS — наследуемся от CAPPlugin:

import Capacitor

@objc(EchoPlugin)
public class EchoPlugin: CAPPlugin, CAPBridgedPlugin {
    public let identifier = "EchoPlugin"
    public let jsName = "Echo"
    public let pluginMethods: [CAPPluginMethod] = [
        CAPPluginMethod(name: "echo", returnType: CAPPluginReturnPromise)
    ]

    @objc func echo(_ call: CAPPluginCall) {
        let value = call.getString("value") ?? ""
        call.resolve(["value": value])
    }
}

Android — аннотация @CapacitorPlugin, имя должно совпадать с именем в registerPlugin:

@CapacitorPlugin(name = "Echo")
class EchoPlugin : Plugin() {
    @PluginMethod
    fun echo(call: PluginCall) {
        val value = call.getString("value")
        val ret = JSObject()
        ret.put("value", value)
        call.resolve(ret)
    }
}

Для TypeScript-кода Echo.echo(...) неотличим от любого готового плагина — мост склеивает реализации по имени.

Мост или стандартный web-API

Для части возможностей на выбор: либо мост через плагин, либо стандартный web-API браузера. Например, геолокацию можно получить и через navigator.geolocation, и через @capacitor/geolocation.

Общее правило:

  • web-API подходит, если не нужна глубокая нативная интеграция и важна совместимость с браузером;
  • плагин выбирают, когда нужны возможности выше браузерного уровня или точный контроль над системными диалогами.

Коротко

  • WebView изолирован от нативного SDK — для выхода за его пределы нужен мост Capacitor.
  • Мост превращает вызов нативного кода в обычный await-промис в TypeScript.
  • Официальные плагины закрывают камеру, геолокацию, файловую систему, хранилище и информацию об устройстве.
  • После установки любого плагина обязательно запускать npx cap sync.
  • Разрешения запрашивают лениво; строки-описания прописывают в Info.plist и AndroidManifest.xml.
  • Свой плагин — один TypeScript-интерфейс плюс две нативные реализации (Swift и Kotlin).

Что почитать дальше

  • Capacitor: веб-приложение в нативной обёртке — как работает сам контейнер и WebView.
  • iOS: WKWebView, App Store и ограничения — специфика iOS-платформы.
  • Android: TWA и WebView — специфика Android-платформы.
  • Офлайн и хранилище — работа с данными без сети.