init view
This commit is contained in:
parent
c09a064eff
commit
269ee4f524
90
punchnet/AbortView.swift
Normal file
90
punchnet/AbortView.swift
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
//
|
||||||
|
// AbortView.swift
|
||||||
|
// sdlan
|
||||||
|
//
|
||||||
|
// Created by 安礼成 on 2024/6/5.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct AbortView: View {
|
||||||
|
|
||||||
|
struct AlertShow: Identifiable {
|
||||||
|
enum ShowContent {
|
||||||
|
case error(String)
|
||||||
|
case upgrade(String, String)
|
||||||
|
}
|
||||||
|
|
||||||
|
var id: String
|
||||||
|
var content: ShowContent
|
||||||
|
}
|
||||||
|
|
||||||
|
@State private var alertShow: AlertShow?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
Image("logo")
|
||||||
|
|
||||||
|
Text("sdlan")
|
||||||
|
|
||||||
|
Text("Version1.1")
|
||||||
|
|
||||||
|
Button {
|
||||||
|
Task {
|
||||||
|
guard let response = try? await SDLAPI.checkVersion(clientId: "test", version: 1, channel: "macos") else {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.alertShow = AlertShow(id: "network_error", content: .error("Network Error"))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let result = response.result {
|
||||||
|
if result.upgrade_type == 0 {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.alertShow = AlertShow(id: "upgrade_0", content: .upgrade(result.upgrade_prompt, ""))
|
||||||
|
}
|
||||||
|
} else if result.upgrade_type == 1 {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.alertShow = AlertShow(id: "upgrade_1", content: .upgrade(result.upgrade_prompt, result.upgrade_address))
|
||||||
|
}
|
||||||
|
} else if result.upgrade_type == 2 {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.alertShow = AlertShow(id: "upgrade_1", content: .upgrade(result.upgrade_prompt, result.upgrade_address))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let error = response.error {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.alertShow = AlertShow(id: "response_error", content: .error(error.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} label: {
|
||||||
|
Text("版本检测")
|
||||||
|
.font(.system(size: 16, weight: .regular))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.cornerRadius(5.0)
|
||||||
|
}
|
||||||
|
.frame(width: 138, height: 33)
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
.background(Color(red: 74 / 255, green: 207 / 255, blue: 154 / 255))
|
||||||
|
.cornerRadius(5.0)
|
||||||
|
|
||||||
|
}
|
||||||
|
.alert(item: $alertShow) { show in
|
||||||
|
switch show.content {
|
||||||
|
case .error(let errorMessage):
|
||||||
|
Alert(title: Text("错误提示"), message: Text(errorMessage))
|
||||||
|
case .upgrade(let prompt, let address):
|
||||||
|
Alert(title: Text("版本升级"), message: Text(prompt), primaryButton: .default(Text("升级版本"), action: {
|
||||||
|
if let url = URL(string: address) {
|
||||||
|
// schema: "macappstore://apps.apple.com/app/idYOUR_APP_ID"
|
||||||
|
NSWorkspace.shared.open(url)
|
||||||
|
}
|
||||||
|
}), secondaryButton: .cancel())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
21
punchnet/Assets.xcassets/logo.imageset/Contents.json
vendored
Normal file
21
punchnet/Assets.xcassets/logo.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "logo.jpg",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
punchnet/Assets.xcassets/logo.imageset/logo.jpg
vendored
Normal file
BIN
punchnet/Assets.xcassets/logo.imageset/logo.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@ -1,18 +1,101 @@
|
|||||||
//
|
//
|
||||||
// ContentView.swift
|
// ContentView.swift
|
||||||
// punchnet
|
// sdlan
|
||||||
//
|
//
|
||||||
// Created by 安礼成 on 2025/5/12.
|
// Created by 安礼成 on 2024/1/17.
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SwiftData
|
import SwiftData
|
||||||
|
import Combine
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@Environment(\.modelContext) private var modelContext
|
//@Environment(\.modelContext) private var modelContext
|
||||||
@Query private var items: [Item]
|
//@Query private var items: [Item]
|
||||||
|
|
||||||
|
@AppStorage("token") private var token: String = ""
|
||||||
|
@ObservedObject private var vpnManager = VPNManager.shared
|
||||||
|
@State private var showAlert = false
|
||||||
|
@State private var showStunAlert = false
|
||||||
|
@State private var message: NoticeMessage.InboundMessage = .none
|
||||||
|
@State private var cancel: AnyCancellable?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
|
VStack(alignment: .center, spacing: 10) {
|
||||||
|
|
||||||
|
Image("logo")
|
||||||
|
|
||||||
|
Text("PUNCHENT")
|
||||||
|
.font(.system(size: 46, weight: .bold))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.cornerRadius(5.0)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
.frame(width: 1, height: 10)
|
||||||
|
|
||||||
|
TextField("邀请码", text: $token)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.frame(width: 245, height: 27)
|
||||||
|
.cornerRadius(5.0)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
.frame(width: 1, height: 10)
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
Task {
|
||||||
|
switch self.vpnManager.vpnStatus {
|
||||||
|
case .connected:
|
||||||
|
try await vpnManager.disableVpn()
|
||||||
|
case .disconnected:
|
||||||
|
if self.token.isEmpty {
|
||||||
|
self.showAlert = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try await vpnManager.enableVpn(options: [
|
||||||
|
"version:": SystemConfig.version as NSObject,
|
||||||
|
"installed_channel": SystemConfig.installedChannel as NSObject,
|
||||||
|
"token": self.token as NSObject
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, label: {
|
||||||
|
Text(vpnManager.title)
|
||||||
|
.font(.system(size: 16, weight: .regular))
|
||||||
|
.foregroundColor(vpnManager.color)
|
||||||
|
.cornerRadius(5.0)
|
||||||
|
})
|
||||||
|
.frame(width: 138, height: 33)
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
.background(Color(red: 74 / 255, green: 207 / 255, blue: 154 / 255))
|
||||||
|
.cornerRadius(5.0)
|
||||||
|
}
|
||||||
|
.frame(width: 380, height: 560)
|
||||||
|
.background(Color(red: 36 / 255, green: 38 / 255, blue: 51 / 255))
|
||||||
|
.alert(isPresented: $showAlert) {
|
||||||
|
Alert(title: Text("请输入正确的邀请码"))
|
||||||
|
}
|
||||||
|
.alert(isPresented: $showStunAlert) {
|
||||||
|
switch self.message {
|
||||||
|
case .upgradeMessage(let upgradeMessage):
|
||||||
|
Alert(title: Text(upgradeMessage.prompt))
|
||||||
|
case .alertMessage(let alertMessage):
|
||||||
|
Alert(title: Text(alertMessage.alert))
|
||||||
|
default:
|
||||||
|
Alert(title: Text(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
self.cancel = UDPNoticeCenterServer.shared.messageFlow.sink{ message in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.message = message
|
||||||
|
self.showStunAlert = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
NavigationSplitView {
|
NavigationSplitView {
|
||||||
List {
|
List {
|
||||||
ForEach(items) { item in
|
ForEach(items) { item in
|
||||||
@ -35,25 +118,26 @@ struct ContentView: View {
|
|||||||
} detail: {
|
} detail: {
|
||||||
Text("Select an item")
|
Text("Select an item")
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private func addItem() {
|
private func addItem() {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
let newItem = Item(timestamp: Date())
|
|
||||||
modelContext.insert(newItem)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func deleteItems(offsets: IndexSet) {
|
private func deleteItems(offsets: IndexSet) {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
for index in offsets {
|
// for index in offsets {
|
||||||
modelContext.delete(items[index])
|
// modelContext.delete(items[index])
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ContentView()
|
ContentView()
|
||||||
.modelContainer(for: Item.self, inMemory: true)
|
//.modelContainer(for: Item.self, inMemory: true)
|
||||||
}
|
}
|
||||||
|
|||||||
50
punchnet/Core/NoticeMessage.swift
Normal file
50
punchnet/Core/NoticeMessage.swift
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
//
|
||||||
|
// NoticeMessage.swift
|
||||||
|
// sdlan
|
||||||
|
//
|
||||||
|
// Created by 安礼成 on 2024/6/3.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct NoticeMessage {
|
||||||
|
// 消息类型
|
||||||
|
enum NoticeType: UInt8 {
|
||||||
|
case upgrade = 1
|
||||||
|
case alert = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UpgradeMessage: Codable {
|
||||||
|
let prompt: String
|
||||||
|
let address: String
|
||||||
|
|
||||||
|
var binaryData: Data {
|
||||||
|
let json = try! JSONEncoder().encode(self)
|
||||||
|
var data = Data()
|
||||||
|
data.append(contentsOf: [NoticeType.upgrade.rawValue])
|
||||||
|
data.append(json)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AlertMessage: Codable {
|
||||||
|
let alert: String
|
||||||
|
|
||||||
|
var binaryData: Data {
|
||||||
|
let json = try! JSONEncoder().encode(self)
|
||||||
|
var data = Data()
|
||||||
|
data.append(contentsOf: [NoticeType.alert.rawValue])
|
||||||
|
data.append(json)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum InboundMessage {
|
||||||
|
case none
|
||||||
|
case upgradeMessage(UpgradeMessage)
|
||||||
|
case alertMessage(AlertMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
47
punchnet/Core/SDLAPI.swift
Normal file
47
punchnet/Core/SDLAPI.swift
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// SDLApi.swift
|
||||||
|
// sdlan
|
||||||
|
//
|
||||||
|
// Created by 安礼成 on 2024/6/5.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct JSONRPCResponse<T: Decodable>: Decodable {
|
||||||
|
let result: T?
|
||||||
|
let error: JSONRPCError?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct JSONRPCError: Decodable {
|
||||||
|
let code: Int
|
||||||
|
let message: String
|
||||||
|
let data: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SDLAPI {
|
||||||
|
|
||||||
|
struct Upgrade: Decodable {
|
||||||
|
let upgrade_type: Int
|
||||||
|
let upgrade_prompt: String
|
||||||
|
let upgrade_address: String
|
||||||
|
}
|
||||||
|
|
||||||
|
static func checkVersion(clientId: String, version: Int, channel: String) async throws -> JSONRPCResponse<Upgrade> {
|
||||||
|
let params: [String:Any] = [
|
||||||
|
"client_id": clientId,
|
||||||
|
"version": version,
|
||||||
|
"channel": channel
|
||||||
|
]
|
||||||
|
|
||||||
|
let postData = try! JSONSerialization.data(withJSONObject: params)
|
||||||
|
var request = URLRequest(url: URL(string: "http://127.0.0.1:18082/test/upgrade")!)
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||||
|
request.httpBody = postData
|
||||||
|
|
||||||
|
let (data, _) = try await URLSession.shared.data(for: request)
|
||||||
|
|
||||||
|
return try JSONDecoder().decode(JSONRPCResponse<Upgrade>.self, from: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
18
punchnet/Core/SystemConfig.swift
Normal file
18
punchnet/Core/SystemConfig.swift
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// SystemConfig.swift
|
||||||
|
// sdlan
|
||||||
|
//
|
||||||
|
// Created by 安礼成 on 2024/6/3.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct SystemConfig {
|
||||||
|
// 版本设置
|
||||||
|
static let version = 1
|
||||||
|
|
||||||
|
static let version_name = "1.1"
|
||||||
|
|
||||||
|
// 安装渠道
|
||||||
|
static let installedChannel = "MacAppStore"
|
||||||
|
}
|
||||||
86
punchnet/Core/UDPNoticeCenterServer.swift
Normal file
86
punchnet/Core/UDPNoticeCenterServer.swift
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
//
|
||||||
|
// UDPMessageCenterServer.swift
|
||||||
|
// sdlan
|
||||||
|
//
|
||||||
|
// Created by 安礼成 on 2024/5/20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import NIOCore
|
||||||
|
import NIOPosix
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
final class UDPNoticeCenterServer: ChannelInboundHandler {
|
||||||
|
public typealias InboundIn = AddressedEnvelope<ByteBuffer>
|
||||||
|
public typealias OutboundOut = AddressedEnvelope<ByteBuffer>
|
||||||
|
|
||||||
|
private var group: MultiThreadedEventLoopGroup?
|
||||||
|
private var thread: Thread?
|
||||||
|
|
||||||
|
var messageFlow = PassthroughSubject<NoticeMessage.InboundMessage, Never>()
|
||||||
|
static let shared = UDPNoticeCenterServer()
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func start() {
|
||||||
|
self.thread = Thread {
|
||||||
|
self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
||||||
|
let bootstrap = DatagramBootstrap(group: self.group!)
|
||||||
|
.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
|
||||||
|
.channelInitializer { channel in
|
||||||
|
channel.pipeline.addHandler(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel = try! bootstrap.bind(host: "127.0.0.1", port: 50195).wait()
|
||||||
|
try! channel.closeFuture.wait()
|
||||||
|
}
|
||||||
|
self.thread?.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop() {
|
||||||
|
self.thread?.cancel()
|
||||||
|
try? self.group?.syncShutdownGracefully()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --MARK: ChannelInboundHandler
|
||||||
|
|
||||||
|
public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
||||||
|
let envelope = self.unwrapInboundIn(data)
|
||||||
|
var buffer = envelope.data
|
||||||
|
guard let type = buffer.readInteger(as: UInt8.self),
|
||||||
|
let noticeType = NoticeMessage.NoticeType(rawValue: type),
|
||||||
|
let bytes = buffer.readBytes(length: buffer.readableBytes) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch noticeType {
|
||||||
|
case .upgrade:
|
||||||
|
if let upgradeMessage = try? JSONDecoder().decode(NoticeMessage.UpgradeMessage.self, from: Data(bytes)) {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.messageFlow.send(.upgradeMessage(upgradeMessage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .alert:
|
||||||
|
if let alertMessage = try? JSONDecoder().decode(NoticeMessage.AlertMessage.self, from: Data(bytes)) {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.messageFlow.send(.alertMessage(alertMessage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func channelReadComplete(context: ChannelHandlerContext) {
|
||||||
|
// As we are not really interested getting notified on success or failure we just pass nil as promise to
|
||||||
|
// reduce allocations.
|
||||||
|
context.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func errorCaught(context: ChannelHandlerContext, error: Error) {
|
||||||
|
// As we are not really interested getting notified on success or failure we just pass nil as promise to
|
||||||
|
// reduce allocations.
|
||||||
|
context.close(promise: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
35
punchnet/Extension/DataExtension.swift
Normal file
35
punchnet/Extension/DataExtension.swift
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
//
|
||||||
|
// DataExtension.swift
|
||||||
|
// sdlan
|
||||||
|
//
|
||||||
|
// Created by 安礼成 on 2024/2/1.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Data {
|
||||||
|
|
||||||
|
mutating public func append(int32val: Int32) {
|
||||||
|
self.append(contentsOf: [
|
||||||
|
(UInt8) (int32val >> 24 & 0xFF),
|
||||||
|
(UInt8) (int32val >> 16 & 0xFF),
|
||||||
|
(UInt8) (int32val >> 8 & 0xFF),
|
||||||
|
(UInt8) (int32val & 0xFF)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating public func append(int16val: Int16) {
|
||||||
|
self.append(contentsOf: [
|
||||||
|
(UInt8) (int16val >> 8 & 0xFF),
|
||||||
|
(UInt8) (int16val & 0xFF)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating public func append(str: String) {
|
||||||
|
if let data = str.data(using: .utf8) {
|
||||||
|
self.append(contentsOf: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
98
punchnet/VPNManager.swift
Normal file
98
punchnet/VPNManager.swift
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
//
|
||||||
|
// VPNManager.swift
|
||||||
|
// sdlan
|
||||||
|
//
|
||||||
|
// Created by 安礼成 on 2024/1/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import NetworkExtension
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
// vpn管理类
|
||||||
|
class VPNManager: ObservableObject {
|
||||||
|
static let shared = VPNManager()
|
||||||
|
|
||||||
|
@Published var vpnStatus: VPNStatus = .disconnected
|
||||||
|
@Published var title: String = "启动"
|
||||||
|
@Published var color: Color = .white
|
||||||
|
|
||||||
|
enum VPNStatus {
|
||||||
|
case connected
|
||||||
|
case disconnected
|
||||||
|
}
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开启vpn
|
||||||
|
func enableVpn(options: [String : NSObject]? = nil) async throws {
|
||||||
|
let manager = try await loadAndCreateProviderManager()
|
||||||
|
try await manager.loadFromPreferences()
|
||||||
|
self.addVPNStatusObserver(manager)
|
||||||
|
|
||||||
|
try manager.connection.startVPNTunnel(options: options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭vpn
|
||||||
|
func disableVpn() async throws {
|
||||||
|
let managers = try await NETunnelProviderManager.loadAllFromPreferences()
|
||||||
|
managers.first?.connection.stopVPNTunnel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private Methods
|
||||||
|
|
||||||
|
// 监控系统VPN的状态的变化
|
||||||
|
private func addVPNStatusObserver(_ manager: NETunnelProviderManager) {
|
||||||
|
NotificationCenter.default.removeObserver(self)
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: manager.connection, queue: .main) { [unowned self] (notification) -> Void in
|
||||||
|
// 更新vpn的状态
|
||||||
|
switch manager.connection.status {
|
||||||
|
case .invalid, .disconnected, .disconnecting:
|
||||||
|
self.vpnStatus = .disconnected
|
||||||
|
self.title = "启动"
|
||||||
|
self.color = .white
|
||||||
|
case .connecting, .connected, .reasserting:
|
||||||
|
self.vpnStatus = .connected
|
||||||
|
self.title = "停止"
|
||||||
|
self.color = .red
|
||||||
|
@unknown default:
|
||||||
|
self.vpnStatus = .disconnected
|
||||||
|
self.title = "启动"
|
||||||
|
self.color = .red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private Methods
|
||||||
|
|
||||||
|
// 加载或者创建相关的配置
|
||||||
|
private func loadAndCreateProviderManager() async throws -> NETunnelProviderManager {
|
||||||
|
let managers = try await NETunnelProviderManager.loadAllFromPreferences()
|
||||||
|
|
||||||
|
let manager = managers.first ?? NETunnelProviderManager()
|
||||||
|
manager.localizedDescription = "sdlan"
|
||||||
|
manager.isEnabled = true
|
||||||
|
|
||||||
|
// 设置相关参数,在PacketTunnel中可以用
|
||||||
|
let protocolConfiguration = NETunnelProviderProtocol()
|
||||||
|
protocolConfiguration.serverAddress = "sdlan"
|
||||||
|
protocolConfiguration.providerConfiguration = [String:AnyObject]()
|
||||||
|
protocolConfiguration.providerBundleIdentifier = "com.jihe.sdlan.Tun"
|
||||||
|
manager.protocolConfiguration = protocolConfiguration
|
||||||
|
manager.isOnDemandEnabled = false
|
||||||
|
|
||||||
|
manager.isEnabled = true
|
||||||
|
|
||||||
|
try await manager.saveToPreferences()
|
||||||
|
|
||||||
|
return manager
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
NotificationCenter.default.removeObserver(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
28
punchnet/sdlan.entitlements
Normal file
28
punchnet/sdlan.entitlements
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?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>com.apple.developer.networking.networkextension</key>
|
||||||
|
<array>
|
||||||
|
<string>packet-tunnel-provider</string>
|
||||||
|
</array>
|
||||||
|
<key>com.apple.developer.networking.vpn.api</key>
|
||||||
|
<array>
|
||||||
|
<string>allow-vpn</string>
|
||||||
|
</array>
|
||||||
|
<key>com.apple.developer.system-extension.install</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>$(TeamIdentifierPrefix)</string>
|
||||||
|
</array>
|
||||||
|
<key>com.apple.security.files.user-selected.read-only</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.server</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
117
punchnet/sdlanApp.swift
Normal file
117
punchnet/sdlanApp.swift
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
//
|
||||||
|
// sdlanApp.swift
|
||||||
|
// sdlan
|
||||||
|
//
|
||||||
|
// Created by 安礼成 on 2024/1/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import AppKit
|
||||||
|
import SwiftData
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct sdlanApp: App {
|
||||||
|
/*
|
||||||
|
var sharedModelContainer: ModelContainer = {
|
||||||
|
let schema = Schema([
|
||||||
|
Item.self,
|
||||||
|
])
|
||||||
|
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
|
||||||
|
|
||||||
|
do {
|
||||||
|
return try ModelContainer(for: schema, configurations: [modelConfiguration])
|
||||||
|
} catch {
|
||||||
|
fatalError("Could not create ModelContainer: \(error)")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Environment(\.openWindow) private var openWindow
|
||||||
|
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||||
|
|
||||||
|
@AppStorage("token") var token: String = ""
|
||||||
|
@ObservedObject var vpnManager = VPNManager.shared
|
||||||
|
|
||||||
|
var body: some Scene {
|
||||||
|
WindowGroup(id: "mainWindow") {
|
||||||
|
ContentView()
|
||||||
|
}
|
||||||
|
.commands {
|
||||||
|
CommandGroup(replacing: .appInfo) {
|
||||||
|
Button {
|
||||||
|
openWindow(id: "abortSDLAN")
|
||||||
|
} label: {
|
||||||
|
Text("About sdlan")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Window("", id: "abortSDLAN") {
|
||||||
|
AbortView()
|
||||||
|
}
|
||||||
|
|
||||||
|
//.modelContainer(sharedModelContainer)
|
||||||
|
MenuBarExtra("sdlanApp", systemImage: "hammer") {
|
||||||
|
VStack {
|
||||||
|
Button(action: {
|
||||||
|
self.menuClick()
|
||||||
|
}, label: {
|
||||||
|
Text(vpnManager.title)
|
||||||
|
})
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
NSApplication.shared.terminate(nil)
|
||||||
|
}, label: { Text("退出") })
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.menuBarExtraStyle(.menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func menuClick() {
|
||||||
|
switch self.vpnManager.vpnStatus {
|
||||||
|
case .disconnected:
|
||||||
|
if token.isEmpty {
|
||||||
|
let windows = NSApplication.shared.windows
|
||||||
|
if let window = windows.first(where: {$0.title == "sdlan"}) {
|
||||||
|
window.level = .floating
|
||||||
|
} else {
|
||||||
|
self.openWindow(id: "mainWindow")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Task {
|
||||||
|
try await vpnManager.enableVpn(options: ["token": token as NSObject])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .connected:
|
||||||
|
Task {
|
||||||
|
try await vpnManager.disableVpn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理APP的生命周期
|
||||||
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
|
|
||||||
|
func applicationWillFinishLaunching(_ notification: Notification) {
|
||||||
|
UDPNoticeCenterServer.shared.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
|
||||||
|
Task {
|
||||||
|
try await VPNManager.shared.disableVpn()
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
sender.reply(toApplicationShouldTerminate: true)
|
||||||
|
}
|
||||||
|
UDPNoticeCenterServer.shared.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
return .terminateLater
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user