Android Support (#166)

1. Add vpnservice tauri plugin for android.
2. add workflow for android.
3. Easytier Core support android, allow set tun fd.
This commit is contained in:
Sijie.Sun
2024-07-15 00:03:55 +08:00
committed by GitHub
parent 4938e3ed2b
commit 858ade2eee
113 changed files with 3847 additions and 537 deletions
+32
View File
@@ -0,0 +1,32 @@
name: Audit
on:
schedule:
- cron: '0 0 * * *'
push:
branches:
- main
paths:
- ".github/workflows/audit.yml"
- "**/Cargo.lock"
- "**/Cargo.toml"
pull_request:
branches:
- main
paths:
- ".github/workflows/audit.yml"
- "**/Cargo.lock"
- "**/Cargo.toml"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: rustsec/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
+53
View File
@@ -0,0 +1,53 @@
name: Check
on:
push:
branches:
- main
paths:
- ".github/workflows/check.yml"
- "**/*.rs"
- "**/Cargo.toml"
pull_request:
branches:
- main
paths:
- ".github/workflows/check.yml"
- "**/*.rs"
- "**/Cargo.toml"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
fmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all -- --check
clippy:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: install webkit2gtk
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y webkit2gtk-4.1
- uses: Swatinem/rust-cache@v2
- run: cargo clippy --all-targets --all-features -- -D warnings
+33
View File
@@ -0,0 +1,33 @@
name: Test
on:
push:
branches:
- main
pull_request:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: install webkit2gtk
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y webkit2gtk-4.1
- uses: Swatinem/rust-cache@v2
- run: cargo test --all-targets --all-features -- -D warnings
+17
View File
@@ -0,0 +1,17 @@
/.vs
.DS_Store
.Thumbs.db
*.sublime*
.idea/
debug.log
package-lock.json
.vscode/settings.json
yarn.lock
/.tauri
/target
Cargo.lock
node_modules/
dist-js
dist
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "tauri-plugin-vpnservice"
version = "0.0.0"
authors = [ "You" ]
description = ""
edition = "2021"
rust-version = "1.70"
exclude = ["/examples", "/webview-dist", "/webview-src", "/node_modules"]
links = "tauri-plugin-vpnservice"
[dependencies]
tauri = { version = "2.0.0-beta.23" }
serde = "1.0"
thiserror = "1.0"
[build-dependencies]
tauri-plugin = { version = "2.0.0-beta.18", features = ["build"] }
+1
View File
@@ -0,0 +1 @@
# Tauri Plugin vpnservice
@@ -0,0 +1,2 @@
/build
/.tauri
@@ -0,0 +1,45 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.plugin.vpnservice"
compileSdk = 34
defaultConfig {
minSdk = 21
targetSdk = 34
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.6.0")
implementation("com.google.android.material:material:1.7.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
implementation(project(":tauri-android"))
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,2 @@
include ':tauri-android'
project(':tauri-android').projectDir = new File('./.tauri/tauri-api')
@@ -0,0 +1,24 @@
package com.plugin.vpnservice
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.plugin.vpnservice", appContext.packageName)
}
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
@@ -0,0 +1,10 @@
package com.plugin.vpnservice
import android.util.Log
class Example {
fun pong(value: String): String {
Log.i("Pong", value)
return value
}
}
@@ -0,0 +1,115 @@
package com.plugin.vpnservice
import android.content.Intent
import android.net.VpnService
import android.net.IpPrefix
import android.os.Build
import android.os.ParcelFileDescriptor
import android.os.Bundle
import java.net.InetAddress
import java.util.Arrays
import app.tauri.plugin.JSObject
fun stringToIpPrefix(ipPrefixString: String): IpPrefix {
val parts = ipPrefixString.split("/")
if (parts.size != 2) throw IllegalArgumentException("Invalid IP prefix string")
val address = InetAddress.getByName(parts[0])
val prefixLength = parts[1].toInt()
return IpPrefix(address, prefixLength)
}
class TauriVpnService : VpnService() {
companion object {
@JvmField var triggerCallback: (String, JSObject) -> Unit = { _, _ -> }
@JvmField var self: TauriVpnService? = null
const val IPV4_ADDR = "IPV4_ADDR"
const val ROUTES = "ROUTES"
const val DNS = "DNS"
const val DISALLOWED_APPLICATIONS = "DISALLOWED_APPLICATIONS"
const val MTU = "MTU"
}
private lateinit var vpnInterface: ParcelFileDescriptor
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
println("vpn on start command ${intent?.getExtras()} $intent")
var args = intent?.getExtras()
vpnInterface = createVpnInterface(args)
println("vpn created ${vpnInterface.fd}")
var event_data = JSObject()
event_data.put("fd", vpnInterface.fd)
triggerCallback("vpn_service_start", event_data)
return START_STICKY
}
override fun onCreate() {
super.onCreate()
self = this
println("vpn on create")
}
override fun onDestroy() {
println("vpn on destroy")
self = null
super.onDestroy()
disconnect()
}
override fun onRevoke() {
println("vpn on revoke")
self = null
super.onRevoke()
disconnect()
}
private fun disconnect() {
triggerCallback("vpn_service_stop", JSObject())
vpnInterface.close()
}
private fun createVpnInterface(args: Bundle?): ParcelFileDescriptor {
var builder = Builder()
.setSession("TauriVpnService")
.setBlocking(false)
var mtu = args?.getInt(MTU) ?: 1500
var ipv4Addr = args?.getString(IPV4_ADDR) ?: "10.126.126.1/24"
var dns = args?.getString(DNS) ?: "114.114.114.114"
var routes = args?.getStringArray(ROUTES) ?: emptyArray()
var disallowedApplications = args?.getStringArray(DISALLOWED_APPLICATIONS) ?: emptyArray()
println("vpn create vpn interface. mtu: $mtu, ipv4Addr: $ipv4Addr, dns:" +
"$dns, routes: ${java.util.Arrays.toString(routes)}," +
"disallowedApplications: ${java.util.Arrays.toString(disallowedApplications)}")
val ipParts = ipv4Addr.split("/")
if (ipParts.size != 2) throw IllegalArgumentException("Invalid IP addr string")
builder.addAddress(ipParts[0], ipParts[1].toInt())
builder.setMtu(mtu)
builder.addDnsServer(dns)
for (route in routes) {
builder.addRoute(stringToIpPrefix(route))
}
for (app in disallowedApplications) {
builder.addDisallowedApplication(app)
}
return builder.also {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
it.setMetered(false)
}
}
.establish()
?: throw IllegalStateException("Failed to init VpnService")
}
}
@@ -0,0 +1,93 @@
package com.plugin.vpnservice
import android.app.Activity
import android.content.Intent
import android.net.VpnService
import app.tauri.annotation.Command
import app.tauri.annotation.InvokeArg
import app.tauri.annotation.TauriPlugin
import app.tauri.plugin.Invoke
import app.tauri.plugin.JSObject
import app.tauri.plugin.Plugin
import android.webkit.WebView
@InvokeArg
class PingArgs {
var value: String? = null
}
@InvokeArg
class StartVpnArgs {
var ipv4Addr: String? = null
var routes: Array<String> = emptyArray()
var dns: String? = null
var disallowedApplications: Array<String> = emptyArray()
var mtu: Int? = null
}
@TauriPlugin
class VpnServicePlugin(private val activity: Activity) : Plugin(activity) {
private val implementation = Example()
override fun load(webView: WebView) {
println("load vpn service plugin")
TauriVpnService.triggerCallback = { event, data ->
println("vpn: triggerCallback $event $data")
trigger(event, data)
}
}
@Command
fun ping(invoke: Invoke) {
val args = invoke.parseArgs(PingArgs::class.java)
val ret = JSObject()
ret.put("value", implementation.pong(args.value ?: "default value :("))
invoke.resolve(ret)
}
@Command
fun prepareVpn(invoke: Invoke) {
println("prepare vpn in plugin")
val it = VpnService.prepare(activity)
var ret = JSObject()
if (it != null) {
activity.startActivityForResult(it, 0x0f)
ret.put("errorMsg", "again")
}
invoke.resolve(ret)
}
@Command
fun startVpn(invoke: Invoke) {
val args = invoke.parseArgs(StartVpnArgs::class.java)
println("start vpn in plugin, args: $args")
TauriVpnService.self?.onRevoke()
val it = VpnService.prepare(activity)
var ret = JSObject()
if (it != null) {
ret.put("errorMsg", "need_prepare")
} else {
var intent = Intent(activity, TauriVpnService::class.java)
intent.putExtra(TauriVpnService.IPV4_ADDR, args.ipv4Addr)
intent.putExtra(TauriVpnService.ROUTES, args.routes)
intent.putExtra(TauriVpnService.DNS, args.dns)
intent.putExtra(TauriVpnService.DISALLOWED_APPLICATIONS, args.disallowedApplications)
intent.putExtra(TauriVpnService.MTU, args.mtu)
activity.startService(intent)
}
invoke.resolve(ret)
}
@Command
fun stopVpn(invoke: Invoke) {
println("stop vpn in plugin")
TauriVpnService.self?.onRevoke()
activity.stopService(Intent(activity, TauriVpnService::class.java))
println("stop vpn in plugin end")
invoke.resolve(JSObject())
}
}
@@ -0,0 +1,17 @@
package com.plugin.vpnservice
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
+14
View File
@@ -0,0 +1,14 @@
const COMMANDS: &[&str] = &[
"ping",
"prepare_vpn",
"start_vpn",
"stop_vpn",
"register_listener",
];
fn main() {
tauri_plugin::Builder::new(COMMANDS)
.android_path("android")
.ios_path("ios")
.build();
}
+35
View File
@@ -0,0 +1,35 @@
import { invoke } from '@tauri-apps/api/core'
export async function ping(value: string): Promise<string | null> {
return await invoke<{ value?: string }>('plugin:vpnservice|ping', {
payload: {
value,
},
}).then((r) => (r.value ? r.value : null));
}
export interface InvokeResponse {
errorMsg?: string;
}
export interface StartVpnRequest {
ipv4Addr?: string;
routes?: string[];
dns?: string;
disallowedApplications?: string[];
mtu?: number;
}
export async function prepare_vpn(): Promise<InvokeResponse | null> {
return await invoke<InvokeResponse>('plugin:vpnservice|prepare_vpn', {})
}
export async function start_vpn(request: StartVpnRequest): Promise<InvokeResponse | null> {
return await invoke<InvokeResponse>('plugin:vpnservice|start_vpn', {
...request,
})
}
export async function stop_vpn(): Promise<InvokeResponse | null> {
return await invoke<InvokeResponse>('plugin:vpnservice|stop_vpn', {})
}
+10
View File
@@ -0,0 +1,10 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
Package.resolved
+31
View File
@@ -0,0 +1,31 @@
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "tauri-plugin-vpnservice",
platforms: [
.iOS(.v13),
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "tauri-plugin-vpnservice",
type: .static,
targets: ["tauri-plugin-vpnservice"]),
],
dependencies: [
.package(name: "Tauri", path: "../.tauri/tauri-api")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "tauri-plugin-vpnservice",
dependencies: [
.byName(name: "Tauri")
],
path: "Sources")
]
)
+3
View File
@@ -0,0 +1,3 @@
# Tauri Plugin vpnservice
A description of this package.
@@ -0,0 +1,20 @@
import SwiftRs
import Tauri
import UIKit
import WebKit
class PingArgs: Decodable {
let value: String?
}
class ExamplePlugin: Plugin {
@objc public func ping(_ invoke: Invoke) throws {
let args = try invoke.parseArgs(PingArgs.self)
invoke.resolve(["value": args.value ?? ""])
}
}
@_cdecl("init_plugin_vpnservice")
func initPlugin() -> Plugin {
return ExamplePlugin()
}
@@ -0,0 +1,8 @@
import XCTest
@testable import ExamplePlugin
final class ExamplePluginTests: XCTestCase {
func testExample() throws {
let plugin = ExamplePlugin()
}
}
+33
View File
@@ -0,0 +1,33 @@
{
"name": "tauri-plugin-vpnservice-api",
"version": "0.0.0",
"author": "You",
"description": "",
"type": "module",
"types": "./dist-js/index.d.ts",
"main": "./dist-js/index.cjs",
"module": "./dist-js/index.js",
"exports": {
"types": "./dist-js/index.d.ts",
"import": "./dist-js/index.js",
"require": "./dist-js/index.cjs"
},
"files": [
"dist-js",
"README.md"
],
"scripts": {
"build": "rollup -c",
"prepublishOnly": "yarn build",
"pretest": "yarn build"
},
"dependencies": {
"@tauri-apps/api": ">=2.0.0-beta.6"
},
"devDependencies": {
"@rollup/plugin-typescript": "^11.1.6",
"rollup": "^4.9.6",
"typescript": "^5.3.3",
"tslib": "^2.6.2"
}
}
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-ping"
description = "Enables the ping command without any pre-configured scope."
commands.allow = ["ping"]
[[permission]]
identifier = "deny-ping"
description = "Denies the ping command without any pre-configured scope."
commands.deny = ["ping"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-prepare-vpn"
description = "Enables the prepare_vpn command without any pre-configured scope."
commands.allow = ["prepare_vpn"]
[[permission]]
identifier = "deny-prepare-vpn"
description = "Denies the prepare_vpn command without any pre-configured scope."
commands.deny = ["prepare_vpn"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-register-listener"
description = "Enables the register_listener command without any pre-configured scope."
commands.allow = ["register_listener"]
[[permission]]
identifier = "deny-register-listener"
description = "Denies the register_listener command without any pre-configured scope."
commands.deny = ["register_listener"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-start-vpn"
description = "Enables the start_vpn command without any pre-configured scope."
commands.allow = ["start_vpn"]
[[permission]]
identifier = "deny-start-vpn"
description = "Denies the start_vpn command without any pre-configured scope."
commands.deny = ["start_vpn"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-stop-vpn"
description = "Enables the stop_vpn command without any pre-configured scope."
commands.allow = ["stop_vpn"]
[[permission]]
identifier = "deny-stop-vpn"
description = "Denies the stop_vpn command without any pre-configured scope."
commands.deny = ["stop_vpn"]
@@ -0,0 +1,146 @@
## Default Permission
Default permissions for the plugin
- `allow-ping`
- `allow-start-vpn`
### Permission Table
<table>
<tr>
<th>Identifier</th>
<th>Description</th>
</tr>
<tr>
<td>
`vpnservice:allow-ping`
</td>
<td>
Enables the ping command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`vpnservice:deny-ping`
</td>
<td>
Denies the ping command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`vpnservice:allow-prepare-vpn`
</td>
<td>
Enables the prepare_vpn command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`vpnservice:deny-prepare-vpn`
</td>
<td>
Denies the prepare_vpn command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`vpnservice:allow-register-listener`
</td>
<td>
Enables the register_listener command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`vpnservice:deny-register-listener`
</td>
<td>
Denies the register_listener command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`vpnservice:allow-start-vpn`
</td>
<td>
Enables the start_vpn command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`vpnservice:deny-start-vpn`
</td>
<td>
Denies the start_vpn command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`vpnservice:allow-stop-vpn`
</td>
<td>
Enables the stop_vpn command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`vpnservice:deny-stop-vpn`
</td>
<td>
Denies the stop_vpn command without any pre-configured scope.
</td>
</tr>
</table>
@@ -0,0 +1,3 @@
[default]
description = "Default permissions for the plugin"
permissions = ["allow-ping", "allow-start-vpn"]
@@ -0,0 +1,377 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PermissionFile",
"description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.",
"type": "object",
"properties": {
"default": {
"description": "The default permission set for the plugin",
"anyOf": [
{
"$ref": "#/definitions/DefaultPermission"
},
{
"type": "null"
}
]
},
"set": {
"description": "A list of permissions sets defined",
"type": "array",
"items": {
"$ref": "#/definitions/PermissionSet"
}
},
"permission": {
"description": "A list of inlined permissions",
"default": [],
"type": "array",
"items": {
"$ref": "#/definitions/Permission"
}
}
},
"definitions": {
"DefaultPermission": {
"description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.",
"type": "object",
"required": [
"permissions"
],
"properties": {
"version": {
"description": "The version of the permission.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 1.0
},
"description": {
"description": "Human-readable description of what the permission does. Tauri convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.",
"type": [
"string",
"null"
]
},
"permissions": {
"description": "All permissions this set contains.",
"type": "array",
"items": {
"type": "string"
}
}
}
},
"PermissionSet": {
"description": "A set of direct permissions grouped together under a new name.",
"type": "object",
"required": [
"description",
"identifier",
"permissions"
],
"properties": {
"identifier": {
"description": "A unique identifier for the permission.",
"type": "string"
},
"description": {
"description": "Human-readable description of what the permission does.",
"type": "string"
},
"permissions": {
"description": "All permissions this set contains.",
"type": "array",
"items": {
"$ref": "#/definitions/PermissionKind"
}
}
}
},
"Permission": {
"description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.",
"type": "object",
"required": [
"identifier"
],
"properties": {
"version": {
"description": "The version of the permission.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 1.0
},
"identifier": {
"description": "A unique identifier for the permission.",
"type": "string"
},
"description": {
"description": "Human-readable description of what the permission does. Tauri internal convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.",
"type": [
"string",
"null"
]
},
"commands": {
"description": "Allowed or denied commands when using this permission.",
"default": {
"allow": [],
"deny": []
},
"allOf": [
{
"$ref": "#/definitions/Commands"
}
]
},
"scope": {
"description": "Allowed or denied scoped when using this permission.",
"allOf": [
{
"$ref": "#/definitions/Scopes"
}
]
},
"platforms": {
"description": "Target platforms this permission applies. By default all platforms are affected by this permission.",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/Target"
}
}
}
},
"Commands": {
"description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.",
"type": "object",
"properties": {
"allow": {
"description": "Allowed command.",
"default": [],
"type": "array",
"items": {
"type": "string"
}
},
"deny": {
"description": "Denied command, which takes priority.",
"default": [],
"type": "array",
"items": {
"type": "string"
}
}
}
},
"Scopes": {
"description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```",
"type": "object",
"properties": {
"allow": {
"description": "Data that defines what is allowed by the scope.",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/Value"
}
},
"deny": {
"description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/Value"
}
}
}
},
"Value": {
"description": "All supported ACL values.",
"anyOf": [
{
"description": "Represents a null JSON value.",
"type": "null"
},
{
"description": "Represents a [`bool`].",
"type": "boolean"
},
{
"description": "Represents a valid ACL [`Number`].",
"allOf": [
{
"$ref": "#/definitions/Number"
}
]
},
{
"description": "Represents a [`String`].",
"type": "string"
},
{
"description": "Represents a list of other [`Value`]s.",
"type": "array",
"items": {
"$ref": "#/definitions/Value"
}
},
{
"description": "Represents a map of [`String`] keys to [`Value`]s.",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/Value"
}
}
]
},
"Number": {
"description": "A valid ACL number.",
"anyOf": [
{
"description": "Represents an [`i64`].",
"type": "integer",
"format": "int64"
},
{
"description": "Represents a [`f64`].",
"type": "number",
"format": "double"
}
]
},
"Target": {
"description": "Platform target.",
"oneOf": [
{
"description": "MacOS.",
"type": "string",
"enum": [
"macOS"
]
},
{
"description": "Windows.",
"type": "string",
"enum": [
"windows"
]
},
{
"description": "Linux.",
"type": "string",
"enum": [
"linux"
]
},
{
"description": "Android.",
"type": "string",
"enum": [
"android"
]
},
{
"description": "iOS.",
"type": "string",
"enum": [
"iOS"
]
}
]
},
"PermissionKind": {
"type": "string",
"oneOf": [
{
"description": "allow-ping -> Enables the ping command without any pre-configured scope.",
"type": "string",
"enum": [
"allow-ping"
]
},
{
"description": "deny-ping -> Denies the ping command without any pre-configured scope.",
"type": "string",
"enum": [
"deny-ping"
]
},
{
"description": "allow-prepare-vpn -> Enables the prepare_vpn command without any pre-configured scope.",
"type": "string",
"enum": [
"allow-prepare-vpn"
]
},
{
"description": "deny-prepare-vpn -> Denies the prepare_vpn command without any pre-configured scope.",
"type": "string",
"enum": [
"deny-prepare-vpn"
]
},
{
"description": "allow-register-listener -> Enables the register_listener command without any pre-configured scope.",
"type": "string",
"enum": [
"allow-register-listener"
]
},
{
"description": "deny-register-listener -> Denies the register_listener command without any pre-configured scope.",
"type": "string",
"enum": [
"deny-register-listener"
]
},
{
"description": "allow-start-vpn -> Enables the start_vpn command without any pre-configured scope.",
"type": "string",
"enum": [
"allow-start-vpn"
]
},
{
"description": "deny-start-vpn -> Denies the start_vpn command without any pre-configured scope.",
"type": "string",
"enum": [
"deny-start-vpn"
]
},
{
"description": "allow-stop-vpn -> Enables the stop_vpn command without any pre-configured scope.",
"type": "string",
"enum": [
"allow-stop-vpn"
]
},
{
"description": "deny-stop-vpn -> Denies the stop_vpn command without any pre-configured scope.",
"type": "string",
"enum": [
"deny-stop-vpn"
]
},
{
"description": "default -> Default permissions for the plugin",
"type": "string",
"enum": [
"default"
]
}
]
}
}
}
+308
View File
@@ -0,0 +1,308 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
'@tauri-apps/api':
specifier: '>=2.0.0-beta.6'
version: 2.0.0-beta.14
devDependencies:
'@rollup/plugin-typescript':
specifier: ^11.1.6
version: 11.1.6(rollup@4.18.1)(tslib@2.6.3)(typescript@5.5.3)
rollup:
specifier: ^4.9.6
version: 4.18.1
tslib:
specifier: ^2.6.2
version: 2.6.3
typescript:
specifier: ^5.3.3
version: 5.5.3
packages:
'@rollup/plugin-typescript@11.1.6':
resolution: {integrity: sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^2.14.0||^3.0.0||^4.0.0
tslib: '*'
typescript: '>=3.7.0'
peerDependenciesMeta:
rollup:
optional: true
tslib:
optional: true
'@rollup/pluginutils@5.1.0':
resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
'@rollup/rollup-android-arm-eabi@4.18.1':
resolution: {integrity: sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.18.1':
resolution: {integrity: sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.18.1':
resolution: {integrity: sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.18.1':
resolution: {integrity: sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-linux-arm-gnueabihf@4.18.1':
resolution: {integrity: sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm-musleabihf@4.18.1':
resolution: {integrity: sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm64-gnu@4.18.1':
resolution: {integrity: sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-arm64-musl@4.18.1':
resolution: {integrity: sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-powerpc64le-gnu@4.18.1':
resolution: {integrity: sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==}
cpu: [ppc64]
os: [linux]
'@rollup/rollup-linux-riscv64-gnu@4.18.1':
resolution: {integrity: sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-s390x-gnu@4.18.1':
resolution: {integrity: sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==}
cpu: [s390x]
os: [linux]
'@rollup/rollup-linux-x64-gnu@4.18.1':
resolution: {integrity: sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==}
cpu: [x64]
os: [linux]
'@rollup/rollup-linux-x64-musl@4.18.1':
resolution: {integrity: sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==}
cpu: [x64]
os: [linux]
'@rollup/rollup-win32-arm64-msvc@4.18.1':
resolution: {integrity: sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.18.1':
resolution: {integrity: sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.18.1':
resolution: {integrity: sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==}
cpu: [x64]
os: [win32]
'@tauri-apps/api@2.0.0-beta.14':
resolution: {integrity: sha512-YLYgHqdwWswr4Y70+hRzaLD6kLIUgHhE3shLXNquPiTaQ9+cX3Q2dB0AFfqsua6NXYFNe7LfkmMzaqEzqv3yQg==}
engines: {node: '>= 18.18', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
'@types/estree@1.0.5':
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
is-core-module@2.14.0:
resolution: {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==}
engines: {node: '>= 0.4'}
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
resolve@1.22.8:
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
hasBin: true
rollup@4.18.1:
resolution: {integrity: sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
tslib@2.6.3:
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
typescript@5.5.3:
resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==}
engines: {node: '>=14.17'}
hasBin: true
snapshots:
'@rollup/plugin-typescript@11.1.6(rollup@4.18.1)(tslib@2.6.3)(typescript@5.5.3)':
dependencies:
'@rollup/pluginutils': 5.1.0(rollup@4.18.1)
resolve: 1.22.8
typescript: 5.5.3
optionalDependencies:
rollup: 4.18.1
tslib: 2.6.3
'@rollup/pluginutils@5.1.0(rollup@4.18.1)':
dependencies:
'@types/estree': 1.0.5
estree-walker: 2.0.2
picomatch: 2.3.1
optionalDependencies:
rollup: 4.18.1
'@rollup/rollup-android-arm-eabi@4.18.1':
optional: true
'@rollup/rollup-android-arm64@4.18.1':
optional: true
'@rollup/rollup-darwin-arm64@4.18.1':
optional: true
'@rollup/rollup-darwin-x64@4.18.1':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.18.1':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.18.1':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.18.1':
optional: true
'@rollup/rollup-linux-arm64-musl@4.18.1':
optional: true
'@rollup/rollup-linux-powerpc64le-gnu@4.18.1':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.18.1':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.18.1':
optional: true
'@rollup/rollup-linux-x64-gnu@4.18.1':
optional: true
'@rollup/rollup-linux-x64-musl@4.18.1':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.18.1':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.18.1':
optional: true
'@rollup/rollup-win32-x64-msvc@4.18.1':
optional: true
'@tauri-apps/api@2.0.0-beta.14': {}
'@types/estree@1.0.5': {}
estree-walker@2.0.2: {}
fsevents@2.3.3:
optional: true
function-bind@1.1.2: {}
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
is-core-module@2.14.0:
dependencies:
hasown: 2.0.2
path-parse@1.0.7: {}
picomatch@2.3.1: {}
resolve@1.22.8:
dependencies:
is-core-module: 2.14.0
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
rollup@4.18.1:
dependencies:
'@types/estree': 1.0.5
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.18.1
'@rollup/rollup-android-arm64': 4.18.1
'@rollup/rollup-darwin-arm64': 4.18.1
'@rollup/rollup-darwin-x64': 4.18.1
'@rollup/rollup-linux-arm-gnueabihf': 4.18.1
'@rollup/rollup-linux-arm-musleabihf': 4.18.1
'@rollup/rollup-linux-arm64-gnu': 4.18.1
'@rollup/rollup-linux-arm64-musl': 4.18.1
'@rollup/rollup-linux-powerpc64le-gnu': 4.18.1
'@rollup/rollup-linux-riscv64-gnu': 4.18.1
'@rollup/rollup-linux-s390x-gnu': 4.18.1
'@rollup/rollup-linux-x64-gnu': 4.18.1
'@rollup/rollup-linux-x64-musl': 4.18.1
'@rollup/rollup-win32-arm64-msvc': 4.18.1
'@rollup/rollup-win32-ia32-msvc': 4.18.1
'@rollup/rollup-win32-x64-msvc': 4.18.1
fsevents: 2.3.3
supports-preserve-symlinks-flag@1.0.0: {}
tslib@2.6.3: {}
typescript@5.5.3: {}
+31
View File
@@ -0,0 +1,31 @@
import { readFileSync } from 'fs'
import { join } from 'path'
import { cwd } from 'process'
import typescript from '@rollup/plugin-typescript'
const pkg = JSON.parse(readFileSync(join(cwd(), 'package.json'), 'utf8'))
export default {
input: 'guest-js/index.ts',
output: [
{
file: pkg.exports.import,
format: 'esm'
},
{
file: pkg.exports.require,
format: 'cjs'
}
],
plugins: [
typescript({
declaration: true,
declarationDir: `./${pkg.exports.import.split('/')[0]}`
})
],
external: [
/^@tauri-apps\/api/,
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {})
]
}
+21
View File
@@ -0,0 +1,21 @@
use serde::{ser::Serializer, Serialize};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Io(#[from] std::io::Error),
#[cfg(mobile)]
#[error(transparent)]
PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
}
impl Serialize for Error {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_string().as_ref())
}
}
+45
View File
@@ -0,0 +1,45 @@
use tauri::{
plugin::{Builder, TauriPlugin},
Runtime,
};
#[cfg(mobile)]
use tauri::Manager;
#[cfg(mobile)]
mod mobile;
#[cfg(mobile)]
use mobile::Vpnservice;
mod error;
mod models;
pub use error::{Error, Result};
/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the vpnservice APIs.
#[cfg(mobile)]
pub trait VpnserviceExt<R: Runtime> {
fn vpnservice(&self) -> &Vpnservice<R>;
}
#[cfg(mobile)]
impl<R: Runtime, T: Manager<R>> crate::VpnserviceExt<R> for T {
fn vpnservice(&self) -> &Vpnservice<R> {
self.state::<Vpnservice<R>>().inner()
}
}
/// Initializes the plugin.
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("vpnservice")
.setup(|_app, _api| {
#[cfg(mobile)]
{
let vpnservice = mobile::init(_app, _api)?;
_app.manage(vpnservice);
}
Ok(())
})
.build()
}
+54
View File
@@ -0,0 +1,54 @@
use serde::de::DeserializeOwned;
use tauri::{
plugin::{PluginApi, PluginHandle},
AppHandle, Runtime,
};
use crate::models::*;
#[cfg(target_os = "android")]
const PLUGIN_IDENTIFIER: &str = "com.plugin.vpnservice";
#[cfg(target_os = "ios")]
tauri::ios_plugin_binding!(init_plugin_vpnservice);
// initializes the Kotlin or Swift plugin classes
pub fn init<R: Runtime, C: DeserializeOwned>(
_app: &AppHandle<R>,
api: PluginApi<R, C>,
) -> crate::Result<Vpnservice<R>> {
#[cfg(target_os = "android")]
let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "VpnServicePlugin")?;
#[cfg(target_os = "ios")]
let handle = api.register_ios_plugin(init_plugin_vpnservice)?;
Ok(Vpnservice(handle))
}
/// Access to the vpnservice APIs.
pub struct Vpnservice<R: Runtime>(PluginHandle<R>);
impl<R: Runtime> Vpnservice<R> {
pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {
self.0
.run_mobile_plugin("ping", payload)
.map_err(Into::into)
}
pub fn prepare_vpn(&self, payload: VoidRequest) -> crate::Result<Status> {
self.0
.run_mobile_plugin("prepare_vpn", payload)
.map_err(Into::into)
}
pub fn start_vpn(&self, payload: StartVpnRequest) -> crate::Result<Status> {
self.0
.run_mobile_plugin("start_vpn", payload)
.map_err(Into::into)
}
pub fn stop_vpn(&self, payload: VoidRequest) -> crate::Result<Status> {
self.0
.run_mobile_plugin("stop_vpn", payload)
.map_err(Into::into)
}
}
+33
View File
@@ -0,0 +1,33 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PingRequest {
pub value: Option<String>,
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PingResponse {
pub value: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct VoidRequest {}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct StartVpnRequest {
pub ipv4_addr: Option<String>,
pub routes: Option<Vec<String>>,
pub dns: Option<String>,
pub disallowed_applications: Option<Vec<String>>,
pub mtu: Option<u32>,
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Status {
pub error_msg: Option<String>,
}
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es2021",
"module": "esnext",
"moduleResolution": "bundler",
"skipLibCheck": true,
"strict": true,
"noUnusedLocals": true,
"noImplicitAny": true,
"noEmit": true
},
"include": ["guest-js/*.ts"],
"exclude": ["dist-js", "node_modules"]
}