Bridge library
Оригинальная документация пакета @synchra24/app.
TypeScript SDK для гибридных приложений, которые открываются внутри мобильного приложения Synchra.24 через WebView.
Пакет оборачивает native bridge Synchra и предоставляет Promise-based API для вызова возможностей приложения: запросы к разрешенным API, контекст провайдера, тема, геопозиция, выбор файлов и медиа, получение файлов из S3, QR-сканер, выбор пользователей, календарь, выбор времени, snackbar и модальное окно подтверждения.
Установка
npm install @synchra24/app
import { SynchraApp } from "@synchra24/app";
const synchra = new SynchraApp("SERVICE_TOKEN");
SERVICE_TOKEN - токен внутреннего сервиса, созданный в Synchra.24. Native-приложение проверяет этот токен при каждом bridge-вызове.
Быстрый старт
const synchra = new SynchraApp("SERVICE_TOKEN");
const integrated = await synchra.isSynchraIntegrated();
if (!integrated) {
console.log("Приложение открыто не внутри Synchra.24 или токен невалиден.");
}
const provider = await synchra.getProviderContext();
const theme = synchra.getSynchraTheme();
await synchra.showSnackbar({
message: `Provider ID: ${provider.provider_id}`,
type: "success",
});
console.log(theme?.active);
console.log(theme?.current.controlPrimary);
Создание клиента
const synchra = new SynchraApp(token, {
responseTimeout: 60000,
});
Параметры:
responseTimeout- таймаут ожидания ответа от native bridge в миллисекундах. По умолчанию60000.
Когда экземпляр больше не нужен, можно снять listeners и отклонить ожидающие запросы:
synchra.destroy();
Ошибки
Большинство методов возвращают Promise. Если native-приложение вернуло ошибку, Promise будет отклонен через Error(message).
try {
const position = await synchra.getGeoPosition();
} catch (error) {
console.error(error);
}
Метод isSynchraIntegrated() специально сделан безопасным: он возвращает false, если bridge недоступен или токен не прошел проверку.
Проверка интеграции
Проверяет, что приложение открыто внутри Synchra.24 и service token валиден.
const integrated = await synchra.isSynchraIntegrated();
Возвращает:
boolean;
Тема приложения
Synchra прокидывает цветовую тему напрямую в WebView, поэтому чтение темы не требует bridge-запроса.
const theme = synchra.getSynchraTheme();
Возвращает:
{
active: 'light' | 'dark';
current: Record<string, string>;
themes: {
light: Record<string, string>;
dark: Record<string, string>;
};
} | null
Подписка на изменение темы:
const unsubscribe = synchra.onSynchraThemeChange((theme) => {
document.body.style.background = theme.current.primary;
});
unsubscribe();
Также доступен legacy bridge активной темы:
const activeTheme = await synchra.getThemeState();
const unsubscribeAppearance = synchra.onThemeAppearance((theme) => {
console.log(theme);
});
API-запросы
Выполняет разрешенные Open API запросы через native-приложение Synchra.
const response = await synchra.requestApi<{ parameters: { name: string } }>({
method: "GET",
path: "providers/parameters",
query: {
key: "value",
},
});
console.log(response.result.parameters.name);
Payload:
{
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
path: string;
body?: Record<string, unknown>;
query?: Record<string, unknown>;
apiVersion?: number;
form?: Record<string, unknown> | { name: string; value: unknown }[];
}
apiVersion позволяет вызывать не только /api/v1, но и другие версии API. Если передать apiVersion: 2, запрос уйдет на /api/v2/....
form нужен для multipart/form-data запросов. Поскольку bridge сериализуется через postMessage, в form нужно передавать обычный JSON-совместимый объект или массив полей, а native-слой сам соберет FormData.
Пример multipart-запроса:
await synchra.requestApi({
method: "POST",
path: "reports/create",
apiVersion: 1,
form: {
title: "Отчет",
published: true,
tags: ["daily", "shift"],
},
});
Контекст провайдера
Возвращает текущий контекст провайдера Synchra без provider token.
const provider = await synchra.getProviderContext();
console.log(provider.provider_id);
console.log(provider.profile_id);
console.log(provider.role.permissions);
console.log(provider.bucketName);
Provider token этим методом не возвращается.
Выбор пользователей
Открывает native-виджет выбора пользователей.
const result = await synchra.selectUsers({
users: [25],
all_users: true,
});
console.log(result.users);
Payload:
{
users?: number[];
all_users?: boolean;
}
Возвращает:
{
users: number[];
}
Геопозиция
Запрашивает текущую геопозицию пользователя через native-приложение.
const position = await synchra.getGeoPosition();
console.log(position.latitude, position.longitude);
Возвращает:
{
latitude: number;
longitude: number;
altitude: number | null;
accuracy: number | null;
altitude_accuracy: number | null;
heading: number | null;
speed: number | null;
timestamp: number;
}
Фото с камеры
Открывает native-камеру и возвращает фото. По умолчанию файл загружается в хранилище Synchra, а в ответе приходят file, bucket и base64.
const photo = await synchra.takePhoto({
use_front_camera: false,
upload: true,
});
console.log(photo.file);
console.log(photo.bucket);
console.log(photo.base64);
Если нужен только base64, загрузку можно отключить:
const photo = await synchra.takePhoto({
upload: false,
});
console.log(photo.file); // null
console.log(photo.bucket); // null
console.log(photo.base64);
Возвращает:
{
file: string | null;
bucket: string | null;
base64: string;
name: string;
type: string;
}
Фото из галереи
Открывает native-выбор фото из галереи. Формат ответа такой же, как у takePhoto().
const photo = await synchra.pickPhoto({
upload: false,
});
Выбор файла
Открывает native file picker. Файл всегда копируется в локальный cache приложения и возвращается как local_uri. При необходимости он дополнительно загружается в хранилище Synchra.
const file = await synchra.pickFile({
upload: true,
});
console.log(file.file);
console.log(file.bucket);
console.log(file.local_uri);
console.log(file.name);
console.log(file.size);
Если нужен только локальный файл без загрузки в Synchra, загрузку можно отключить:
const file = await synchra.pickFile({
upload: false,
});
Возвращает:
{
file: string | null;
bucket: string | null;
local_uri: string;
name: string;
type: string;
size: number;
}
QR-сканер
Открывает native QR-сканер.
const qr = await synchra.scanQr();
console.log(qr.value);
console.log(qr.type);
Возвращает:
{
value: string;
type?: string;
}
Получение изображения из S3
Получает файл из S3 через native-приложение. По умолчанию возвращает локальный путь local_uri, потому что это заметно легче для bridge и лучше подходит для больших изображений.
const image = await synchra.getS3Image({
file: "avatar.jpg",
});
console.log(image.local_uri);
Если нужен именно base64, можно явно запросить его:
const image = await synchra.getS3Image({
file: "avatar.jpg",
response_type: "base64",
});
Возвращает:
{
file: string;
local_uri: string | null;
base64: string | null;
response_type: "local_uri" | "base64";
source_url: string;
}
Получение файла из S3
Работает так же, как получение изображения, но предназначен для любых файлов.
const file = await synchra.getS3File({
file: "docs/report.pdf",
});
console.log(file.local_uri);
Для обоих S3-методов bucket снаружи не передается. Native-слой всегда берет его из providerCardContext.bucketName текущего провайдера.
Навигация по внутренним экранам
Позволяет из гибридного приложения открывать внутренние экраны Synchra.
await synchra.navigate({
action: "navigate",
screen: "Tasks",
});
С параметрами:
await synchra.navigate({
action: "navigate",
screen: "TaskCard",
params: {
number: "123",
},
});
Поддерживаемые действия:
navigatepushreplacego_backpop_to_top
Пример возврата назад:
await synchra.navigate({
action: "go_back",
});
Возвращает:
{
success: boolean;
action: "navigate" | "push" | "replace" | "go_back" | "pop_to_top";
screen: string | null;
}
Если указать неизвестный экран, native bridge вернет ошибку screen not found.
Выбор даты
Открывает native date picker для выбора одной даты.
const result = await synchra.selectDate({
date: "2026-04-21",
});
console.log(result.date);
Возвращает:
{
date: string | null; // YYYY-MM-DD
}
Выбор диапазона дат
Открывает native date range picker.
const range = await synchra.selectDateRange({
start_date: "2026-04-01",
end_date: "2026-04-21",
});
console.log(range.start_date, range.end_date);
Возвращает:
{
start_date: string | null;
end_date: string | null;
}
Выбор времени
Открывает native time picker.
const time = await synchra.selectTime({
time: "09:30",
});
console.log(time.time);
console.log(time.hours);
console.log(time.minutes);
В payload можно передать либо time, либо hours/minutes.
Возвращает:
{
time: string; // HH:mm
hours: number;
minutes: number;
}
Snackbar
Показывает native snackbar Synchra.
await synchra.showSnackbar({
message: "Сохранено",
type: "success",
timeout: 4000,
});
Доступные типы:
successerrorwarnempty
Возвращает:
{
shown: boolean;
}
Модальное окно
Открывает native-модальное окно Synchra с двумя кнопками.
const result = await synchra.openModal({
title: "Подтверждение",
text: "Продолжить действие?",
primary_button_text: "Продолжить",
secondary_button_text: "Отмена",
});
if (result.action === "primary") {
// Нажата первая кнопка
}
if (result.action === "secondary") {
// Нажата вторая кнопка
}
if (result.action === "dismiss") {
// Окно закрыто нажатием по фону
}
Возвращает:
{
action: "primary" | "secondary" | "dismiss";
}
TypeScript
Пакет поставляется с декларациями TypeScript. Можно импортировать типы payload и response:
import type {
SynchraProviderContext,
SynchraPickedFilePayload,
SynchraThemePayload,
} from "@synchra24/app";
Запуск вне Synchra
Большинство bridge-методов требуют window.ReactNativeWebView. При вызове вне мобильного приложения Synchra они выбросят ошибку. Для безопасной runtime-проверки используйте isSynchraIntegrated().
const integrated = await synchra.isSynchraIntegrated();
if (!integrated) {
// Показать fallback UI для обычного браузера.
}
Разработка
Установить зависимости:
npm install
Проверить типы:
npm run typecheck
Собрать пакет:
npm run build
Проверить состав публикации:
npm pack --dry-run
В npm-пакет попадают только dist, README.md и package.json.
Публикация
Перед публикацией нужно убедиться, что native-приложение Synchra поддерживает те же bridge-события, что и SDK.
npm publish
Для публичной публикации scoped-пакета:
npm publish --access public