Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Siri Shortcuts integration for iOS #8

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a0fe44d
Automatic configuration files changes by Xcode
alessionossa Jan 28, 2022
3c63c38
project: Added WireGuardIntentsExtensioniOS target and Siri capability
alessionossa Jan 28, 2022
0dd0672
project: Added sources to WireGuardIntentsExtensioniOS
alessionossa Jan 29, 2022
ab1e96f
Implemented GetPeers intent
alessionossa Jan 29, 2022
ceabb4e
WireguardApp: iOS: Moved tunnelsManager initialization to AppDelegate
alessionossa Jan 29, 2022
fe3f2d0
Implemented UpdateConfiguration intent
alessionossa Feb 1, 2022
bb6ea1b
Implemented SetTunnelStatus intent
alessionossa Feb 1, 2022
c29787f
WireguardApp: iOS: Added Siri shortcuts donations for SetTunnelStatus…
alessionossa Feb 1, 2022
63c0956
Removed SetTunnelStatus
alessionossa Apr 1, 2023
7ec6974
Complete cleanup after SetTunnelStatus Intent removal
alessionossa Apr 1, 2023
2952206
Remove GetPeers SiriKit Intent
alessionossa Apr 5, 2023
bee5d34
Implement GetPeers AppIntent
alessionossa Apr 11, 2023
d05a169
Removed UpdateConfigurationIntent SiriKit Intent
alessionossa Apr 5, 2023
c1d7199
Remove WireGuardIntentsExtension
alessionossa Apr 5, 2023
a0a6f26
WireguardApp: iOS: Cleanup after SiriKit Intents removal
alessionossa Apr 11, 2023
1cb0653
WireguardApp: Add async variant of modify tunnel function
alessionossa Apr 11, 2023
ba250fe
Implement UpdateConfiguration AppIntent with Dictionary as input
alessionossa Apr 11, 2023
eec11c1
Remove UpdateConfiguration Intent with Dictionary input
alessionossa Apr 11, 2023
e13bc40
Implement BuildPeerConfigurationUpdate App Intent
alessionossa Apr 11, 2023
ee03536
Implement UpdateTunnelConfiguration App Intent
alessionossa Apr 11, 2023
97bf1c1
WireguardApp: macOS: Add App Intents to macOS app
alessionossa Apr 11, 2023
7abdf6e
Fix return value of AppIntents in iOS 17
alessionossa Feb 27, 2024
56c4d6c
Remove missing peers error in UpdateTunnelConfiguration Intent
alessionossa Feb 27, 2024
7da272a
Rename constant to satisfy SwiftLint
alessionossa Feb 27, 2024
06ed6e9
Update AppIntents Strings
alessionossa Feb 27, 2024
8a2a4eb
Add backwards compatibility of GetPeers wit SiriKit version
alessionossa Feb 27, 2024
2f99f5c
Remove backward compatibility of GetPeers with SiriKit version
alessionossa Feb 27, 2024
a37ae8a
Merge branch 'master' into an/shortcuts-integration
alessionossa Feb 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Sources/WireGuardApp/Tunnel/TunnelsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ class TunnelsManager {
}
}

@available(*, renamed: "modify(tunnel:tunnelConfiguration:onDemandOption:shouldEnsureOnDemandEnabled:)")
func modify(tunnel: TunnelContainer, tunnelConfiguration: TunnelConfiguration,
onDemandOption: ActivateOnDemandOption,
shouldEnsureOnDemandEnabled: Bool = false,
Expand Down Expand Up @@ -299,6 +300,22 @@ class TunnelsManager {
}
}

@available(iOS 13.0, macOS 10.15.0, *)
func modify(tunnel: TunnelContainer, tunnelConfiguration: TunnelConfiguration,
onDemandOption: ActivateOnDemandOption,
shouldEnsureOnDemandEnabled: Bool = false) async throws {
return try await withCheckedThrowingContinuation { continuation in
modify(tunnel: tunnel, tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption, shouldEnsureOnDemandEnabled: shouldEnsureOnDemandEnabled) { error in
if let error = error {
continuation.resume(throwing: error)
return
}

continuation.resume(returning: ())
}
}
}

func remove(tunnel: TunnelContainer, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
let tunnelProviderManager = tunnel.tunnelProvider
#if os(macOS)
Expand Down
28 changes: 28 additions & 0 deletions Sources/WireGuardApp/UI/iOS/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import UIKit
import os.log
import AppIntents

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
Expand All @@ -11,6 +12,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var mainVC: MainViewController?
var isLaunchedForSpecificAction = false

var tunnelsManager: TunnelsManager?

static let tunnelsManagerReadyNotificationName: Notification.Name = Notification.Name(rawValue: "com.wireguard.ios.tunnelsManagerReadyNotification")

func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Logger.configureGlobal(tagged: "APP", withFilePath: FileManager.logFileURL?.path)

Expand All @@ -29,6 +34,29 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

self.mainVC = mainVC

// Create the tunnels manager, and when it's ready, inform tunnelsListVC
TunnelsManager.create { [weak self] result in
guard let self = self else { return }

switch result {
case .failure(let error):
ErrorPresenter.showErrorAlert(error: error, from: self.mainVC)
case .success(let tunnelsManager):
self.tunnelsManager = tunnelsManager
self.mainVC?.tunnelsListVC?.setTunnelsManager(tunnelsManager: tunnelsManager)

tunnelsManager.activationDelegate = self.mainVC

if #available(iOS 16.0, *) {
AppDependencyManager.shared.add(dependency: tunnelsManager)
}

NotificationCenter.default.post(name: AppDelegate.tunnelsManagerReadyNotificationName,
object: self,
userInfo: nil)
}
}

return true
}

Expand Down
17 changes: 9 additions & 8 deletions Sources/WireGuardApp/UI/iOS/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
Expand Down Expand Up @@ -64,8 +64,6 @@
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleDisplayName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
Expand All @@ -74,17 +72,22 @@
<string>$(VERSION_NAME)</string>
<key>CFBundleVersion</key>
<string>$(VERSION_ID)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<false/>
<key>NSCameraUsageDescription</key>
<string>Localized</string>
<key>NSFaceIDUsageDescription</key>
<string>Localized</string>
<key>NSUserActivityTypes</key>
<array/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
</array>
<array/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
Expand Down Expand Up @@ -123,8 +126,6 @@
</dict>
</dict>
</array>
<key>NSFaceIDUsageDescription</key>
<string>Localized</string>
<key>com.wireguard.ios.app_group_id</key>
<string>group.$(APP_ID_IOS)</string>
</dict>
Expand Down
33 changes: 15 additions & 18 deletions Sources/WireGuardApp/UI/iOS/ViewController/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import UIKit

class MainViewController: UISplitViewController {

var tunnelsManager: TunnelsManager?
var tunnelsManager: TunnelsManager? {
return (UIApplication.shared.delegate as? AppDelegate)?.tunnelsManager
}
var onTunnelsManagerReady: ((TunnelsManager) -> Void)?
var tunnelsListVC: TunnelsListTableViewController?

Expand Down Expand Up @@ -38,29 +40,24 @@ class MainViewController: UISplitViewController {
// On iPad, always show both masterVC and detailVC, even in portrait mode, like the Settings app
preferredDisplayMode = .allVisible

// Create the tunnels manager, and when it's ready, inform tunnelsListVC
TunnelsManager.create { [weak self] result in
guard let self = self else { return }

switch result {
case .failure(let error):
ErrorPresenter.showErrorAlert(error: error, from: self)
case .success(let tunnelsManager):
self.tunnelsManager = tunnelsManager
self.tunnelsListVC?.setTunnelsManager(tunnelsManager: tunnelsManager)

tunnelsManager.activationDelegate = self

self.onTunnelsManagerReady?(tunnelsManager)
self.onTunnelsManagerReady = nil
}
}
NotificationCenter.default.addObserver(self, selector: #selector(handleTunnelsManagerReady(_:)),
name: AppDelegate.tunnelsManagerReadyNotificationName, object: nil)
}

func allTunnelNames() -> [String]? {
guard let tunnelsManager = self.tunnelsManager else { return nil }
return tunnelsManager.mapTunnels { $0.name }
}

@objc
func handleTunnelsManagerReady(_ notification: Notification) {
guard let tunnelsManager = self.tunnelsManager else { return }

self.onTunnelsManagerReady?(tunnelsManager)
self.onTunnelsManagerReady = nil

NotificationCenter.default.removeObserver(self, name: AppDelegate.tunnelsManagerReadyNotificationName, object: nil)
}
}

extension MainViewController: TunnelsManagerActivationDelegate {
Expand Down
5 changes: 5 additions & 0 deletions Sources/WireGuardApp/UI/macOS/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import Cocoa
import ServiceManagement
import AppIntents

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
Expand Down Expand Up @@ -58,6 +59,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
self.tunnelsTracker = tunnelsTracker
self.statusItemController = statusItemController

if #available(macOS 13.0, *) {
AppDependencyManager.shared.add(dependency: tunnelsManager)
}

if !isLaunchedAtLogin {
self.showManageTunnelsWindow(completion: nil)
}
Expand Down
35 changes: 35 additions & 0 deletions Sources/WireguardAppIntents/AppIntents.strings
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2021 WireGuard LLC. All Rights Reserved.

// App Intents Common
"wireguardAppIntentsWrongTunnelError %@" = "Cannot find a tunnel with the name \"%1$@\"";
"wireguardAppIntentsMissingConfigurationError" = "The selected tunnel has no configuration.";

// Get peers Intent
"getPeersIntentName" = "Get Peers";
"getPeersIntentDescription" = "Get list of public keys of peers in the selected configuration";
"getPeersIntentTunnelParameterTitle" = "Tunnel";
"getPeersIntentSummary ${tunnelName}" = "Get peers of ${tunnelName}";

// Tunnel Configuration Update
"updateTunnelConfigurationIntentName" = "Update Tunnel Configuration";
"updateTunnelConfigurationDescription" = "Update peers configuration of the selected tunnel.";
"updateTunnelConfigurationIntentTunnelParameterTitle" = "Tunnel";
"updateTunnelConfigurationIntentPeersParameterTitle" = "Peers";
"updateTunnelConfigurationIntentMergeParameterTitle" = "Merge configuration";
"updateTunnelConfigurationIntentSummary ${tunnelName}" = "Update ${tunnelName} configuration";

"updateTunnelConfigurationIntentPeerOptionsUnavailableError" = "Use the output of \"Build Peer Configuration\" action to update tunnel configuration. To update multiple peers at once, you can use the \"Add to Variable\" action and pass the resulting Variable in this field.";
"updateTunnelConfigurationIntentMalformedPublicKeyError %@" = "The key \"%1$@\" is not a valid Public Key encoded in Base64 format.";

// Build Peer Configuration
"buildPeerConfigurationUpdateIntentName" = "Build Peer Configuration";
"buildPeerConfigurationUpdateIntentDescription" = "Build an item that contains the informations needed to update an peer's configuration.";
"buildPeerConfigurationUpdateIntentSummary ${publicKey}" = "Build configuration for peer ${publicKey}";
"buildPeerConfigurationUpdateIntentPublicKeyParameterTitle" = "Peer Public Key";
"buildPeerConfigurationUpdateIntentEndpointParameterTitle" = "Endpoint";

// Peer Configuration Update Entity
"peerConfigurationUpdateEntityName" = "Configuration Update - Peer";
"peerConfigurationUpdateEntityPropertyPublicKeyTitle" = "Public Key";
"peerConfigurationUpdateEntityPropertyEndpointTitle" = "Endpoint";
77 changes: 77 additions & 0 deletions Sources/WireguardAppIntents/BuildPeerConfigurationUpdate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2021 WireGuard LLC. All Rights Reserved.

import AppIntents

@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *)
struct BuildPeerConfigurationUpdate: AppIntent {

static var title = LocalizedStringResource("buildPeerConfigurationUpdateIntentName", table: "AppIntents")
static var description = IntentDescription(
LocalizedStringResource("buildPeerConfigurationUpdateIntentDescription", table: "AppIntents")
)

@Parameter(
title: LocalizedStringResource("buildPeerConfigurationUpdateIntentPublicKeyParameterTitle", table: "AppIntents")
)
var publicKey: String

@Parameter(
title: LocalizedStringResource("buildPeerConfigurationUpdateIntentEndpointParameterTitle", table: "AppIntents")
)
var endpoint: String

func perform() async throws -> some IntentResult & ReturnsValue<AppIntentsPeer> {
let peerConfigurationUpdate = AppIntentsPeer()
peerConfigurationUpdate.publicKey = publicKey
peerConfigurationUpdate.endpoint = endpoint

return .result(value: peerConfigurationUpdate)
}

static var parameterSummary: some ParameterSummary {
Summary("buildPeerConfigurationUpdateIntentSummary \(\.$publicKey)", table: "AppIntents") {
\.$endpoint
}
}
}

@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *)
struct AppIntentsPeer: TransientAppEntity {

static let kEndpointConfigUpdateDictionaryKey = "Endpoint"

static var typeDisplayRepresentation = TypeDisplayRepresentation(
name: LocalizedStringResource("peerConfigurationUpdateEntityName", table: "AppIntents")
)

@Property(
title: LocalizedStringResource("peerConfigurationUpdateEntityPropertyPublicKeyTitle", table: "AppIntents")
)
var publicKey: String

@Property(
title: LocalizedStringResource("peerConfigurationUpdateEntityPropertyEndpointTitle", table: "AppIntents")
)
var endpoint: String?

var displayRepresentation: DisplayRepresentation {
var dictionary: [String: [String: String]] = [:]
dictionary[publicKey] = [:]

if let endpoint {
dictionary[publicKey]?.updateValue(endpoint, forKey: Self.kEndpointConfigUpdateDictionaryKey)
}

let jsonData: Data
do {
jsonData = try JSONSerialization.data(withJSONObject: dictionary)
} catch {
return DisplayRepresentation(stringLiteral: error.localizedDescription)
}

let jsonString = String(data: jsonData, encoding: .utf8)!

return DisplayRepresentation(stringLiteral: jsonString)
}
}
55 changes: 55 additions & 0 deletions Sources/WireguardAppIntents/GetPeers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2021 WireGuard LLC. All Rights Reserved.

import Foundation
import AppIntents

@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *)
struct GetPeers: AppIntent {

static var title = LocalizedStringResource("getPeersIntentName", table: "AppIntents")
static var description = IntentDescription(
LocalizedStringResource("getPeersIntentDescription", table: "AppIntents")
)

@Parameter(
title: LocalizedStringResource("getPeersIntentTunnelParameterTitle", table: "AppIntents"),
optionsProvider: TunnelsOptionsProvider()
)
var tunnelName: String

@Dependency
var tunnelsManager: TunnelsManager

func perform() async throws -> some IntentResult & ReturnsValue<[String]> {
guard let tunnelContainer = tunnelsManager.tunnel(named: tunnelName) else {
throw GetPeersIntentError.wrongTunnel(name: tunnelName)
}

guard let tunnelConfiguration = tunnelContainer.tunnelConfiguration else {
throw GetPeersIntentError.missingConfiguration
}

let publicKeys = tunnelConfiguration.peers.map { $0.publicKey.base64Key }
return .result(value: publicKeys)
}

static var parameterSummary: some ParameterSummary {
Summary("getPeersIntentSummary \(\.$tunnelName)", table: "AppIntents")
}
}

@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *)
enum GetPeersIntentError: Swift.Error, CustomLocalizedStringResourceConvertible {
case wrongTunnel(name: String)
case missingConfiguration

var localizedStringResource: LocalizedStringResource {
switch self {
case .wrongTunnel(let name):
return LocalizedStringResource("wireguardAppIntentsWrongTunnelError \(name)", table: "AppIntents")
case .missingConfiguration:
return LocalizedStringResource("wireguardAppIntentsMissingConfigurationError", table: "AppIntents")
}
}
}
15 changes: 15 additions & 0 deletions Sources/WireguardAppIntents/TunnelsOptionsProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2021 WireGuard LLC. All Rights Reserved.

import AppIntents

@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *)
struct TunnelsOptionsProvider: DynamicOptionsProvider {
@Dependency
var tunnelsManager: TunnelsManager

func results() async throws -> [String] {
let tunnelsNames = tunnelsManager.mapTunnels { $0.name }
return tunnelsNames
}
}
Loading