react-native-ovpn 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +503 -37
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,29 +1,75 @@
1
1
  # react-native-ovpn
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/react-native-ovpn.svg)](https://www.npmjs.com/package/react-native-ovpn)
4
- [![license](https://img.shields.io/npm/l/react-native-ovpn.svg)](LICENSE)
4
+ [![license](https://img.shields.io/npm/l/react-native-ovpn.svg)](#license)
5
5
 
6
- OpenVPN client for React Native — Android, iOS, and Expo. New Architecture (TurboModule), bounded auto-reconnect, kill-switch, custom DNS.
6
+ OpenVPN client for React Native — **Android, iOS, and Expo**. Built as a TurboModule (New Architecture compatible), with bounded auto-reconnect, kill switch, custom DNS, and a foreground-service notification.
7
7
 
8
8
  ```ts
9
9
  import { OpenVPNClient } from 'react-native-ovpn';
10
10
 
11
11
  const client = new OpenVPNClient();
12
- client.on('state', (s) => console.log(s)); // 'connecting' | 'connected' | ...
12
+ client.on('state', (s) => console.log(s)); // 'connecting' | 'connected' | 'disconnected' | 'reconnecting' | 'disconnecting'
13
13
  client.on('stats', ({ bytesIn, bytesOut }) => {});
14
+ client.on('error', (err) => console.warn(err.code, err.message));
14
15
 
15
16
  await client.requestPermission();
16
- await client.connect({ config: ovpnString, username: 'alice', password: 's3cret' });
17
+ await client.connect({
18
+ config: ovpnFileContents,
19
+ username: 'alice',
20
+ password: 's3cret',
21
+ });
22
+ // ...
17
23
  await client.disconnect();
24
+ client.dispose();
18
25
  ```
19
26
 
20
- ## Documentation
27
+ ---
21
28
 
22
- - **[Getting started](docs/getting-started.md)** — install, Android setup, iOS setup, Expo setup
23
- - **[API reference](docs/api.md)** — full method/event/type reference
24
- - **[OpenVPN config support](docs/ovpn-support.md)** — which `.ovpn` directives work
25
- - **[Recipes](docs/examples.md)** — kill-switch, custom DNS, reconnect, 2FA
26
- - **[Troubleshooting](docs/troubleshooting.md)** — common errors and fixes
29
+ ## Table of contents
30
+
31
+ - [Features](#features)
32
+ - [Install](#install)
33
+ - [Expo (recommended)](#expo-recommended)
34
+ - [Bare React Native](#bare-react-native)
35
+ - [API](#api)
36
+ - [`OpenVPNClient`](#openvpnclient)
37
+ - [Events](#events)
38
+ - [Types](#types)
39
+ - [Errors](#errors)
40
+ - [Recipes](#recipes)
41
+ - [Kill switch](#kill-switch)
42
+ - [Custom DNS](#custom-dns)
43
+ - [Per-app routing](#per-app-routing-disallowedallowed-apps)
44
+ - [Handling auto-reconnect](#handling-auto-reconnect)
45
+ - [Reading live bandwidth stats](#reading-live-bandwidth-stats)
46
+ - [Customizing the foreground notification](#customizing-the-foreground-notification)
47
+ - [OpenVPN config support](#openvpn-config-support)
48
+ - [Troubleshooting](#troubleshooting)
49
+ - [How it works](#how-it-works)
50
+ - [License](#license)
51
+
52
+ ---
53
+
54
+ ## Features
55
+
56
+ | Feature | Android | iOS |
57
+ | --- | --- | --- |
58
+ | Connect / disconnect | ✅ | ✅ |
59
+ | State + stats + log events | ✅ | ✅ |
60
+ | Auto-reconnect with bounded exponential backoff | ✅ | ✅ |
61
+ | Username + password auth | ✅ | ✅ |
62
+ | Certificate-only auth | ✅ | ✅ |
63
+ | TLS-auth / tls-crypt / tls-crypt-v2 | ✅ | ✅ |
64
+ | Legacy cipher fallback (AES-128-CBC) — auto-injected | ✅ | ✅ |
65
+ | Custom DNS override | ✅ | ✅ |
66
+ | Kill switch | ✅ | ❌ (Apple limitation) |
67
+ | Foreground service notification | ✅ | n/a |
68
+ | Per-app routing (allowed / disallowed apps) | ✅ | ❌ |
69
+ | New Architecture (TurboModule + Fabric) | ✅ | ✅ |
70
+ | Expo config plugin | ✅ | ✅ |
71
+
72
+ ---
27
73
 
28
74
  ## Install
29
75
 
@@ -35,46 +81,466 @@ yarn add react-native-ovpn
35
81
  pnpm add react-native-ovpn
36
82
  ```
37
83
 
38
- **Expo:** add to `app.config.js`:
84
+ ### Expo (recommended)
85
+
86
+ Add the config plugin to your `app.config.js` (or `app.json`):
39
87
 
40
88
  ```js
41
- plugins: [
42
- ['react-native-ovpn', {
43
- iosAppGroup: 'group.com.example.myapp.openvpn',
44
- iosExtensionBundleIdentifier: 'com.example.myapp.OpenVPNTunnel',
45
- }],
46
- ];
89
+ export default {
90
+ expo: {
91
+ // ...
92
+ plugins: [
93
+ [
94
+ 'react-native-ovpn',
95
+ {
96
+ // iOS only — Apple requires an App Group shared between the host
97
+ // app and the PacketTunnel extension. Format: group.<your-bundle-id>.
98
+ iosAppGroup: 'group.com.example.myapp',
99
+
100
+ // Optional — the extension's bundle id. Defaults to
101
+ // <host-bundle-id>.OpenVPNTunnel
102
+ iosExtensionBundleIdentifier: 'com.example.myapp.OpenVPNTunnel',
103
+
104
+ // Optional — Android notification channel name shown to users
105
+ androidNotificationChannelName: 'VPN',
106
+
107
+ // Optional — path (relative to the project root) to a small PNG/vector
108
+ // notification icon. Falls back to the app icon if omitted.
109
+ androidNotificationIcon: './assets/notification-icon.png',
110
+ },
111
+ ],
112
+ ],
113
+ },
114
+ };
47
115
  ```
48
116
 
49
- Then `npx expo prebuild --clean`.
117
+ Then regenerate the native projects:
50
118
 
51
- **Bare React Native:** see [Getting started](docs/getting-started.md).
119
+ ```bash
120
+ npx expo prebuild --clean
121
+ npx expo run:android
122
+ # or run:ios after the iOS extension setup below
123
+ ```
52
124
 
53
- ## Feature matrix
125
+ > **iOS extension is manual.** Apple requires you to add the
126
+ > *PacketTunnel* extension target through Xcode (App Groups, Network
127
+ > Extensions entitlements, signing). The package ships a Swift template
128
+ > at `ios/PacketTunnelProvider/`. See [iOS PacketTunnel setup](#ios-packettunnel-extension-setup) below.
54
129
 
55
- | Feature | Android | iOS |
130
+ ### Bare React Native
131
+
132
+ #### Android
133
+
134
+ The native side is powered by [ics-openvpn](https://github.com/schwabe/ics-openvpn), vendored as a prebuilt AAR. autolinking handles the rest. You will need:
135
+
136
+ 1. **Permissions** in `android/app/src/main/AndroidManifest.xml`:
137
+ ```xml
138
+ <uses-permission android:name="android.permission.INTERNET" />
139
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
140
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
141
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
142
+ ```
143
+
144
+ 2. **Activity result** for the `VpnService.prepare()` system dialog —
145
+ our `requestPermission()` method takes care of this for you.
146
+
147
+ 3. **MinSdk** of 23 or higher. If you're on Expo SDK 50+, this is already
148
+ the default.
149
+
150
+ 4. **Java desugaring** (already enabled by React Native 0.71+ via the
151
+ default Gradle config).
152
+
153
+ #### iOS
154
+
155
+ Open `ios/<YourApp>.xcworkspace` in Xcode and add the PacketTunnel
156
+ extension target — see [iOS PacketTunnel extension setup](#ios-packettunnel-extension-setup).
157
+
158
+ Then run `pod install` from the `ios` directory.
159
+
160
+ #### iOS PacketTunnel extension setup
161
+
162
+ > ⚠️ iOS requires a separate **Network Extension** target inside your
163
+ > Xcode project. Apple does not allow this to be created from Expo's
164
+ > config plugin or `pod install` alone — it must be added manually,
165
+ > one time, in Xcode.
166
+
167
+ 1. In Xcode, **File → New → Target** → *Network Extension* → *Packet Tunnel Provider*
168
+ 2. Name it **OpenVPNTunnel** (or match your `iosExtensionBundleIdentifier`)
169
+ 3. Add both the host app and the extension to the same **App Group**
170
+ (matches `iosAppGroup` in the plugin config)
171
+ 4. Replace the auto-generated `PacketTunnelProvider.swift` with the one
172
+ that ships at `node_modules/react-native-ovpn/ios/PacketTunnelProvider/PacketTunnelProvider.swift`
173
+ 5. Add the **Network Extensions** entitlement to both targets
174
+ (`com.apple.developer.networking.networkextension` →
175
+ `packet-tunnel-provider`)
176
+ 6. The extension target needs the same `OpenVPNAdapter` pod —
177
+ `pod install` after editing your `Podfile` to include the extension
178
+ 7. Re-build
179
+
180
+ ---
181
+
182
+ ## API
183
+
184
+ ### `OpenVPNClient`
185
+
186
+ ```ts
187
+ import { OpenVPNClient } from 'react-native-ovpn';
188
+
189
+ const client = new OpenVPNClient();
190
+ ```
191
+
192
+ | Method | Returns | Notes |
56
193
  | --- | --- | --- |
57
- | Connect / disconnect | | |
58
- | State + stats + log events | | |
59
- | Auto-reconnect (bounded backoff) | | |
60
- | Username + password auth | | |
61
- | Certificate-only auth | | |
62
- | TLS-auth / tls-crypt / tls-crypt-v2 | | |
63
- | Legacy cipher fallback (AES-128-CBC) | (auto-injected) | |
64
- | Custom DNS override | | |
65
- | Kill-switch | | |
66
- | Foreground notification | ✅ | n/a |
67
- | Per-app routing | ❌ | ❌ |
68
- | Expo config plugin | ✅ | ✅ |
194
+ | `requestPermission()` | `Promise<boolean>` | Shows the OS `VpnService.prepare` dialog (Android) / Network Extension permission (iOS). Must be called before the first `connect()`. Resolves `true` once granted. |
195
+ | `connect(options)` | `Promise<void>` | Resolves once the tunnel reaches `connected`. Rejects on hard errors (auth failure, malformed config, etc.). |
196
+ | `disconnect()` | `Promise<void>` | Tears down the tunnel and cancels any pending reconnect attempts. |
197
+ | `getStatus()` | `Promise<Status>` | Snapshot of the current native state — useful on app reopen if your JS process was killed but the foreground service stayed alive. |
198
+ | `getStats()` | `Promise<Stats>` | Latest byte counters. Cheaper than subscribing to `stats` events. |
199
+ | `on(event, listener)` | `void` | Subscribe to a tunnel event. See [Events](#events). |
200
+ | `off(event, listener)` | `void` | Unsubscribe a single listener. |
201
+ | `removeAllListeners()` | `void` | Drop all listeners across all events. |
202
+ | `dispose()` | `void` | Remove all listeners and tear down native subscriptions. Call when the client instance is no longer needed. |
203
+
204
+ `ConnectOptions`:
205
+
206
+ ```ts
207
+ type ConnectOptions = {
208
+ /** Full .ovpn file contents as a string. */
209
+ config: string;
210
+
211
+ /** username/password for `auth-user-pass` configs. Pass empty strings for cert-only. */
212
+ username: string;
213
+ password: string;
214
+
215
+ /** Enable kill switch — block all traffic when tunnel drops. Android only. */
216
+ killSwitch?: boolean;
217
+
218
+ /** Override DNS servers used inside the tunnel. */
219
+ dns?: string[];
220
+
221
+ /** Android: only these apps tunnel through the VPN. Mutually exclusive with disallowedApps. */
222
+ allowedApps?: string[];
223
+
224
+ /** Android: every app EXCEPT these tunnels. Mutually exclusive with allowedApps. */
225
+ disallowedApps?: string[];
226
+
227
+ /** Customize the foreground service notification. Android only. */
228
+ notification?: NotificationOptions;
229
+
230
+ /** Bounded auto-reconnect policy. */
231
+ reconnect?: ReconnectOptions;
232
+ };
233
+
234
+ type NotificationOptions = {
235
+ title?: string;
236
+ text?: string;
237
+ smallIcon?: string; // resource name (without extension) of a drawable
238
+ };
239
+
240
+ type ReconnectOptions = {
241
+ maxRetries?: number; // default 5
242
+ baseDelayMs?: number; // default 1000
243
+ maxDelayMs?: number; // default 60000
244
+ };
245
+ ```
246
+
247
+ ### Events
248
+
249
+ `client.on(event, listener)` — listener signatures:
250
+
251
+ | Event | Payload | When |
252
+ | --- | --- | --- |
253
+ | `state` | `VPNState` | Tunnel state transitions: `'idle'` → `'connecting'` → `'connected'`. Drops emit `'reconnecting'` first, then `'disconnected'` only after retries are exhausted. |
254
+ | `stats` | `{ bytesIn: number; bytesOut: number; durationMs: number }` | Throttled bandwidth counters (every ~1s on Android, ~3s on iOS). |
255
+ | `log` | `string` | Single log line from the upstream OpenVPN engine. Verbose — only attach when debugging. |
256
+ | `error` | `OpenVPNError` | Recoverable or fatal errors. See [Errors](#errors). |
257
+ | `reconnecting` | `{ attempt: number; delayMs: number }` | Fires per retry attempt while the scheduler is active. |
258
+
259
+ ```ts
260
+ type VPNState =
261
+ | 'idle'
262
+ | 'connecting'
263
+ | 'connected'
264
+ | 'reconnecting'
265
+ | 'disconnecting'
266
+ | 'disconnected';
267
+ ```
268
+
269
+ ### Types
270
+
271
+ ```ts
272
+ type Status = {
273
+ state: VPNState;
274
+ /** ms since epoch when 'connected' was first reached (this session). */
275
+ connectedSince?: number;
276
+ /** Server hostname or IP currently in use. */
277
+ server?: string;
278
+ localIp?: string;
279
+ remoteIp?: string;
280
+ };
281
+
282
+ type Stats = {
283
+ bytesIn: number;
284
+ bytesOut: number;
285
+ durationMs: number;
286
+ };
287
+ ```
288
+
289
+ ### Errors
290
+
291
+ ```ts
292
+ import { OpenVPNError, ERROR_CODES, type ErrorCode } from 'react-native-ovpn';
293
+ ```
294
+
295
+ `ErrorCode` is a union of:
296
+
297
+ | Code | Meaning | Recoverable? |
298
+ | --- | --- | --- |
299
+ | `AUTH_FAILED` | Bad username/password, expired cert, blocked account. | ❌ |
300
+ | `TLS_HANDSHAKE_FAILED` | Server cert mismatch or untrusted CA. | ❌ |
301
+ | `CONNECTION_REFUSED` | Server rejected the connection (capacity, IP ban). | ⚠️ retry maybe |
302
+ | `CONNECTION_TIMEOUT` | Couldn't reach the server. | ⚠️ retry |
303
+ | `DNS_RESOLUTION_FAILED` | Couldn't resolve the `remote` hostname. | ⚠️ retry |
304
+ | `PERMISSION_DENIED` | User declined the VPN system dialog. | ❌ (re-call `requestPermission`) |
305
+ | `MALFORMED_CONFIG` | The `.ovpn` couldn't be parsed. | ❌ |
306
+ | `RECONNECT_EXHAUSTED` | All retry attempts failed. | ❌ |
307
+ | `NATIVE_ERROR` | Anything else surfaced from the native engine. | depends |
308
+
309
+ `HARD_ERROR_CODES` lists the non-recoverable codes. The client uses this set internally to stop the auto-reconnect loop on auth-class failures.
310
+
311
+ ---
312
+
313
+ ## Recipes
69
314
 
70
- See [OpenVPN config support](docs/ovpn-support.md) for the full directive-level matrix.
315
+ ### Kill switch
316
+
317
+ Blocks all traffic when the tunnel drops, so your app never accidentally leaks plain-text packets. **Android only** — iOS doesn't expose this to non-Apple VPN apps.
318
+
319
+ ```ts
320
+ await client.connect({
321
+ config: ovpn,
322
+ username: 'alice',
323
+ password: 's3cret',
324
+ killSwitch: true,
325
+ });
326
+ ```
327
+
328
+ ### Custom DNS
329
+
330
+ Override DNS servers inside the tunnel (e.g. force Cloudflare's 1.1.1.1 instead of the server's defaults):
331
+
332
+ ```ts
333
+ await client.connect({
334
+ config: ovpn,
335
+ username: '',
336
+ password: '',
337
+ dns: ['1.1.1.1', '1.0.0.1'],
338
+ });
339
+ ```
340
+
341
+ ### Per-app routing (disallowed/allowed apps)
342
+
343
+ Tunnel only your app:
344
+
345
+ ```ts
346
+ await client.connect({
347
+ config: ovpn,
348
+ username: '',
349
+ password: '',
350
+ allowedApps: ['com.your.app'],
351
+ });
352
+ ```
353
+
354
+ Or tunnel everything **except** Spotify (it has its own region locks):
355
+
356
+ ```ts
357
+ await client.connect({
358
+ // ...
359
+ disallowedApps: ['com.spotify.music'],
360
+ });
361
+ ```
362
+
363
+ ### Handling auto-reconnect
364
+
365
+ By default the client retries 5 times with exponential backoff (1s → 2s → 4s → … capped at 60s). Bump it for flaky networks:
366
+
367
+ ```ts
368
+ client.on('reconnecting', ({ attempt, delayMs }) => {
369
+ console.log(`reconnect ${attempt} in ${delayMs}ms`);
370
+ });
371
+
372
+ await client.connect({
373
+ config: ovpn,
374
+ username: '',
375
+ password: '',
376
+ reconnect: { maxRetries: 10, baseDelayMs: 500, maxDelayMs: 30_000 },
377
+ });
378
+ ```
379
+
380
+ When all retries fail you'll get `state: 'disconnected'` + an `error` with code `RECONNECT_EXHAUSTED`.
381
+
382
+ ### Reading live bandwidth stats
383
+
384
+ ```ts
385
+ const [stats, setStats] = useState({ bytesIn: 0, bytesOut: 0 });
386
+
387
+ useEffect(() => {
388
+ const handler = (s) => setStats(s);
389
+ client.on('stats', handler);
390
+ return () => client.off('stats', handler);
391
+ }, []);
392
+ ```
393
+
394
+ ### Customizing the foreground notification
395
+
396
+ Android requires a sticky notification while the VPN service runs.
397
+
398
+ ```ts
399
+ await client.connect({
400
+ // ...
401
+ notification: {
402
+ title: 'My App VPN',
403
+ text: 'Connected to United States',
404
+ smallIcon: 'notification_icon', // res/drawable/notification_icon.png
405
+ },
406
+ });
407
+ ```
408
+
409
+ To change the notification channel name, configure it once in the Expo plugin (`androidNotificationChannelName`) — it ships in the manifest at prebuild time.
410
+
411
+ ---
412
+
413
+ ## OpenVPN config support
414
+
415
+ The library passes your `.ovpn` to the upstream engine as-is. Most directives work. Known-good and known-bad below.
416
+
417
+ ### ✅ Supported directives
418
+
419
+ `client`, `dev tun`, `proto tcp|udp`, `remote`, `resolv-retry`, `nobind`, `persist-key`, `persist-tun`, `remote-cert-tls`, `auth-user-pass`, `cipher`, `data-ciphers`, `auth`, `verb`, `mute`, `keepalive`, `<ca>...</ca>`, `<cert>...</cert>`, `<key>...</key>`, `<tls-auth>...</tls-auth>`, `<tls-crypt>...</tls-crypt>`, `<tls-crypt-v2>...</tls-crypt-v2>`, `tls-cipher`, `comp-lzo`, `compress`, `redirect-gateway`, `route`, `dhcp-option DNS`, `script-security` (parsed but ignored — see below).
420
+
421
+ **Legacy ciphers (AES-128-CBC, AES-256-CBC):** The library auto-injects
422
+ `data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305:AES-256-CBC:AES-128-CBC`
423
+ and `data-ciphers-fallback AES-128-CBC` if your `.ovpn` doesn't specify
424
+ them, so VPN Gate / SoftEther / older corporate servers negotiate cleanly.
425
+
426
+ ### ⚠️ Caveats
427
+
428
+ | Directive | Behavior |
429
+ | --- | --- |
430
+ | `dev tap` | Not supported — Layer-2 tunneling isn't available on Android/iOS. |
431
+ | `script-security`, `up`, `down` | Stripped at runtime — sandboxed platforms don't allow shell hooks. |
432
+ | `management` | Stripped — the engine speaks management to the wrapper internally. |
433
+ | `pkcs12` | Inline `<ca>/<cert>/<key>` works; PKCS#12 file paths don't. Extract to PEM first. |
434
+ | `--config /path/to/file.ovpn` | N/A — pass the contents as a string in `connect({ config })`. |
435
+
436
+ ### ❌ Not supported
437
+
438
+ `fragment`, `mssfix N`, `route-method`, `dhcp-renew`, `pull-filter`, `route-noexec`, `client-cert-not-required` (deprecated), `route-delay` (silently ignored).
439
+
440
+ ---
441
+
442
+ ## Troubleshooting
443
+
444
+ ### `RESOLVE: Cannot resolve host address: <hostname>`
445
+
446
+ DNS is failing inside the tunnel pre-handshake. Common causes:
447
+
448
+ - Server is offline (VPN Gate / public servers rotate every few hours)
449
+ - Phone's underlying internet is down — try opening a browser
450
+ - Custom DNS in `connect({ dns })` is wrong — drop it temporarily
451
+
452
+ ### `TLS Error: TLS handshake failed`
453
+
454
+ Your `.ovpn`'s embedded CA cert doesn't match the server's. Re-download the
455
+ `.ovpn` from your provider — server certs rotate.
456
+
457
+ ### `AUTH_FAILED` after a known-good username/password
458
+
459
+ - Some providers issue a separate VPN password (not your dashboard login). Check the provider's docs.
460
+ - 2FA-protected accounts need an *application password* that pre-applies the OTP.
461
+
462
+ ### The tunnel connects, but no traffic flows
463
+
464
+ - `redirect-gateway def1 bypass-dhcp` should be in the `.ovpn` — without it, only `route` directives get installed.
465
+ - On Android, check Settings → Connections → More connection settings → VPN — your app should appear as the active VPN.
466
+
467
+ ### Android: app killed but tunnel stays running, then UI shows "Not Connected"
468
+
469
+ The JS process can die while the `VpnService` (a foreground service) stays alive. Call `getStatus()` on app reopen to reconcile:
470
+
471
+ ```ts
472
+ useEffect(() => {
473
+ client.getStatus().then((s) => {
474
+ if (s.state === 'connected') {
475
+ // update UI to connected state
476
+ }
477
+ });
478
+ }, []);
479
+ ```
480
+
481
+ ### Android: Samsung / Xiaomi / OPPO killing the service after ~5 minutes
482
+
483
+ These OEMs aggressively kill background services. Have your app prompt for **Battery optimization exemption**:
484
+
485
+ ```ts
486
+ import * as IntentLauncher from 'expo-intent-launcher';
487
+ import { Platform } from 'react-native';
488
+
489
+ if (Platform.OS === 'android') {
490
+ await IntentLauncher.startActivityAsync(
491
+ 'android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS',
492
+ { data: `package:${YOUR_PACKAGE_ID}` }
493
+ );
494
+ }
495
+ ```
496
+
497
+ You must also declare the permission in your manifest (Expo: add to `app.config.js` `android.permissions: ['REQUEST_IGNORE_BATTERY_OPTIMIZATIONS']`).
498
+
499
+ ### iOS: extension fails to load, app reports `PERMISSION_DENIED`
500
+
501
+ - App Group entitlement is missing on **one** of the two targets (host or extension). Both need it, with the same group identifier.
502
+ - The PacketTunnel target's bundle id must start with the host app's bundle id (`com.example.app.OpenVPNTunnel` if host is `com.example.app`).
503
+
504
+ ---
505
+
506
+ ## How it works
507
+
508
+ - **Android**: A thin Kotlin wrapper around [ics-openvpn](https://github.com/schwabe/ics-openvpn) (vendored as a prebuilt AAR). Tunneling runs in a `VpnService` (`OpenvpnService`). The wrapper translates `connect()` parameters into the engine's `ProfileBuilder`, then forwards engine state callbacks back over a TurboModule event emitter.
509
+
510
+ - **iOS**: Uses [OpenVPNAdapter](https://github.com/ss-abramchuk/OpenVPNAdapter) running in a Packet Tunnel Provider extension. The host app talks to the extension via `NETunnelProviderManager`. App Group shared user defaults pass state events between the two processes.
511
+
512
+ - **Auto-reconnect**: A pure-JS scheduler (`Scheduler` class) drives retries with exponential backoff. The state event collapses `disconnected → reconnecting` in the same JS tick so the UI never paints "Not Connected" between retries.
513
+
514
+ - **Codegen**: Native specs in `src/NativeOpenvpn.ts` produce both old-architecture and Fabric-compatible bindings via React Native's codegen.
515
+
516
+ ---
71
517
 
72
518
  ## License
73
519
 
74
- Wrapper code: MIT.
520
+ Wrapper code (this package): **MIT**.
521
+
522
+ **Important — copyleft inheritance:** This library embeds two upstream
523
+ projects that ship under copyleft licenses:
75
524
 
76
- **Important** — Android embeds [ics-openvpn](https://github.com/schwabe/ics-openvpn) (**GPLv2**), iOS embeds [OpenVPNAdapter](https://github.com/ss-abramchuk/OpenVPNAdapter) (**AGPLv3**). Apps that ship `react-native-ovpn` inherit those copyleft obligations. Fine for personal, internal, or open-source apps; **not compatible** with closed-source commercial distribution without separate commercial agreements with the upstream projects.
525
+ - **Android**: [ics-openvpn](https://github.com/schwabe/ics-openvpn) **GPL-2.0**
526
+ - **iOS**: [OpenVPNAdapter](https://github.com/ss-abramchuk/OpenVPNAdapter) → **AGPL-3.0**
527
+
528
+ Any app that ships `react-native-ovpn` inherits those obligations:
529
+
530
+ - ✅ Personal projects, internal-only apps, open-source apps → fine
531
+ - ✅ Compliance for GPL/AGPL → publish your app's source under a
532
+ GPL/AGPL-compatible license, or
533
+ - ❌ Closed-source commercial distribution → **not legal** without
534
+ separate commercial agreements with the upstream maintainers (Arne Schwabe
535
+ for ics-openvpn, Sergey Abramchuk for OpenVPNAdapter)
536
+
537
+ Use accordingly. See `LICENSE` in the package root for the MIT text covering
538
+ this library's own code.
539
+
540
+ ---
77
541
 
78
542
  ## Contributing
79
543
 
80
- [Development workflow](CONTRIBUTING.md) · [Code of conduct](CODE_OF_CONDUCT.md)
544
+ Issues and PRs welcome. Please open an issue first for substantial changes.
545
+
546
+ Maintained by [@Raselj71](https://github.com/Raselj71).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-ovpn",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "OpenVPN client for React Native — Android, iOS, and Expo. New Architecture (TurboModule), kill switch, custom DNS, bounded auto-reconnect.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",