# Начало работы с URnetwork SDK

Этот гайд ведёт разработчика от пустого проекта до первого подключения:
получить JWT аккаунта, установить биндинг для своей платформы, поднять
устройство и убедиться, что трафик идёт. SDK — это один Go-модуль,
`github.com/urnetwork/sdk`, открытый через три слоя биндингов: биндинги
gomobile для Android и Apple, сборка WebAssembly для JavaScript и
c-shared-библиотеки с курируемым C ABI для всего остального. Об
архитектуре за каждым биндингом читайте [тур по SDK](/docs/tour-sdk).

URnetwork использует выходные устройства, которые держат участники. На
ретранслируемых путях провайдеры не получают исходные IP ваших
пользователей. Устройства, построенные на SDK, тоже по умолчанию шифруют
трафик до провайдера. Полная модель — в статье
[Как работает URnetwork](/docs/overview).

## Что понадобится

Все три слоя выводят наружу одно и то же ядро, на котором построены
официальные приложения URnetwork: устройство (движок соединения),
API-клиент, конфигурационную модель сетевого пространства (с каким
развёртыванием платформы вы говорите, с какими эндпоинтами и флагами) и
вью-контроллеры — безголовые объекты «состояние плюс события» для типовых
экранов, к которым можно привязать свой UI или игнорировать их.
Операторский API за этим задокументирован на [/docs/api](/docs/api). Вы
строите собственное приложение на размещённой платформе URnetwork: API,
ретрансляторы и провайдеры — живой сервис, а аккаунты ваши пользователи
приносят или создают.

Минимальные платформы, по биндингам:

- **Android** — уровень API 24 и новее. Java-пакет — `com.bringyour.sdk`.
- **iOS / macOS** — iOS 16.0 и macOS 13.5.
- **JavaScript** — только браузер.
- **cgo** — Ubuntu 22.04+ (glibc 2.35+) или Windows 10+, amd64 и arm64.
- **Сборка из исходников** — Go 1.26+.

SDK распространяется под MPL-2.0 (Mozilla Public License): свободно
линкуйте его в приложения с закрытым кодом. Пофайловый копилефт обязывает
делиться только изменениями собственных файлов SDK.

## Вход

Каждый сниппет ниже принимает `byJwt`: JWT аккаунта, подписанный токен
входа, который API URnetwork выдаёт при аутентификации аккаунта.
Отдельной системы API-ключей нет.

Ваше приложение один раз проводит процесс входа через API-клиент SDK.
`authLogin` сообщает методы аутентификации идентификатора;
`authLoginWithPassword` завершает вход (`authVerify` довершает код из
почты или SMS), а кошельковая аутентификация и `networkCreate` —
параллельные точки входа. Кошельковая аутентификация — только по подписи:
вы отправляете `wallet_address`, `wallet_message` и `wallet_signature`,
никогда не ключ, и ни одна поверхность URnetwork не спрашивает приватный
ключ или мнемонику кошелька. `networkCreate` вовсе без метода
аутентификации — путь мгновенного аккаунта: он чеканит постоянный аккаунт
и возвращает `seedphrase` — собственную фразу восстановления URnetwork
для этого аккаунта, сгенерированную на сервере и выданную ровно один раз.
Покажите её пользователю сразу, иначе у аккаунта нет пути восстановления.

Каждый путь возвращает `by_jwt`. Сохраните его, вызовите `setByJwt` и
передайте конструктору устройства. Код обновления вы не пишете; менеджер
токенов в `Api` ротирует JWT. Жёсткий сбой аутентификации трактуйте как
«запустить вход заново».

Аккаунт — это ещё и единица оплаты: учитывается тариф того аккаунта, чей
JWT держит устройство. Бесплатный — дневной лимит данных, Pro — большой
месячный; текущие цифры на [ur.io/products](https://ur.io/products).
Закладывайте один аккаунт на конечного пользователя — путь мгновенного
аккаунта делает это бесшовным, — а не один вшитый аккаунт, сливающий
использование и поведение всех пользователей в одного актора. Когда у
аккаунта кончаются данные, передача замирает до обновления лимита,
поэтому говорите в своём UX «кончились данные», а не показывайте общую
сетевую ошибку.

SDK владеет и процессом платного тарифа, потому что клиент никогда не
должен сам называть свою цену: зарегистрируйте намерение через
`createSolanaPaymentIntent` (`reference` из `createPaymentReference`,
`plan` — `"monthly"` или `"yearly"`), возьмите `amountUsd` из результата
и передайте его в `buildSolanaPaymentUrl`. В C ABI есть вызов намерения,
но пока нет построителя URL.

## Установка

Готовые артефакты — это ассеты релизов на
[github.com/urnetwork/build](https://github.com/urnetwork/build/releases).
Версии датированные, `vYYYY.M.D-<code>` (например,
`v2026.7.22-999364023`), не SemVer: закрепите релиз и обновляйтесь
осознанно — версия говорит, когда её нарезали, а не сдвинулся ли API.

**Android.** Скачайте `URnetworkSdk-<version>.aar` (плюс `-sources.jar`
для навигации в IDE) и положите их в каталог, который ваш Gradle-модуль
уже сканирует, например:

```groovy
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.aar'])
}
```

**iOS / macOS.** Скачайте `URnetworkSdk.xcframework.zip`, распакуйте и
сошлитесь на него из локального Swift-пакета как на `binaryTarget`:

```swift
// Package.swift
targets: [
    .binaryTarget(name: "URnetworkSdk", path: "URnetworkSdk.xcframework")
]
```

Все типы с префиксом `Sdk` (`SdkDeviceLocal`, `SdkNetworkSpace`, ...).
Xcframework несёт `ios/arm64`, `iossimulator/arm64`, `macos/arm64` и
`macos/amd64`; среза iOS-симулятора для Intel нет.

**JavaScript.** Npm-пакет —
[`@urnetwork/sdk-js`](https://www.npmjs.com/package/@urnetwork/sdk-js),
загрузчик только для браузера, который подтягивает `wasm_exec.js` из Go
плюс wasm SDK и инстанцирует их на странице. Ставьте тег `nightly`; он
нарезается из текущего SDK по той же датированной схеме версий и несёт
wasm. `latest` отстаёт на месяцы и wasm не несёт вовсе, поэтому простой
`npm install` оставляет вам загрузчик, которому нечего загружать.

```sh
npm install @urnetwork/sdk-js@nightly   # then pin the version it resolved to
```

Wasm — примерно 43 МБ, целиком ядро Go, так что он не уменьшится, но
хорошо жмётся: раздавайте его как статический ассет (gzip или brotli, с
жёстким кешем) и загружайте лениво, только когда пользователь добирается
до поверхности подключения. Никогда не позволяйте бандлеру инлайнить или
преобразовывать его и всегда поставляйте `sdk.wasm` и `wasm_exec.js` из
одной сборки; клей ABI-спарен с тулчейном Go, который скомпилировал wasm,
и сборка SDK гейтится на их побайтовом совпадении.

**cgo.** C-shared-библиотеки — тот же слой, на котором построены
поставляемые приложения URnetwork для Linux и Windows, — и рядом два
курируемых заголовка:

- `libURnetworkSdk.so` — Linux.
- `URnetworkSdk.dll` (+ `urnetwork_sdk.def`) — Windows.
- `urnetwork_sdk.h` — чистый C ABI.
- `urnetwork_sdk.hpp` — header-only C++17 RAII-обёртка над ним, требующая
  [nlohmann/json](https://github.com/nlohmann/json) в вашем include-пути.

Всё, у чего есть C-интерфейс внешних функций (Rust bindgen, Python
ctypes, C# P/Invoke), ложится на этот ABI из хендлов и JSON; заголовок
C++ — единственное языковое удобство. С MSVC сначала сгенерируйте
библиотеку импорта из файла `.def`:

```bat
lib /def:urnetwork_sdk.def /machine:x64 /out:URnetworkSdk.lib
```

**Из исходников.** Нужен Go 1.26+, а для мобильных сначала `make init`:
он закрепляет тот самый gomobile, которым нарезаются биндинги, и
устанавливает checksec, который запускает Android-таргет. `build_android`
требует ещё и выставленного `ANDROID_NDK_HOME`, поскольку срезает
`.comment` через `llvm-objcopy` из NDK. `sdk/build-android.sh` и
`sdk/build-ios.sh` оборачивают и то и другое. Из `sdk/build`:

```sh
make init             # pinned gomobile + checksec; run before the mobile targets
make build_android    # AAR
make build_apple      # xcframework (build_ios is an alias)
make build_js         # wasm + loader
make build_linux      # c-shared .so, cross-compiled with zig
make build_windows    # c-shared .dll; not in `all` — the shipped one builds in a VM
```

`build_android` гейтится на списке пропусков gomobile, поэтому
отсутствующий символ — это несовпадение версий, а не молчаливо выпавший
биндинг; gomobile не биндит ни срезы структур (списки пересекают границу
как `SdkStringList`, `SdkIdList`, ...), ни `context.Context`.

Сторонние оценки безопасности — и их рамки — разобраны в
[модели угроз](/docs/threat-model).

## Разрешение на туннель

Согласие операционной системы — и сам туннель — принадлежат вашему
приложению. SDK начинается на пакетном уровне:

- На **Android** `VpnService` — ваш: ваше приложение объявляет его,
  получает согласие на VPN через `VpnService.prepare()` и вызывает
  `establish()`. SDK принимает дело на этом файловом дескрипторе.
- На **платформах Apple** разделение — требование Apple, а не SDK:
  пакетные туннели работают в отдельном NetworkExtension с жёсткими
  лимитами памяти, поэтому поставщик туннеля владеет устройством, а
  процесс вашего приложения присоединяется к нему удалённо.
- В **браузере** страница не может владеть сетевым интерфейсом, поэтому
  спрашивать разрешение не о чем, и ничто в слое JavaScript не
  туннелирует собственный трафик страницы. Обе JavaScript-модели
  устройства вместо этого управляют устройством на платформе. Расширение
  URnetwork покрывает вкладки браузера; нативное приложение покрывает всю
  машину.

О том, что URnetwork записывает о подключениях, читайте в
[модели угроз](/docs/threat-model).

## Подключение

Новое устройство начинает с сетевых умолчаний. Сеанс клиент↔провайдер
запечатан из коробки: `setPerformanceProfile` поставляется с включённым
`PostQuantumEncryption` — флагом за настройкой приложений «Постквантовое
шифрование» (Post Quantum Encryption), — и каждая текущая сборка
провайдера включает отвечающую сторону. С включённым флагом клиент
работает в режиме fail-closed: он не несёт данные приложения в открытом
виде и пропускает провайдера, с которым не удаётся запечатать сеанс, а не
использует его незапечатанным. Выключите флаг — и трафик снова сможет
пойти стандартным путём. `AllowDirect` по умолчанию
выключен; это выбираемая явно настройка скорости, которая убирает
анонимизирующее звено и вручает тому провайдеру реальный IP вашего
пользователя; приложения показывают её перевёрнутой как «Строгая
анонимизация» (Strong Anonymization), а профили размещённых устройств
принудительно её выключают. Защиту от утечек подключаете вы: kill switch
(аварийный стоп) — это примитив `setRouteLocal`, устройство стартует с
разрешённой локальной маршрутизацией, и `setRouteLocal(false)` заставляет
трафик останавливаться, а не откатываться, когда туннель лежит. SDK не
встраивает ни аналитики, ни отчётов о падениях; его единственные
соединения — эндпоинты платформы и те ретрансляторы и провайдеры, которых
использует устройство.

### Android

Порядок бутстрапа: создайте `NetworkSpaceManager`, нацеленный на
приватное хранилище приложения (каталог держит локальное состояние,
включая учётные данные), создайте сетевое пространство, затем установите
JWT аккаунта на его API-клиенте. Ключ — имя хоста плюс имя окружения;
продакшен — `("ur.network", "main")`, и создаёт пространство именно
`updateNetworkSpace`, тогда как `getNetworkSpace` лишь читает
существующее, поэтому на свежей установке возвращает null. URL выводятся
из ключа (`https://api.<host>`, `wss://connect.<host>`; не-`main`
окружение добавляет префикс к сервису), но вывод предпочитает
`migrationHostName`, который продакшен ставит в `bringyour.com`:
поставляемые приложения ходят на `api.bringyour.com`, а `api.ur.network`
не резолвится. Затем создайте устройство и вручите ему файловый
дескриптор туннеля:

```kotlin
import com.bringyour.sdk.Sdk

val manager = Sdk.newNetworkSpaceManager(context.filesDir.absolutePath)
val key = Sdk.newNetworkSpaceKey("ur.network", "main")
val networkSpace = manager.updateNetworkSpace(key) { it.migrationHostName = "bringyour.com" }
networkSpace.api.setByJwt(byJwt)

val device = Sdk.newDeviceLocalWithMemoryTarget(
    networkSpace,
    byJwt,
    deviceDescription,   // free-form, shown in the account's device list
    deviceSpec,          // e.g. Build.MODEL
    appVersion,
    Sdk.newId(),         // instanceId; persist and reuse per install
    /* enableRpc */ false,
    keyMaterial,         // persisted DeviceLocalKeyMaterial, or null for ephemeral
    memoryTargetByteCount,
)

// inside your VpnService, after establish():
val detachedFd = pfd.detachFd()          // ParcelFileDescriptor -> raw fd
val ioLoop = Sdk.newIoLoop(device, detachedFd) { /* done callback */ }
```

`newIoLoop` забирает владение отсоединённым fd и качает пакеты в обе
стороны, пока вы его не закроете. Не трогайте fd из Java снова после
отсоединения.

Три аргумента конструктора заслуживают внимания. `instanceId`:
сгенерируйте один `Sdk.newId()` при первом запуске, сохраните и
переиспользуйте всю жизнь установки (одно живое устройство на процесс);
свежий id на каждый запуск добавляет фантомную запись в список устройств
аккаунта. `keyMaterial`: null годится для чистого клиента, но если
устройство когда-нибудь будет раздавать ёмкость, сохраните
`getKeyMaterial()` в защищённом хранилище платформы. Это провайдерская
идентичность устройства, и её потеря обнуляет историю надёжности
провайдера. `memoryTargetByteCount`: байтовый бюджет, под который
устройство подгоняет свои буферы и темп GC; выберите тот, что
укладывается в реальный потолок вашего процесса (см.
[тур](/docs/tour-sdk)). `DeviceLocal` живёт внутри процесса: умирает ваш
процесс — умирает и туннель, поэтому запускайте `VpnService` как
foreground-сервис и пересоздавайте устройство после рестарта с теми же
`instanceId` и ключевым материалом.

### iOS / macOS

`NEPacketTunnelProvider` владеет `SdkDeviceLocal` и потоком пакетов, а
процесс приложения присоединяется к нему как удалённое устройство через
loopback:

```swift
// in the packet tunnel provider (owns the device):
var err: NSError?
let device = SdkNewDeviceLocalWithMemoryTarget(
    networkSpace, byJwt, deviceDescription, deviceSpec, appVersion,
    instanceId, /* enableRpc */ true, keyMaterial, memoryTarget, &err)

// in the app process (attaches to it):
let remote = SdkNewDeviceRemoteWithDefaults(networkSpace, byJwt, instanceId, &err)
try remote?.setRpcServer(clientPem, serverCertPem: serverCertPem, hostPort: hostPort)
```

Приложение чеканит ключевой материал и RPC-PEM
(`SdkGenerateDeviceRpcKeyMaterial`) и вручает их расширению в
конфигурации провайдера `NETunnelProviderProtocol`; расширение читает
оттуда `rpc_server_pem`, `rpc_client_pem` и `rpc_listen_hostport`. App
group на этом пути нет. Зачем существует разделение и как работает
переподключение — в [туре](/docs/tour-sdk).

### JavaScript

```js
import { URNetwork } from "@urnetwork/sdk-js";

const sdk = await URNetwork.init({
  wasmUrl: "/wasm/sdk.wasm",
  wasmExecUrl: "/wasm/wasm_exec.js",
});
```

Wasm регистрирует свои экспорты на `window`, поэтому держите один
экземпляр модуля на страницу. `init` идемпотентен внутри модуля (второй
вызов возвращает тот же экземпляр), но две копии загрузчика — скажем, два
бандла или два фрейма, делящие один realm, — гоняются за одними и теми же
глобалами. Инициализируйте один раз в синглтоне уровня модуля, никогда —
внутри жизненного цикла компонента.

Две модели устройства:

- `sdk.createProxyDevice(...)` — лёгкий клиент размещённых прокси-URL,
  для случаев, когда всё, что вам нужно, — «прокси-URL с выходом через
  URnetwork».
- `sdk.createPlatformDeviceRemote({...})` — полный `DeviceRemote`,
  говорящий по device-RPC с размещённым устройством через вебсокет, для
  настоящего UI подключения (локации, слушатели, статистика):

```js
const device = sdk.createPlatformDeviceRemote({
  apiUrl: "api.bringyour.com",
  platformUrl: "connect.bringyour.com",
  byJwt,
  proxyUrl,        // from the platform's proxy config endpoint
  signedProxyId,   // HMAC auth token, not the JWT — see the tour
});

const unsub = device.addConnectLocationChangeListener((loc) => {
  console.log("location:", loc?.name);
});
device.setConnectLocation({ bestAvailable: true });
```

`createPlatformDeviceRemote` бросает исключение, если загруженный wasm
старше биндинга `DeviceRemote`; это симптом старого npm-тега.

### cgo

Контракт ABI, коротко:

- Объекты — **непрозрачные хендлы `uint64_t`**. `urnet_release(h)`
  освобождает хендл, не останавливая объект, поэтому сначала вызывайте
  его `*_close`/`*_stop` там, где они есть.
- Возвращаемые строки `char*` принадлежат вызывающему; освобождайте их
  через `urnet_free_string`.
- Структурированные данные пересекают границу как строки **UTF-8 JSON**;
  идентификаторы — строки UUID, времена — миллисекунды эпохи Unix
  (`0` = нет).
- Колбэки срабатывают на **произвольных потоках под управлением Go**;
  переносите на свой поток сами. Их строки и буферы живут только на время
  вызова, а хендлы, которые они вам вручают, освобождать вам.
- Способные на отказ вызовы принимают `char** out_error`; при сбое туда
  пишется сообщение, которое вы освобождаете через `urnet_free_string`.
  Передайте `NULL`, чтобы игнорировать текст.

Заголовок несёт одно платформенное разделение: `urnet_new_io_loop` —
насос fd, которым пользуется приложение для Linux, — сидит внутри
`#if !defined(_WIN32)` и отсутствует в Windows-`.def`. На Windows пакеты
двигают `urnet_device_local_send_packet` и
`urnet_device_local_add_receive_packet`.

Рабочий сквозной пример живёт в `cgo/smoke`; `make smoke_hpp` в `sdk/cgo`
собирает смоук-тест C++-обёртки против сборки для хоста и запускает его.

## Убедитесь, что работает

Поднимите устройство и проверьте выход: любая проверка «какой у меня IP»
через туннель должна теперь сообщать адрес провайдера, а не машины. То же
состояние видно в коде и в аккаунте: слушатели срабатывают по мере смены
подключения (JavaScript-сниппет выше логирует каждую смену локации по
мере прибытия), а устройство появляется в списке устройств аккаунта под
`deviceDescription`, который вы передали. На C ABI
`urnet_live_handle_count()` сообщает живые хендлы; в тестах на утечки
проверяйте, что он возвращается к базовой отметке.

## Если что-то не так

Поддержка — это открытые каналы: issues в репозиториях
([github.com/urnetwork](https://github.com/urnetwork)), отзывы о продукте
на [feedback.ur.io](https://feedback.ur.io) и отчёты о безопасности на
security@ur.io (политика раскрытия на [ur.io/vdp](https://ur.io/vdp)).
Платного уровня поддержки SDK сегодня нет. У двух замираний простые
объяснения: замирание передачи на работающем туннеле — обычно аккаунт без
данных, а JavaScript-загрузчик, которому нечего загружать, — npm-тег
`latest`. [Тур](/docs/tour-sdk) покрывает режимы отказа, которые стоит
знать до того, как вы отгрузитесь.

## Что дальше

- **Пройдите [тур по SDK](/docs/tour-sdk).** Архитектура за каждым
  биндингом: разделение процессов, потоки, аутентификация, переподключение
  и подбор целевого объёма памяти.
- **Расскажите своим пользователям, к чему они присоединяются.** Если
  ваше приложение ведёт трафик через URnetwork, [обзор](/docs/overview) —
  каноническое описание пути и того, что может видеть каждая сторона, а
  [модель угроз](/docs/threat-model) — полная запись за ним.
- **Посмотрите готовые приложения** — опыт, который получат ваши
  пользователи: [Android](/docs/getting-started-android),
  [iOS](/docs/getting-started-ios), [macOS](/docs/getting-started-macos),
  [Windows](/docs/getting-started-windows),
  [Linux](/docs/getting-started-linux) и
  [браузер](/docs/getting-started-browser).
