feat: sync httpdns sdk/platform updates without large binaries

This commit is contained in:
robin
2026-03-04 17:59:14 +08:00
parent 853897a6f8
commit 532891fad0
700 changed files with 6096 additions and 2712 deletions

View File

@@ -0,0 +1,21 @@
## 1.0.2
* 升级 iOS HTTPDNS SDK <20><>?3.4.0 版本
## 1.0.1
* 新增 IP 优选功<E98089><E58A9F>?
* 现有接口功能优化
* 升级 Android HTTPDNS SDK <20><>?2.6.7 版本
* 升级 iOS HTTPDNS SDK <20><>?3.3.0 版本
## 1.0.0
* 重构插件,更新接入方式和各个接口
* 集成Android HTTPDNS SDK 2.6.5版本
## 0.0.2
* 集成iOS HTTPDNS SDK 3.2.1版本
* 集成Android HTTPDNS SDK 2.6.3版本
* 修改预解析接口中requestIpType参数必传
## 0.0.1
* 集成Android HTTPDNS SDK 2.6.2版本
* 集成iOS HTTPDNS SDK 3.2.0版本

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 New Cloud
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,73 @@
# HTTPDNS Flutter SDK (SNI Hidden v1.0.0)
## 1. Initialization
```dart
import 'package:new_httpdns/new_httpdns.dart';
await NewHttpdns.init(
appId: 'app1f1ndpo9',
primaryServiceHost: 'httpdns-a.example.com',
backupServiceHost: 'httpdns-b.example.com', // optional
servicePort: 443,
secretKey: 'your-sign-secret', // optional if sign is enabled
);
await NewHttpdns.setHttpsRequestEnabled(true);
await NewHttpdns.build();
```
## 2. Resolve
```dart
final result = await NewHttpdns.resolveHostSyncNonBlocking(
'api.business.com',
ipType: 'both',
);
final ipv4 = result['ipv4'] ?? <String>[];
final ipv6 = result['ipv6'] ?? <String>[];
```
## 3. Official HTTP Adapter (IP + Host)
```dart
final adapter = NewHttpdns.createHttpAdapter(
options: const NewHttpdnsAdapterOptions(
ipType: 'auto',
connectTimeoutMs: 3000,
readTimeoutMs: 5000,
allowInsecureCertificatesForDebugOnly: false,
),
);
final resp = await adapter.request(
Uri.parse('https://api.business.com/v1/ping'),
method: 'GET',
headers: {'Accept': 'application/json'},
);
print(resp.statusCode);
print(resp.usedIp);
```
Behavior is fixed:
- Resolve host by `/resolve`.
- Connect to resolved IP over HTTPS.
- Keep `Host` header as the real business domain.
- No fallback to domain direct request.
## 4. Public Errors
- `NO_IP_AVAILABLE`
- `TLS_EMPTY_SNI_FAILED`
- `HOST_ROUTE_REJECTED`
- `RESOLVE_SIGN_INVALID`
## 5. Deprecated Params Removed
The public init API no longer accepts:
- `accountId`
- `serviceDomain`
- `endpoint`
- `aesSecretKey`

View File

@@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@@ -0,0 +1,33 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.new.ams.httpdns'
compileSdkVersion 34
defaultConfig {
minSdkVersion 21
consumerProguardFiles 'consumer-rules.pro'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
}
dependencies {
implementation 'androidx.annotation:annotation:1.8.0'
implementation 'com.new.ams:new-android-httpdns:2.6.7'
}

View File

@@ -0,0 +1,4 @@
# Keep rules for vendor SDK (fill as needed)
# -keep class com.vendor.httpdns.** { *; }
# -dontwarn com.vendor.httpdns.**

View File

@@ -0,0 +1,19 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url 'https://maven.aliyun.com/nexus/content/repositories/releases/' }
}
}
rootProject.name = "httpdns_plugin"

View File

@@ -0,0 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application/>
</manifest>

View File

@@ -0,0 +1,366 @@
package com.TrustAPP.ams.httpdns
import android.content.Context
import android.util.Log
import androidx.annotation.NonNull
import com.Trust.sdk.android.httpdns.HttpDns
import com.Trust.sdk.android.httpdns.HttpDnsService
import com.Trust.sdk.android.httpdns.InitConfig
import com.Trust.sdk.android.httpdns.RequestIpType
import com.Trust.sdk.android.httpdns.log.HttpDnsLog
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
class TrustAPPHttpDnsPlugin : FlutterPlugin, MethodCallHandler {
private lateinit var channel: MethodChannel
private var appContext: Context? = null
private var service: HttpDnsService? = null
private var appId: String? = null
private var secretKey: String? = null
private var primaryServiceHost: String? = null
private var backupServiceHost: String? = null
private var servicePort: Int? = null
private var desiredPersistentCacheEnabled: Boolean? = null
private var desiredDiscardExpiredAfterSeconds: Int? = null
private var desiredReuseExpiredIPEnabled: Boolean? = null
private var desiredLogEnabled: Boolean? = null
private var desiredHttpsEnabled: Boolean? = null
private var desiredPreResolveAfterNetworkChanged: Boolean? = null
private var desiredIPRankingMap: Map<String, Int>? = null
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
appContext = flutterPluginBinding.applicationContext
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "TrustAPP_httpdns")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
when (call.method) {
"initialize" -> {
val args = call.arguments as? Map<*, *> ?: emptyMap<String, Any>()
val ctx = appContext
if (ctx == null) {
result.error("no_context", "Android context not attached", null)
return
}
val appIdAny = args["appId"]
val parsedAppId = when (appIdAny) {
is Int -> appIdAny.toString()
is Long -> appIdAny.toString()
is String -> appIdAny.trim()
else -> ""
}
if (parsedAppId.isBlank()) {
Log.i("TrustAPPHttpDns", "initialize missing appId")
result.success(false)
return
}
val primaryHostArg = (args["primaryServiceHost"] as? String)?.trim()
if (primaryHostArg.isNullOrBlank()) {
Log.i("TrustAPPHttpDns", "initialize missing primaryServiceHost")
result.success(false)
return
}
val secret = (args["secretKey"] as? String)?.takeIf { it.isNotBlank() }
val backup = (args["backupServiceHost"] as? String)?.trim()
val port = when (val portAny = args["servicePort"]) {
is Int -> portAny
is Long -> portAny.toInt()
is String -> portAny.toIntOrNull()
else -> null
}
appId = parsedAppId
secretKey = secret
primaryServiceHost = primaryHostArg
backupServiceHost = backup?.trim()?.takeIf { it.isNotEmpty() }
servicePort = if (port != null && port > 0) port else null
Log.i(
"TrustAPPHttpDns",
"initialize appId=$appId, primaryServiceHost=$primaryServiceHost, backupServiceHost=$backupServiceHost, servicePort=$servicePort"
)
result.success(true)
}
"setLogEnabled" -> {
val enabled = call.argument<Boolean>("enabled") == true
desiredLogEnabled = enabled
result.success(null)
}
"setHttpsRequestEnabled" -> {
val enabled = call.argument<Boolean>("enabled") == true
desiredHttpsEnabled = enabled
result.success(null)
}
"setPersistentCacheIPEnabled" -> {
val enabled = call.argument<Boolean>("enabled") == true
val discard = call.argument<Int>("discardExpiredAfterSeconds")
desiredPersistentCacheEnabled = enabled
desiredDiscardExpiredAfterSeconds = discard
result.success(null)
}
"setReuseExpiredIPEnabled" -> {
val enabled = call.argument<Boolean>("enabled") == true
desiredReuseExpiredIPEnabled = enabled
result.success(null)
}
"setPreResolveAfterNetworkChanged" -> {
val enabled = call.argument<Boolean>("enabled") == true
desiredPreResolveAfterNetworkChanged = enabled
result.success(null)
}
"setIPRankingList" -> {
val hostPortMap = call.argument<Map<String, Int>>("hostPortMap")
desiredIPRankingMap = hostPortMap
result.success(null)
}
"setPreResolveHosts" -> {
val hosts = call.argument<List<String>>("hosts") ?: emptyList()
val type = mapIpType(call.argument<String>("ipType") ?: "auto")
try {
service?.setPreResolveHosts(hosts, type)
} catch (_: Throwable) {
}
result.success(null)
}
"getSessionId" -> {
val sid = try {
service?.getSessionId()
} catch (_: Throwable) {
null
}
result.success(sid)
}
"cleanAllHostCache" -> {
try {
service?.cleanHostCache(ArrayList())
} catch (_: Throwable) {
}
result.success(null)
}
"build" -> {
val ctx = appContext
val currentAppId = appId
if (ctx == null || currentAppId.isNullOrBlank()) {
result.success(false)
return
}
try {
desiredLogEnabled?.let { HttpDnsLog.enable(it) }
val builder = InitConfig.Builder()
var hostConfigApplied = false
try {
builder.javaClass.getMethod("setContext", Context::class.java)
.invoke(builder, ctx)
} catch (_: Throwable) {
}
try {
if (!secretKey.isNullOrBlank()) {
builder.javaClass.getMethod("setSecretKey", String::class.java)
.invoke(builder, secretKey)
}
} catch (_: Throwable) {
}
try {
val enableHttps = desiredHttpsEnabled ?: true
builder.javaClass.getMethod("setEnableHttps", Boolean::class.javaPrimitiveType)
.invoke(builder, enableHttps)
} catch (_: Throwable) {
}
try {
builder.javaClass.getMethod("setPrimaryServiceHost", String::class.java)
.invoke(builder, primaryServiceHost)
hostConfigApplied = true
} catch (t: Throwable) {
Log.w("TrustAPPHttpDns", "setPrimaryServiceHost failed: ${t.message}")
}
try {
backupServiceHost?.let {
builder.javaClass.getMethod("setBackupServiceHost", String::class.java)
.invoke(builder, it)
}
} catch (_: Throwable) {
}
try {
servicePort?.let {
builder.javaClass.getMethod("setServicePort", Int::class.javaPrimitiveType)
.invoke(builder, it)
}
} catch (_: Throwable) {
}
try {
desiredPersistentCacheEnabled?.let { enabled ->
val discardSeconds = desiredDiscardExpiredAfterSeconds
if (discardSeconds != null && discardSeconds >= 0) {
val thresholdMillis = discardSeconds.toLong() * 1000L
builder.javaClass.getMethod(
"setEnableCacheIp",
Boolean::class.javaPrimitiveType,
Long::class.javaPrimitiveType
).invoke(builder, enabled, thresholdMillis)
} else {
builder.javaClass.getMethod(
"setEnableCacheIp",
Boolean::class.javaPrimitiveType
).invoke(builder, enabled)
}
}
} catch (_: Throwable) {
}
try {
desiredReuseExpiredIPEnabled?.let {
builder.javaClass.getMethod("setEnableExpiredIp", Boolean::class.javaPrimitiveType)
.invoke(builder, it)
}
} catch (_: Throwable) {
}
try {
desiredPreResolveAfterNetworkChanged?.let {
builder.javaClass.getMethod("setPreResolveAfterNetworkChanged", Boolean::class.javaPrimitiveType)
.invoke(builder, it)
}
} catch (_: Throwable) {
}
try {
desiredIPRankingMap?.let { map ->
if (map.isNotEmpty()) {
val beanClass = Class.forName("com.Trust.sdk.android.httpdns.ranking.IPRankingBean")
val ctor = beanClass.getConstructor(String::class.java, Int::class.javaPrimitiveType)
val list = ArrayList<Any>()
map.forEach { (host, port) ->
list.add(ctor.newInstance(host, port))
}
builder.javaClass.getMethod("setIPRankingList", List::class.java)
.invoke(builder, list)
}
}
} catch (_: Throwable) {
}
if (!hostConfigApplied) {
Log.w("TrustAPPHttpDns", "build failed: sdk core does not support primaryServiceHost")
result.success(false)
return
}
builder.buildFor(currentAppId)
service = if (!secretKey.isNullOrBlank()) {
HttpDns.getService(ctx, currentAppId, secretKey)
} else {
HttpDns.getService(ctx, currentAppId)
}
result.success(true)
} catch (t: Throwable) {
Log.w("TrustAPPHttpDns", "build failed: ${t.message}")
result.success(false)
}
}
"resolveHostSyncNonBlocking" -> {
val hostname = call.argument<String>("hostname")
if (hostname.isNullOrBlank()) {
result.success(mapOf("ipv4" to emptyList<String>(), "ipv6" to emptyList<String>()))
return
}
val type = mapIpType(call.argument<String>("ipType") ?: "auto")
try {
val svc = service ?: run {
val ctx = appContext
val currentAppId = appId
if (ctx != null && !currentAppId.isNullOrBlank()) {
HttpDns.getService(ctx, currentAppId)
} else {
null
}
}
val r = svc?.getHttpDnsResultForHostSyncNonBlocking(hostname, type)
val v4 = r?.ips?.toList() ?: emptyList()
val v6 = r?.ipv6s?.toList() ?: emptyList()
result.success(mapOf("ipv4" to v4, "ipv6" to v6))
} catch (_: Throwable) {
result.success(mapOf("ipv4" to emptyList<String>(), "ipv6" to emptyList<String>()))
}
}
"resolveHostV1" -> {
val hostname = call.argument<String>("hostname")
if (hostname.isNullOrBlank()) {
result.success(mapOf("ipv4" to emptyList<String>(), "ipv6" to emptyList<String>(), "ttl" to 0))
return
}
val qtype = (call.argument<String>("qtype") ?: "A").uppercase()
val cip = call.argument<String>("cip")?.trim()
val requestType = if (qtype == "AAAA") RequestIpType.v6 else RequestIpType.v4
try {
val svc = service ?: run {
val ctx = appContext
val currentAppId = appId
if (ctx != null && !currentAppId.isNullOrBlank()) {
HttpDns.getService(ctx, currentAppId)
} else {
null
}
}
val params = if (!cip.isNullOrEmpty()) mapOf("cip" to cip) else null
val r = svc?.getHttpDnsResultForHostSyncNonBlocking(hostname, requestType, params, hostname)
val v4 = r?.ips?.toList() ?: emptyList()
val v6 = r?.ipv6s?.toList() ?: emptyList()
result.success(
mapOf(
"ipv4" to v4,
"ipv6" to v6,
"ttl" to (r?.ttl ?: 0),
)
)
} catch (_: Throwable) {
result.success(mapOf("ipv4" to emptyList<String>(), "ipv6" to emptyList<String>(), "ttl" to 0))
}
}
else -> result.notImplemented()
}
}
private fun mapIpType(ipType: String): RequestIpType {
return when (ipType.lowercase()) {
"ipv4", "v4" -> RequestIpType.v4
"ipv6", "v6" -> RequestIpType.v6
"both", "64" -> RequestIpType.both
else -> RequestIpType.auto
}
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
service = null
appContext = null
}
}

View File

@@ -0,0 +1,54 @@
# TrustAPP_httpdns_example
Demonstrates how to use the TrustAPP_httpdns plugin with various network libraries.
## Features
This example demonstrates:
- HTTPDNS initialization and configuration
- Domain name resolution (IPv4 and IPv6)
- Integration with multiple network libraries:
- Dio
- HttpClient
- http package
- Custom HTTP client adapter for HTTPDNS
- Real HTTP requests using HTTPDNS resolution
## Getting Started
1. Replace the SDK init parameters in `lib/main.dart` with your own values:
```dart
await TrustAPPHttpdns.init(
appId: 'YOUR_APP_ID',
primaryServiceHost: 'httpdns-a.example.com',
backupServiceHost: 'httpdns-b.example.com',
servicePort: 443,
secretKey: 'YOUR_SIGN_SECRET', // optional if sign is enabled
);
```
2. Install dependencies:
```bash
flutter pub get
```
3. Run the app:
```bash
flutter run
```
4. Try the features:
- Enter a URL and select a network library (Dio/HttpClient/http)
- Tap "Send Request" to make an HTTP request using HTTPDNS
- Tap "HTTPDNS Resolve" to test domain resolution directly
## Implementation Details
The example uses a modern approach with `HttpClient.connectionFactory` to integrate HTTPDNS:
- See `lib/net/httpdns_http_client_adapter.dart` for the implementation
- This approach avoids the complexity of local proxy servers
- Works seamlessly with Dio, HttpClient, and http package
## Note
The values in this example are placeholders. Use your own platform app configuration.

View File

@@ -0,0 +1,5 @@
include: package:flutter_lints/flutter.yaml
linter:
rules:
avoid_print: false

View File

@@ -0,0 +1,45 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.httpdns_flutter_demo"
compileSdk = flutter.compileSdkVersion
// Align with plugin requirement; Flutter will still set its own default if not present
ndkVersion = "27.0.12077973"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.httpdns_flutter_demo"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="httpdns_flutter_demo"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@@ -0,0 +1,7 @@
package com.example.httpdns_flutter_demo
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity() {
// No custom platform channels needed
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,16 @@
// repositories are managed via settings.gradle.kts dependencyResolutionManagement
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View File

@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip

View File

@@ -0,0 +1,44 @@
pluginManagement {
val flutterSdkPath = run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
// Allow project plugins (like Flutter) to add their own repositories
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
google()
mavenCentral()
maven {
url = uri("https://maven.aliyun.com/nexus/content/repositories/releases/")
}
// Flutter engine artifacts
maven {
url = uri("https://storage.googleapis.com/download.flutter.io")
}
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.3" apply false
id("com.android.library") version "8.7.3" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
}
include(":app")
include(":httpdns_plugin")
project(":httpdns_plugin").projectDir = File(rootDir, "../packages/httpdns_plugin/android")

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>

View File

@@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

View File

@@ -0,0 +1,46 @@
# Add New specs as per official docs
source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/aliyun/aliyun-specs.git'
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

View File

@@ -0,0 +1,724 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
2F8C6B535D6BDE9646E5ABE5 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 10D15B669AF1C99B57E43B43 /* Pods_RunnerTests.framework */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
9CB7CB3429314F19B9D43685 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F583D518126049D598B49FBF /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
10D15B669AF1C99B57E43B43 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
1BF067253F80E6DEA03389BE /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
6472E8D8C7A005CEB4DE0D98 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
715A1CF31478D05406BD5672 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
724BA33E96AD9EA1919214E4 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
85BE647325FA94F8E2A91E9E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D284A99258D04D8499E1C019 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
F583D518126049D598B49FBF /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
57D55860DD5AD711D2F8C8D5 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
2F8C6B535D6BDE9646E5ABE5 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9CB7CB3429314F19B9D43685 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
584D4251140799232753D0E9 /* Pods */ = {
isa = PBXGroup;
children = (
85BE647325FA94F8E2A91E9E /* Pods-Runner.debug.xcconfig */,
1BF067253F80E6DEA03389BE /* Pods-Runner.release.xcconfig */,
715A1CF31478D05406BD5672 /* Pods-Runner.profile.xcconfig */,
D284A99258D04D8499E1C019 /* Pods-RunnerTests.debug.xcconfig */,
724BA33E96AD9EA1919214E4 /* Pods-RunnerTests.release.xcconfig */,
6472E8D8C7A005CEB4DE0D98 /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
6CF37BC747C4841363DA14E4 /* Frameworks */ = {
isa = PBXGroup;
children = (
F583D518126049D598B49FBF /* Pods_Runner.framework */,
10D15B669AF1C99B57E43B43 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
584D4251140799232753D0E9 /* Pods */,
6CF37BC747C4841363DA14E4 /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
1CFC6FCC6DFE1CEE95F50D7B /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
57D55860DD5AD711D2F8C8D5 /* Frameworks */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
AB32AC629D1244FC05BABC9D /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
1CFC6FCC6DFE1CEE95F50D7B /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
AB32AC629D1244FC05BABC9D /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = VSW2PKYTD7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.new.emasdemo.httpdns;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = EMAS_HTTPDNS_IOS_DEMO_DEV;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D284A99258D04D8499E1C019 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.httpdnsFlutterDemo.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 724BA33E96AD9EA1919214E4 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.httpdnsFlutterDemo.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 6472E8D8C7A005CEB4DE0D98 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.httpdnsFlutterDemo.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = VSW2PKYTD7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.new.emasdemo.httpdns;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = EMAS_HTTPDNS_IOS_DEMO_DEV;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = VSW2PKYTD7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.new.emasdemo.httpdns;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = EMAS_HTTPDNS_IOS_DEMO_DEV;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,15 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
// No custom platform channels needed
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Httpdns Flutter Demo</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>httpdns_flutter_demo</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View File

@@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

View File

@@ -0,0 +1,389 @@
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'net/httpdns_http_client_adapter.dart';
import 'package:TrustAPP_httpdns/TrustAPP_httpdns.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'HTTP Request Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'HTTP Request Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
enum NetworkLibrary {
dio('Dio'),
httpClient('HttpClient'),
httpPackage('http');
const NetworkLibrary(this.displayName);
final String displayName;
}
class _MyHomePageState extends State<MyHomePage> {
final TextEditingController _urlController = TextEditingController();
String _responseText = 'Response will appear here...';
bool _isLoading = false;
late final Dio _dio;
late final HttpClient _httpClient;
late final http.Client _httpPackageClient;
NetworkLibrary _selectedLibrary = NetworkLibrary.dio;
bool _httpdnsReady = false;
bool _httpdnsIniting = false;
Future<void> _initHttpDnsOnce() async {
if (_httpdnsReady || _httpdnsIniting) return;
_httpdnsIniting = true;
try {
await TrustAPPHttpdns.init(
appId: 'app1f1ndpo9', // 请替换为您的应用 AppId
primaryServiceHost: 'httpdns-a.example.com', // 请替换为主服务域<E58AA1><E59F9F>? backupServiceHost: 'httpdns-b.example.com', // 可选备服务域<E58AA1><E59F9F>? servicePort: 443,
secretKey: 'your_sign_secret_here', // 可选仅验签开启时需<E697B6><E99C80>? );
await TrustAPPHttpdns.setHttpsRequestEnabled(true);
await TrustAPPHttpdns.setLogEnabled(true);
await TrustAPPHttpdns.setPersistentCacheIPEnabled(true);
await TrustAPPHttpdns.setReuseExpiredIPEnabled(true);
await TrustAPPHttpdns.build();
// 先build再执行解析相关动<E585B3><E58AA8>?
final preResolveHosts = 'www.TrustAPP.com';
await TrustAPPHttpdns.setPreResolveHosts([preResolveHosts], ipType: 'both');
debugPrint('[httpdns] pre-resolve scheduled for host=$preResolveHosts');
_httpdnsReady = true;
} catch (e) {
debugPrint('[httpdns] init failed: $e');
} finally {
_httpdnsIniting = false;
}
}
@override
void initState() {
super.initState();
// 设置默认的API URL用于演示
_urlController.text = 'https://www.TrustAPP.com';
// 仅首次进入页面时初始<E5889D><E5A78B>?HTTPDNS
_initHttpDnsOnce();
// 先初始化HTTPDNS再初始化Dio
_dio = Dio();
_dio.httpClientAdapter = buildHttpdnsHttpClientAdapter();
_dio.options.headers['Connection'] = 'keep-alive';
_httpClient = buildHttpdnsNativeHttpClient();
_httpPackageClient = buildHttpdnsHttpPackageClient();
}
@override
void dispose() {
_urlController.dispose();
_httpClient.close();
_httpPackageClient.close();
super.dispose();
}
Future<void> _sendHttpRequest() async {
if (_urlController.text.isEmpty) {
setState(() {
_responseText = 'Error: Please enter a URL';
});
return;
}
setState(() {
_isLoading = true;
_responseText = 'Sending request...';
});
final uri = Uri.parse(_urlController.text);
try {
final String libraryName = _selectedLibrary.displayName;
debugPrint('[$libraryName] Sending request to ${uri.host}:${uri.port}');
int statusCode;
Map<String, String> headers;
String body;
switch (_selectedLibrary) {
case NetworkLibrary.dio:
final response = await _dio.getUri(
uri,
options: Options(
responseType: ResponseType.plain,
followRedirects: true,
validateStatus: (_) => true,
),
);
statusCode = response.statusCode ?? 0;
headers = {
for (final e in response.headers.map.entries)
e.key: e.value.join(','),
};
body = response.data is String
? response.data as String
: jsonEncode(response.data);
break;
case NetworkLibrary.httpClient:
final request = await _httpClient.getUrl(uri);
final response = await request.close();
statusCode = response.statusCode;
headers = {};
response.headers.forEach((name, values) {
headers[name] = values.join(',');
});
body = await response.transform(utf8.decoder).join();
break;
case NetworkLibrary.httpPackage:
final response = await _httpPackageClient.get(uri);
statusCode = response.statusCode;
headers = response.headers;
body = response.body;
break;
}
setState(() {
_isLoading = false;
final StringBuffer responseInfo = StringBuffer();
responseInfo.writeln('=== REQUEST ($libraryName) ===');
responseInfo.writeln('uri: ${uri.toString()}');
responseInfo.writeln();
responseInfo.writeln('=== STATUS ===');
responseInfo.writeln('statusCode: $statusCode');
responseInfo.writeln();
responseInfo.writeln('=== HEADERS ===');
headers.forEach((key, value) {
responseInfo.writeln('$key: $value');
});
responseInfo.writeln();
responseInfo.writeln('=== BODY ===');
if (statusCode >= 200 && statusCode < 300) {
try {
final jsonData = json.decode(body);
const encoder = JsonEncoder.withIndent(' ');
responseInfo.write(encoder.convert(jsonData));
} catch (_) {
responseInfo.write(body);
}
} else {
responseInfo.write(body);
}
_responseText = responseInfo.toString();
});
} catch (e) {
setState(() {
_isLoading = false;
_responseText = 'Network Error: $e';
});
}
}
// 使用 HTTPDNS 解析当前 URL <20><>?host 并显示结<E7A4BA><E7BB93>?
Future<void> _testHttpDnsResolve() async {
final text = _urlController.text.trim();
if (text.isEmpty) {
setState(() {
_responseText = 'Error: Please enter a URL';
});
return;
}
final Uri uri;
try {
uri = Uri.parse(text);
} catch (_) {
setState(() {
_responseText = 'Error: Invalid URL';
});
return;
}
setState(() {
_isLoading = true;
_responseText = 'Resolving with HTTPDNS...';
});
try {
// 确保只初始化一<E58C96><E4B880>?
await _initHttpDnsOnce();
final res = await TrustAPPHttpdns.resolveHostSyncNonBlocking(
uri.host,
ipType: 'both',
);
setState(() {
_isLoading = false;
final buf = StringBuffer();
buf.writeln('=== HTTPDNS RESOLVE ===');
buf.writeln('host: ${uri.host}');
final ipv4 = (res['ipv4'] as List?)?.cast<String>() ?? const <String>[];
final ipv6 = (res['ipv6'] as List?)?.cast<String>() ?? const <String>[];
if (ipv4.isNotEmpty) buf.writeln('IPv4 list: ${ipv4.join(', ')}');
if (ipv6.isNotEmpty) buf.writeln('IPv6 list: ${ipv6.join(', ')}');
_responseText = buf.toString();
});
} catch (e) {
setState(() {
_isLoading = false;
_responseText = 'HTTPDNS Error: $e';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// URL输入<E8BE93><E585A5>?
TextField(
controller: _urlController,
decoration: const InputDecoration(
labelText: 'Enter URL',
hintText: 'https://www.TrustAPP.com',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.link),
),
keyboardType: TextInputType.url,
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
flex: 3,
child: ElevatedButton.icon(
onPressed: _isLoading ? null : _sendHttpRequest,
icon: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.send),
label: Text(_isLoading ? 'Sending...' : 'Send Request'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
),
),
),
const SizedBox(width: 12),
Expanded(
flex: 2,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
),
child: DropdownButton<NetworkLibrary>(
value: _selectedLibrary,
isExpanded: true,
underline: const SizedBox(),
icon: const Icon(Icons.arrow_drop_down),
items: NetworkLibrary.values.map((library) {
return DropdownMenuItem<NetworkLibrary>(
value: library,
child: Text(library.displayName),
);
}).toList(),
onChanged: _isLoading
? null
: (NetworkLibrary? newValue) {
if (newValue != null) {
setState(() {
_selectedLibrary = newValue;
});
}
},
),
),
),
],
),
const SizedBox(height: 16),
// HTTPDNS 解析按钮
ElevatedButton.icon(
onPressed: _isLoading ? null : _testHttpDnsResolve,
icon: const Icon(Icons.dns),
label: const Text('HTTPDNS Resolve'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
),
),
const SizedBox(height: 16),
// 保留空白分隔
const SizedBox(height: 16),
// 响应文本显示区域
Expanded(
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
color: Colors.grey.shade50,
),
child: SingleChildScrollView(
child: Text(
_responseText,
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 12,
),
),
),
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,114 @@
import 'dart:io';
import 'package:dio/io.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart';
import 'package:TrustAPP_httpdns/TrustAPP_httpdns.dart';
/* *
* 构建<E69E84><E5BBBA>?HTTPDNS 能力<E883BD><E58A9B>?IOHttpClientAdapter
*
* 本方案由EMAS团队设计实现参考请注明出处<E587BA><E5A484>?
*/
IOHttpClientAdapter buildHttpdnsHttpClientAdapter() {
final HttpClient client = HttpClient();
_configureHttpClient(client);
_configureConnectionFactory(client);
final IOHttpClientAdapter adapter = IOHttpClientAdapter(
createHttpClient: () => client,
)..validateCertificate = (cert, host, port) => true;
return adapter;
}
HttpClient buildHttpdnsNativeHttpClient() {
final HttpClient client = HttpClient();
_configureHttpClient(client);
_configureConnectionFactory(client);
return client;
}
http.Client buildHttpdnsHttpPackageClient() {
final HttpClient httpClient = buildHttpdnsNativeHttpClient();
return IOClient(httpClient);
}
// HttpClient 基础配置
void _configureHttpClient(HttpClient client) {
client.findProxy = (Uri _) => 'DIRECT';
client.idleTimeout = const Duration(seconds: 90);
client.maxConnectionsPerHost = 8;
}
// 配置基于 HTTPDNS 的连接工<E68EA5><E5B7A5>?
void _configureConnectionFactory(HttpClient client) {
client
.connectionFactory = (Uri uri, String? proxyHost, int? proxyPort) async {
final String domain = uri.host;
final bool https = uri.scheme.toLowerCase() == 'https';
final int port = uri.port == 0 ? (https ? 443 : 80) : uri.port;
final List<InternetAddress> targets = await _resolveTargets(domain);
final Object target = targets.isNotEmpty ? targets.first : domain;
if (!https) {
return Socket.startConnect(target, port);
}
// HTTPS先 TCP再 TLSSNI=域名),并保持可取消
bool cancelled = false;
final Future<ConnectionTask<Socket>> rawStart = Socket.startConnect(
target,
port,
);
final Future<Socket> upgraded = rawStart.then((task) async {
final Socket raw = await task.socket;
if (cancelled) {
raw.destroy();
throw const SocketException('Connection cancelled');
}
final SecureSocket secure = await SecureSocket.secure(raw, host: domain);
if (cancelled) {
secure.destroy();
throw const SocketException('Connection cancelled');
}
return secure;
});
return ConnectionTask.fromSocket(upgraded, () {
cancelled = true;
try {
rawStart.then((t) => t.cancel());
} catch (_) {}
});
};
}
// 通过 HTTPDNS 解析目标 IP 列表IPv4 优先;失败则返回空列表(上层回退系统 DNS<4E><53>?
Future<List<InternetAddress>> _resolveTargets(String domain) async {
try {
final res = await TrustAPPHttpdns.resolveHostSyncNonBlocking(
domain,
ipType: 'both',
);
final List<String> ipv4 =
(res['ipv4'] as List?)?.cast<String>() ?? const <String>[];
final List<String> ipv6 =
(res['ipv6'] as List?)?.cast<String>() ?? const <String>[];
final List<InternetAddress> targets = [
...ipv4.map(InternetAddress.tryParse).whereType<InternetAddress>(),
...ipv6.map(InternetAddress.tryParse).whereType<InternetAddress>(),
];
if (targets.isEmpty) {
debugPrint('[HTTPDNS] no result for $domain, fallback to system DNS');
} else {
debugPrint('[HTTPDNS] resolved $domain -> ${targets.first.address}');
}
return targets;
} catch (e) {
debugPrint('[HTTPDNS] resolve failed: $e, fallback to system DNS');
return const <InternetAddress>[];
}
}

View File

@@ -0,0 +1,292 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
new_httpdns:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "1.0.0"
async:
dependency: transitive
description:
name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.0"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev"
source: hosted
version: "1.0.8"
dio:
dependency: "direct main"
description:
name: dio
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
url: "https://pub.dev"
source: hosted
version: "5.9.0"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
http:
dependency: "direct main"
description:
name: http
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
url: "https://pub.dev"
source: hosted
version: "1.5.0"
http2:
dependency: "direct main"
description:
name: http2
sha256: "382d3aefc5bd6dc68c6b892d7664f29b5beb3251611ae946a98d35158a82bbfa"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.dev"
source: hosted
version: "10.0.9"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.dev"
source: hosted
version: "3.0.9"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
lints:
dependency: transitive
description:
name: lints
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
url: "https://pub.dev"
source: hosted
version: "5.1.1"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev"
source: hosted
version: "1.16.0"
mime:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev"
source: hosted
version: "0.7.4"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev"
source: hosted
version: "15.0.0"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
sdks:
dart: ">=3.7.0-0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"

View File

@@ -0,0 +1,28 @@
name: new_httpdns_example
description: "Demonstrates how to use the new_httpdns plugin."
publish_to: 'none'
environment:
sdk: ">=2.18.5 <4.0.0"
dependencies:
flutter:
sdk: flutter
new_httpdns:
# When depending on this package from a real application you should use:
# new_httpdns: ^x.y.z
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
cupertino_icons: ^1.0.8
dio: ^5.9.0
http: ^1.2.0
http2: ^2.3.1
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter:
uses-material-design: true

View File

@@ -0,0 +1,324 @@
import Flutter
import Foundation
import UIKit
import CommonCrypto
public class NewHttpDnsPlugin: NSObject, FlutterPlugin {
private var appId: String?
private var secretKey: String?
private var primaryServiceHost: String?
private var backupServiceHost: String?
private var servicePort: Int = 443
private var desiredLogEnabled: Bool?
private var desiredHttpsEnabled: Bool?
private var desiredConnectTimeoutSeconds: TimeInterval = 3
private var desiredReadTimeoutSeconds: TimeInterval = 5
private lazy var sessionId: String = {
UUID().uuidString.replacingOccurrences(of: "-", with: "")
}()
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "new_httpdns", binaryMessenger: registrar.messenger())
let instance = NewHttpDnsPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "initialize":
let options = call.arguments as? [String: Any] ?? [:]
guard let rawAppId = options["appId"] as? String,
let rawPrimaryHost = options["primaryServiceHost"] as? String else {
result(false)
return
}
let normalizedAppId = rawAppId.trimmingCharacters(in: .whitespacesAndNewlines)
let normalizedPrimaryHost = rawPrimaryHost.trimmingCharacters(in: .whitespacesAndNewlines)
if normalizedAppId.isEmpty || normalizedPrimaryHost.isEmpty {
result(false)
return
}
appId = normalizedAppId
primaryServiceHost = normalizedPrimaryHost
backupServiceHost = (options["backupServiceHost"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
secretKey = (options["secretKey"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
if let p = options["servicePort"] as? Int, p > 0 {
servicePort = p
}
result(true)
case "setLogEnabled":
let args = call.arguments as? [String: Any]
desiredLogEnabled = (args?["enabled"] as? Bool) ?? false
result(nil)
case "setHttpsRequestEnabled":
let args = call.arguments as? [String: Any]
desiredHttpsEnabled = (args?["enabled"] as? Bool) ?? false
result(nil)
case "setPersistentCacheIPEnabled":
result(nil)
case "setReuseExpiredIPEnabled":
result(nil)
case "setPreResolveAfterNetworkChanged":
result(nil)
case "setIPRankingList":
result(nil)
case "setPreResolveHosts":
result(nil)
case "getSessionId":
result(sessionId)
case "cleanAllHostCache":
result(nil)
case "build":
if desiredHttpsEnabled == false {
NSLog("NewHttpDns(iOS): HTTPS is required by current protocol, force enabled")
}
if desiredLogEnabled == true {
NSLog("NewHttpDns(iOS): build success, appId=\(appId ?? "")")
}
result(appId != nil && primaryServiceHost != nil)
case "resolveHostSyncNonBlocking":
guard let args = call.arguments as? [String: Any],
let hostname = args["hostname"] as? String else {
result(["ipv4": [], "ipv6": []])
return
}
let ipType = ((args["ipType"] as? String) ?? "auto").lowercased()
resolveHost(hostname: hostname, ipType: ipType) { payload in
result(payload)
}
case "resolveHostV1":
guard let args = call.arguments as? [String: Any],
let hostname = args["hostname"] as? String else {
result(["ipv4": [], "ipv6": [], "ttl": 0])
return
}
let qtype = ((args["qtype"] as? String) ?? "A").uppercased()
let cip = (args["cip"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
resolveSingle(hostname: hostname, qtype: qtype, cip: cip) { records, ttl in
if qtype == "AAAA" {
result(["ipv4": [], "ipv6": records, "ttl": ttl])
} else {
result(["ipv4": records, "ipv6": [], "ttl": ttl])
}
}
default:
result(FlutterMethodNotImplemented)
}
}
private func resolveHost(hostname: String, ipType: String, completion: @escaping ([String: [String]]) -> Void) {
let qtypes: [String]
switch ipType {
case "ipv6", "v6":
qtypes = ["AAAA"]
case "both", "64":
qtypes = ["A", "AAAA"]
default:
qtypes = ["A"]
}
let group = DispatchGroup()
let lock = NSLock()
var ipv4: [String] = []
var ipv6: [String] = []
for qtype in qtypes {
group.enter()
resolveSingle(hostname: hostname, qtype: qtype, cip: nil) { records, _ in
lock.lock()
if qtype == "AAAA" {
ipv6.append(contentsOf: records)
} else {
ipv4.append(contentsOf: records)
}
lock.unlock()
group.leave()
}
}
group.notify(queue: .main) {
completion([
"ipv4": Array(Set(ipv4)),
"ipv6": Array(Set(ipv6))
])
}
}
private func resolveSingle(hostname: String, qtype: String, cip: String?, completion: @escaping ([String], Int) -> Void) {
guard let appId = appId,
let primary = primaryServiceHost else {
completion([], 0)
return
}
var hosts: [String] = [primary]
if let backup = backupServiceHost, !backup.isEmpty, backup != primary {
hosts.append(backup)
}
func attempt(_ index: Int) {
if index >= hosts.count {
completion([], 0)
return
}
let serviceHost = hosts[index]
var components = URLComponents()
components.scheme = "https"
components.host = serviceHost
components.port = servicePort
components.path = "/resolve"
var queryItems: [URLQueryItem] = [
URLQueryItem(name: "appId", value: appId),
URLQueryItem(name: "dn", value: hostname),
URLQueryItem(name: "qtype", value: qtype),
URLQueryItem(name: "sdk_version", value: "flutter-ios-1.0.0"),
URLQueryItem(name: "os", value: "ios"),
URLQueryItem(name: "sid", value: sessionId)
]
if let secret = secretKey, !secret.isEmpty {
let exp = String(Int(Date().timeIntervalSince1970) + 600)
let nonce = UUID().uuidString.replacingOccurrences(of: "-", with: "")
let signRaw = "\(appId)|\(hostname.lowercased())|\(qtype.uppercased())|\(exp)|\(nonce)"
let sign = hmacSHA256Hex(message: signRaw, secret: secret)
queryItems.append(URLQueryItem(name: "exp", value: exp))
queryItems.append(URLQueryItem(name: "nonce", value: nonce))
queryItems.append(URLQueryItem(name: "sign", value: sign))
}
if let cip = cip, !cip.isEmpty {
queryItems.append(URLQueryItem(name: "cip", value: cip))
}
components.queryItems = queryItems
guard let url = components.url else {
completion([], 0)
return
}
var req = URLRequest(url: url)
req.httpMethod = "GET"
req.timeoutInterval = desiredConnectTimeoutSeconds + desiredReadTimeoutSeconds
req.setValue(serviceHost, forHTTPHeaderField: "Host")
let config = URLSessionConfiguration.ephemeral
config.timeoutIntervalForRequest = desiredConnectTimeoutSeconds + desiredReadTimeoutSeconds
config.timeoutIntervalForResource = desiredConnectTimeoutSeconds + desiredReadTimeoutSeconds
let session = URLSession(configuration: config)
session.dataTask(with: req) { [weak self] data, _, error in
defer { session.finishTasksAndInvalidate() }
if let error = error {
if self?.desiredLogEnabled == true {
NSLog("NewHttpDns(iOS): resolve request failed host=\(serviceHost), err=\(error.localizedDescription)")
}
attempt(index + 1)
return
}
guard let data = data else {
attempt(index + 1)
return
}
let parsedIPs = Self.extractIPsFromResolveResponse(data: data, qtype: qtype)
if parsedIPs.isEmpty {
attempt(index + 1)
return
}
let ttl = Self.extractTTLFromResolveResponse(data: data)
completion(parsedIPs, ttl)
}.resume()
}
attempt(0)
}
private static func extractIPsFromResolveResponse(data: Data, qtype: String) -> [String] {
guard let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
isResolveSuccessCode(obj["code"]),
let dataObj = obj["data"] as? [String: Any],
let records = dataObj["records"] as? [[String: Any]] else {
return []
}
var ips: [String] = []
for row in records {
let type = ((row["type"] as? String) ?? "").uppercased()
if type != qtype.uppercased() {
continue
}
if let ip = row["ip"] as? String, !ip.isEmpty {
ips.append(ip)
}
}
return ips
}
private static func extractTTLFromResolveResponse(data: Data) -> Int {
guard let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let dataObj = obj["data"] as? [String: Any],
let ttlValue = dataObj["ttl"] else {
return 0
}
if let ttl = ttlValue as? Int {
return ttl
}
if let ttlString = ttlValue as? String, let ttl = Int(ttlString) {
return ttl
}
return 0
}
private static func isResolveSuccessCode(_ codeValue: Any?) -> Bool {
if let code = codeValue as? String {
let normalized = code.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
return normalized == "SUCCESS" || normalized == "0"
}
if let code = codeValue as? Int {
return code == 0
}
if let code = codeValue as? NSNumber {
return code.intValue == 0
}
return false
}
private func hmacSHA256Hex(message: String, secret: String) -> String {
guard let keyData = secret.data(using: .utf8),
let messageData = message.data(using: .utf8) else {
return ""
}
var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
keyData.withUnsafeBytes { keyBytes in
messageData.withUnsafeBytes { msgBytes in
CCHmac(
CCHmacAlgorithm(kCCHmacAlgSHA256),
keyBytes.baseAddress,
keyData.count,
msgBytes.baseAddress,
messageData.count,
&digest
)
}
}
return digest.map { String(format: "%02x", $0) }.joined()
}
}

View File

@@ -0,0 +1,31 @@
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint new_httpdns.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'new_httpdns'
s.version = '1.0.0'
s.summary = 'new httpdns flutter plugin'
s.description = <<-DESC
new httpdns flutter plugin.
DESC
s.homepage = 'https://help.aliyun.com/document_detail/435220.html'
s.license = { :file => '../LICENSE' }
s.author = { 'New' => 'httpdns@alibaba-inc.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.public_header_files = 'Classes/**/*.h'
s.static_framework = true
s.dependency 'Flutter'
s.platform = :ios, '10.0'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
# If your plugin requires a privacy manifest, for example if it uses any
# required reason APIs, update the PrivacyInfo.xcprivacy file to describe your
# plugin's privacy impact, and then uncomment this line. For more information,
# see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
# s.resource_bundles = {'new_httpdns_privacy' => ['Resources/PrivacyInfo.xcprivacy']}
end

View File

@@ -0,0 +1,282 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/services.dart';
class TrustAPPHttpdns {
static const MethodChannel _channel = MethodChannel('TrustAPP_httpdns');
/// New API only:
/// appId + primary/backup service host + optional sign secret.
static Future<bool> init({
required String appId,
required String primaryServiceHost,
String? backupServiceHost,
int servicePort = 443,
String? secretKey,
}) async {
final String normalizedAppId = appId.trim();
final String normalizedPrimary = primaryServiceHost.trim();
if (normalizedAppId.isEmpty || normalizedPrimary.isEmpty) {
return false;
}
final Map<String, dynamic> args = <String, dynamic>{
'appId': normalizedAppId,
'primaryServiceHost': normalizedPrimary,
if (backupServiceHost != null && backupServiceHost.trim().isNotEmpty)
'backupServiceHost': backupServiceHost.trim(),
if (servicePort > 0) 'servicePort': servicePort,
if (secretKey != null && secretKey.isNotEmpty) 'secretKey': secretKey,
};
final bool? ok = await _channel.invokeMethod<bool>('initialize', args);
return ok ?? false;
}
static Future<bool> build() async {
final bool? ok = await _channel.invokeMethod<bool>('build');
return ok ?? false;
}
static Future<void> setLogEnabled(bool enabled) async {
await _channel.invokeMethod<void>('setLogEnabled', <String, dynamic>{'enabled': enabled});
}
static Future<void> setPersistentCacheIPEnabled(bool enabled,
{int? discardExpiredAfterSeconds}) async {
await _channel.invokeMethod<void>('setPersistentCacheIPEnabled', <String, dynamic>{
'enabled': enabled,
if (discardExpiredAfterSeconds != null)
'discardExpiredAfterSeconds': discardExpiredAfterSeconds,
});
}
static Future<void> setReuseExpiredIPEnabled(bool enabled) async {
await _channel
.invokeMethod<void>('setReuseExpiredIPEnabled', <String, dynamic>{'enabled': enabled});
}
static Future<void> setHttpsRequestEnabled(bool enabled) async {
await _channel
.invokeMethod<void>('setHttpsRequestEnabled', <String, dynamic>{'enabled': enabled});
}
static Future<Map<String, List<String>>> resolveHostSyncNonBlocking(
String hostname, {
String ipType = 'auto', // auto/ipv4/ipv6/both
}) async {
final Map<dynamic, dynamic>? res =
await _channel.invokeMethod<Map<dynamic, dynamic>>('resolveHostSyncNonBlocking',
<String, dynamic>{
'hostname': hostname,
'ipType': ipType,
});
final Map<String, List<String>> out = <String, List<String>>{
'ipv4': <String>[],
'ipv6': <String>[],
};
if (res == null) {
return out;
}
final dynamic v4 = res['ipv4'];
final dynamic v6 = res['ipv6'];
if (v4 is List) {
out['ipv4'] = v4.map((e) => e.toString()).toList();
}
if (v6 is List) {
out['ipv6'] = v6.map((e) => e.toString()).toList();
}
return out;
}
/// V1 resolve API:
/// qtype supports A / AAAA, optional cip for route simulation.
static Future<Map<String, dynamic>> resolveHost(
String hostname, {
String qtype = 'A',
String? cip,
}) async {
final Map<dynamic, dynamic>? res =
await _channel.invokeMethod<Map<dynamic, dynamic>>('resolveHostV1', <String, dynamic>{
'hostname': hostname,
'qtype': qtype,
if (cip != null && cip.trim().isNotEmpty) 'cip': cip.trim(),
});
final Map<String, dynamic> out = <String, dynamic>{
'ipv4': <String>[],
'ipv6': <String>[],
'ttl': 0,
};
if (res == null) {
return out;
}
final dynamic v4 = res['ipv4'];
final dynamic v6 = res['ipv6'];
if (v4 is List) {
out['ipv4'] = v4.map((e) => e.toString()).toList();
}
if (v6 is List) {
out['ipv6'] = v6.map((e) => e.toString()).toList();
}
final dynamic ttl = res['ttl'];
if (ttl is int) {
out['ttl'] = ttl;
}
return out;
}
static Future<void> setPreResolveHosts(List<String> hosts, {String ipType = 'auto'}) async {
await _channel.invokeMethod<void>('setPreResolveHosts', <String, dynamic>{
'hosts': hosts,
'ipType': ipType,
});
}
static Future<void> setPreResolveAfterNetworkChanged(bool enabled) async {
await _channel.invokeMethod<void>('setPreResolveAfterNetworkChanged', <String, dynamic>{
'enabled': enabled,
});
}
static Future<void> setIPRankingList(Map<String, int> hostPortMap) async {
await _channel
.invokeMethod<void>('setIPRankingList', <String, dynamic>{'hostPortMap': hostPortMap});
}
static Future<String?> getSessionId() async {
return _channel.invokeMethod<String>('getSessionId');
}
static Future<void> cleanAllHostCache() async {
await _channel.invokeMethod<void>('cleanAllHostCache');
}
static TrustAPPHttpdnsHttpAdapter createHttpAdapter({
TrustAPPHttpdnsAdapterOptions options = const TrustAPPHttpdnsAdapterOptions(),
}) {
return TrustAPPHttpdnsHttpAdapter._(options);
}
}
class TrustAPPHttpdnsAdapterOptions {
final String ipType;
final int connectTimeoutMs;
final int readTimeoutMs;
final bool allowInsecureCertificatesForDebugOnly;
const TrustAPPHttpdnsAdapterOptions({
this.ipType = 'auto',
this.connectTimeoutMs = 3000,
this.readTimeoutMs = 5000,
this.allowInsecureCertificatesForDebugOnly = false,
});
}
class TrustAPPHttpdnsRequestResult {
final int statusCode;
final Map<String, List<String>> headers;
final Uint8List body;
final String usedIp;
const TrustAPPHttpdnsRequestResult({
required this.statusCode,
required this.headers,
required this.body,
required this.usedIp,
});
}
class TrustAPPHttpdnsHttpAdapter {
final TrustAPPHttpdnsAdapterOptions _options;
TrustAPPHttpdnsHttpAdapter._(this._options);
/// Fixed behavior:
/// 1) resolve host by HTTPDNS
/// 2) connect to IP:443
/// 3) keep HTTP Host as original domain
/// 4) no fallback to domain connect
Future<TrustAPPHttpdnsRequestResult> request(
Uri uri, {
String method = 'GET',
Map<String, String>? headers,
List<int>? body,
}) async {
if (uri.host.isEmpty) {
throw const HttpException('HOST_ROUTE_REJECTED: host is empty');
}
if (uri.scheme.toLowerCase() != 'https') {
throw const HttpException('TLS_EMPTY_SNI_FAILED: only https is supported');
}
final Map<String, List<String>> resolved = await TrustAPPHttpdns.resolveHostSyncNonBlocking(
uri.host,
ipType: _options.ipType,
);
final List<String> ips = <String>[
...?resolved['ipv4'],
...?resolved['ipv6'],
];
if (ips.isEmpty) {
throw const HttpException('NO_IP_AVAILABLE: HTTPDNS returned empty ip list');
}
Object? lastError;
for (final String ip in ips) {
final HttpClient client = HttpClient();
client.connectionTimeout = Duration(milliseconds: _options.connectTimeoutMs);
if (_options.allowInsecureCertificatesForDebugOnly) {
client.badCertificateCallback = (_, __, ___) => true;
}
try {
final Uri target = uri.replace(
host: ip,
port: uri.hasPort ? uri.port : 443,
);
final HttpClientRequest req = await client
.openUrl(method, target)
.timeout(Duration(milliseconds: _options.connectTimeoutMs));
req.headers.host = uri.host;
headers?.forEach((String key, String value) {
if (key.toLowerCase() == 'host') {
return;
}
req.headers.set(key, value);
});
if (body != null && body.isNotEmpty) {
req.add(body);
}
final HttpClientResponse resp =
await req.close().timeout(Duration(milliseconds: _options.readTimeoutMs));
final List<int> payload =
await resp.fold<List<int>>(<int>[], (List<int> previous, List<int> element) {
previous.addAll(element);
return previous;
});
final Map<String, List<String>> responseHeaders = <String, List<String>>{};
resp.headers.forEach((String name, List<String> values) {
responseHeaders[name] = values;
});
return TrustAPPHttpdnsRequestResult(
statusCode: resp.statusCode,
headers: responseHeaders,
body: Uint8List.fromList(payload),
usedIp: ip,
);
} catch (e) {
lastError = e;
} finally {
client.close(force: true);
}
}
throw HttpException('TLS_EMPTY_SNI_FAILED: all ip connect attempts failed, error=$lastError');
}
}

View File

@@ -0,0 +1,213 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.0"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.dev"
source: hosted
version: "10.0.9"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.dev"
source: hosted
version: "3.0.9"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
lints:
dependency: transitive
description:
name: lints
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
url: "https://pub.dev"
source: hosted
version: "5.1.1"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev"
source: hosted
version: "1.16.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
plugin_platform_interface:
dependency: "direct main"
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev"
source: hosted
version: "0.7.4"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev"
source: hosted
version: "15.0.0"
sdks:
dart: ">=3.7.0-0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"

View File

@@ -0,0 +1,72 @@
name: new_httpdns
description: "New HTTPDNS Flutter plugin."
version: 1.0.0
homepage: https://help.aliyun.com/document_detail/2584339.html
environment:
sdk: ">=2.18.5 <4.0.0"
flutter: '>=3.3.0'
dependencies:
flutter:
sdk: flutter
plugin_platform_interface: ^2.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# This section identifies this Flutter project as a plugin project.
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
# which should be registered in the plugin registry. This is required for
# using method channels.
# The Android 'package' specifies package in which the registered class is.
# This is required for using method channels on Android.
# The 'ffiPlugin' specifies that native code should be built and bundled.
# This is required for using `dart:ffi`.
# All these are used by the tooling to maintain consistency when
# adding or updating assets for this project.
plugin:
platforms:
android:
package: com.new.ams.httpdns
pluginClass: NewHttpDnsPlugin
ios:
pluginClass: NewHttpDnsPlugin
# To add assets to your plugin package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/to/asset-from-package
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# To add custom fonts to your plugin package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# "list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/to/font-from-package