I am trying to search for UPnP devices in my network by sending an SSDP discovery packet. I can send them but I do not receive responses from devices and I do not know what I am missing. Maybe someone here can help me out. Goal is not to use any external libaries (pods, spm, ...).
Here is what I have:
import UIKit
import Network
extension NWEndpoint.Port {
static let ssdp: NWEndpoint.Port = 1_900
}
extension NWEndpoint.Host {
static let ssdp: NWEndpoint.Host = "239.255.255.250"
}
class ViewController: UIViewController {
private let parameters: NWParameters = {
let parameters: NWParameters = .udp
parameters.allowLocalEndpointReuse = true
parameters.allowFastOpen = true
return parameters
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
do {
try setupConnection()
} catch {
print(error.localizedDescription)
}
}
private func setupConnection() throws {
let endpoints: [NWEndpoint] = [
.hostPort(host: .ssdp, port: .ssdp)
]
let multicast = try NWMulticastGroup(for: endpoints)
let group = NWConnectionGroup(with: multicast, using: parameters)
group.setReceiveHandler(maximumMessageSize: 16384, rejectOversizedMessages: true) { (message, content, isComplete) in
print("Received message from \(String(describing: message.remoteEndpoint))")
}
group.stateUpdateHandler = { [weak viewController = self] (state) in
guard let viewController = viewController else {
return
}
switch state {
case .setup:
print("Setup")
case .waiting(let error):
let message = String(format: "Waiting: %#", error.localizedDescription)
print(message)
case .ready:
print("Ready")
viewController.send(to: group)
case .failed(let error):
let message = String(format: "Failed: %#", error.localizedDescription)
print(message)
case .cancelled:
print("Cancelled")
#unknown default:
print("Unknown group state")
}
}
group.start(queue: .main)
}
private func send(to group: NWConnectionGroup) {
let message = "M-SEARCH * HTTP/1.1\r\nST: ssdp:all\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nMX: 1\r\n\r\n"
let payload = message.data(using: .utf8)
group.send(content: payload) { error in
let message: String
if let error = error {
message = String(format: "Send completed with error %#", error.localizedDescription)
} else {
message = String(format: "Send completed")
}
print(message)
}
}
}
any help is appreciated! thank you in the meantime.
Edit: This does not work either
import UIKit
import Network
extension NWEndpoint.Port {
static let ssdp: NWEndpoint.Port = 1_900
}
extension NWEndpoint.Host {
static let ssdp: NWEndpoint.Host = "239.255.255.250"
}
class ViewController: UIViewController {
private let connectionQueue = DispatchQueue(label: "Connection")
private let listenerQueue = DispatchQueue(label: "Listener")
private let parameters: NWParameters = {
let parameters: NWParameters = .udp
parameters.allowLocalEndpointReuse = true
return parameters
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
do {
try setupConnection()
} catch {
print(error.localizedDescription)
}
}
private func setupConnection() throws {
let listener = try NWListener(using: parameters)
listener.stateUpdateHandler = { state in
print("l-state", state)
}
listener.newConnectionHandler = { (connection) in
print("connection", connection)
}
listener.start(queue: listenerQueue)
let connection = NWConnection(host: .ssdp, port: .ssdp, using: parameters)
connection.stateUpdateHandler = { [weak viewController = self] state in
print("c-state", state)
guard let viewController = viewController else {
return
}
switch state {
case .ready:
viewController.send(to: connection)
default: return
}
}
connection.start(queue: connectionQueue)
}
private func send(to connection: NWConnection) {
let message = "M-SEARCH * HTTP/1.1\r\nSt: ssdp:all\r\nHost: 239.255.255.250:1900\r\nMan: \"ssdp:discover\"\r\nMX: 1\r\n\r\n"
let payload = message.data(using: .utf8)
connection.send(content: payload, completion: .contentProcessed { error in
if let error = error {
print(error.localizedDescription)
} else {
print("ok")
}
})
connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { data, context, isComplete, error in
print("data", data ?? "")
}
}
}
Related
I've logged in with firebase in my iOS app and I have the Google Sheets API pod initialized in the podfile but I'm getting this error when I try to append data to my spreadsheet:
Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential.
Here is my spreadsheets code:
//
// SpredsheetsController.swift
// frcscout
//
// Created by Elliot Scher on 12/21/22
//
import UIKit
import GoogleAPIClientForREST
import GoogleSignIn
class SpreadsheetsController: UIViewController {
let utils = Utils()
let sheetService = GTLRSheetsService()
override func viewDidLoad() {
super.viewDidLoad()
sheetService.authorizer = GIDSignIn.sharedInstance.currentUser?.authentication.fetcherAuthorizer()
}
#IBAction func appendDataPressed(_ sender: UIButton) {
appendData()
}
#IBAction func specificCellPressed(_ sender: UIButton) {
sendDataToCell()
}
#IBAction func readDataPressed(_ sender: UIButton) {
readData()
}
#IBAction func readSheetsPressed(_ sender: UIButton) {
readSheets()
}
}
extension SpreadsheetsController {
func appendData() {
let spreadsheetId = K.sheetID
let range = "A1:Q"
let rangeToAppend = GTLRSheets_ValueRange.init();
let data = ["this", "is", "a", "test"]
rangeToAppend.values = [data]
let query = GTLRSheetsQuery_SpreadsheetsValuesAppend.query(withObject: rangeToAppend, spreadsheetId: spreadsheetId, range: range)
query.valueInputOption = "USER_ENTERED"
sheetService.executeQuery(query) { (ticket, result, error) in
if let error = error {
print("Error in appending data: \(error)")
} else {
print("Data sent: \(data)")
}
}
}
func sendDataToCell() {
let spreadsheetId = K.sheetID
let currentRange = "A5:B5" //Any range on the sheet, for instance: A5:B6
let results = ["this is a test"]
let rangeToAppend = GTLRSheets_ValueRange.init();
rangeToAppend.values = [results]
let query = GTLRSheetsQuery_SpreadsheetsValuesUpdate.query(withObject: rangeToAppend, spreadsheetId: spreadsheetId, range: currentRange)
query.valueInputOption = "USER_ENTERED"
sheetService.executeQuery(query) { (ticket, result, error) in
if let error = error {
print(error)
} else {
print("Sending: \(results)")
}
}
}
func readData() {
print("Getting sheet data...")
let spreadsheetId = K.sheetID
let range = "A1:Q"
let query = GTLRSheetsQuery_SpreadsheetsValuesGet
.query(withSpreadsheetId: spreadsheetId, range:range)
sheetService.executeQuery(query) { (ticket, result, error) in
if let error = error {
print(error)
return
}
guard let result = result as? GTLRSheets_ValueRange else {
return
}
let rows = result.values!
var stringRows = rows as! [[String]]
for row in stringRows {
stringRows.append(row)
print(row)
}
if rows.isEmpty {
print("No data found.")
return
}
print("Number of rows in sheet: \(rows.count)")
}
}
func readSheets() {
print("func findSpreadNameAndSheets executing...")
let spreadsheetId = K.sheetID
let query = GTLRSheetsQuery_SpreadsheetsGet.query(withSpreadsheetId: spreadsheetId)
sheetService.executeQuery(query) { (ticket, result, error) in
if let error = error {
print(error)
} else {
let result = result as? GTLRSheets_Spreadsheet
let sheets = result?.sheets
if let sheetInfo = sheets {
for info in sheetInfo {
print("New sheet found: \(String(describing: info.properties?.title))")
}
}
}
}
}
}
Here is my authentication code:
//
// AuthenticationViewModel.swift
// frcscout
//
// Created by Elliot Scher on 12/15/22.
//
import Foundation
import Firebase
import GoogleSignIn
class AuthenticationViewModel: ObservableObject {
var credential:AuthCredential? = nil
enum SignInState {
case signedIn
case signedOut
}
#Published var state: SignInState = .signedOut
func signIn() {
// 1
if GIDSignIn.sharedInstance.hasPreviousSignIn() {
GIDSignIn.sharedInstance.restorePreviousSignIn { [unowned self] user, error in
authenticateUser(for: user, with: error)
}
} else {
guard let clientID = FirebaseApp.app()?.options.clientID else { return }
let configuration = GIDConfiguration(clientID: clientID)
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return }
guard let rootViewController = windowScene.windows.first?.rootViewController else { return }
GIDSignIn.sharedInstance.signIn(with: configuration, presenting: rootViewController) { [unowned self] user, error in
authenticateUser(for: user, with: error)
}
}
}
private func authenticateUser(for user: GIDGoogleUser?, with error: Error?) {
if let error = error {
print(error.localizedDescription)
return
}
guard let authentication = user?.authentication, let idToken = authentication.idToken else { return }
credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: authentication.accessToken)
Auth.auth().signIn(with: credential!) { [unowned self] (_, error) in
if let error = error {
print(error.localizedDescription)
} else {
self.state = .signedIn
}
}
}
func signOut() {
GIDSignIn.sharedInstance.signOut()
do {
try Auth.auth().signOut()
state = .signedOut
} catch {
print(error.localizedDescription)
}
}
func getCredential() -> AuthCredential {
return credential!
}
}
I think I need an oauth2 token but I'm not sure how to get it from firebase. Could someone help me resolve this issue? Thanks!!
Resolved:
I had to put this line:
sheetService.authorizer = GIDSignIn.sharedInstance.currentUser?.authentication.fetcherAuthorizer()
at the beginning of each method.
I am trying to connect VPN using OpenVPNAdapter but the PacketTunnelProvider isn't called from the controller. What am i missing here?
Controller.swift
import NetworkExtension
var providerManager: NETunnelProviderManager!
var provider = PacketTunnelProvider()
override func viewDidLoad() {
super.viewDidLoad()
self.loadProviderManager {
self.configureVPN(response: self.arrResponse[0], serverAddress: self.arrResponse[0].iP, username: "vpn", password: "vpn")
}
}
func loadProviderManager(completion:#escaping () -> Void) {
NETunnelProviderManager.loadAllFromPreferences { (managers, error) in
if error == nil {
self.providerManager = managers?.first ?? NETunnelProviderManager()
completion()
}
}
}
func configureVPN(response:Response,serverAddress: String, username: String, password: String) {
let data = Data(base64Encoded:response.openVPNConfigDataBase64, options: .ignoreUnknownCharacters)
print(data!)
let decodedString = String(data: data!, encoding: .utf8)!
print(decodedString)
self.providerManager?.loadFromPreferences { error in
if error == nil {
let tunnelProtocol = NETunnelProviderProtocol()
tunnelProtocol.username = username
tunnelProtocol.serverAddress = serverAddress
tunnelProtocol.providerBundleIdentifier = "***.*****.********.***********.********"
tunnelProtocol.providerConfiguration = ["ovpn": data!, "username": username, "password": password]
tunnelProtocol.disconnectOnSleep = false
self.providerManager.protocolConfiguration = tunnelProtocol
self.providerManager.localizedDescription = "SMVPN"
self.providerManager.isEnabled = true
self.providerManager.saveToPreferences(completionHandler: { (error) in
if error == nil {
self.providerManager.loadFromPreferences(completionHandler: { (error) in
if error == nil {
self.provider.startTunnel(options: nil) { error in //this called here not in network extension
if error != nil {
print(error!)
}else {
}
}
}else {
print(error!.localizedDescription)
}
})
}
})
}
}
}
Project Entitlement
PacketTunnelProvider.swift
import NetworkExtension
import OpenVPNAdapter
class PacketTunnelProvider: NEPacketTunnelProvider {
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
var vpnReachability = OpenVPNReachability()
var configuration: OpenVPNConfiguration!
var properties: OpenVPNConfigurationEvaluation!
var UDPSession: NWUDPSession!
var TCPConnection: NWTCPConnection!
lazy var vpnAdapter: OpenVPNAdapter = {
let adapter = OpenVPNAdapter()
adapter.delegate = self
return adapter
}()
override func startTunnel(options: [String : NSObject]?, completionHandler: #escaping (Error?) -> Void) {
// Add code here to start the process of connecting the tunnel.
guard
let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration
else {
fatalError()
}
guard let ovpnFileContent: Data = providerConfiguration["ovpn"] as? Data else { return }
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnFileContent
do {
properties = try vpnAdapter.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
configuration.tunPersist = true
if !properties.autologin {
if let username: String = providerConfiguration["username"] as? String, let password: String = providerConfiguration["password"] as? String {
let credentials = OpenVPNCredentials()
credentials.username = username
credentials.password = password
do {
try vpnAdapter.provide(credentials: credentials)
} catch {
completionHandler(error)
return
}
}
}
vpnReachability.startTracking { [weak self] status in
guard status != .notReachable else { return }
self?.vpnAdapter.reconnect(afterTimeInterval: 5)
}
startHandler = completionHandler
vpnAdapter.connect(using: self)
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: #escaping () -> Void) {
// Add code here to start the process of stopping the tunnel.
stopHandler = completionHandler
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
vpnAdapter.disconnect()
completionHandler()
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
// Add code here to handle the message.
if let handler = completionHandler {
handler(messageData)
}
}
override func sleep(completionHandler: #escaping () -> Void) {
// Add code here to get ready to sleep.
completionHandler()
}
override func wake() {
// Add code here to wake up.
}
}
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?, completionHandler: #escaping (Error?) -> Void) {
setTunnelNetworkSettings(networkSettings) { (error) in
completionHandler(error == nil ? self.packetFlow as? Error : nil)
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?, completionHandler: #escaping (OpenVPNAdapterPacketFlow?) -> Void) {
setTunnelNetworkSettings(networkSettings) { (error) in
completionHandler(error == nil ? self.packetFlow : nil)
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleEvent event: OpenVPNAdapterEvent, message: String?) {
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool, fatal == true else {
return
}
NSLog("Error: \(error.localizedDescription)")
NSLog("Connection Info: \(vpnAdapter.connectionInformation.debugDescription)")
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler = startHandler {
startHandler(error)
self.startHandler = nil
} else {
cancelTunnelWithError(error)
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
NSLog("Log: \(logMessage)")
}
}
extension PacketTunnelProvider: OpenVPNAdapterPacketFlow {
func readPackets(completionHandler: #escaping ([Data], [NSNumber]) -> Void) {
packetFlow.readPackets(completionHandler: completionHandler)
}
func writePackets(_ packets: [Data], withProtocols protocols: [NSNumber]) -> Bool {
return packetFlow.writePackets(packets, withProtocols: protocols)
}
}
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
Extension Entitlement
self.provider.startTunnel(options: nil) { error in // This is called here, not in network extension
This method is called from controller but didn't get called in network extension.
I posted all my code, so if I missed something, then please let me know. Any help is appreciated.
I found this question but I haven't figured it out yet.
PacketTunnelProvider network extension not called Swift 3
You haven't mentioned anything about how you are testing network extension. You should attach a Network extension process before running your project for debugging. Then only network extension methods will trigger for debug.
Finally i found my mistake it's i tried to called network extension but it will never called. so i called NETunnelProviderManager directly and it will work.
self.providerManager.loadFromPreferences(completionHandler: { (error) in
if error == nil {
do {
try self.providerManager.connection.startVPNTunnel()
} catch let error {
print(error.localizedDescription)
}
}else {
print(error!.localizedDescription)
}
})
I am working on a personal project where I will be connecting the user to a VPN. I have followed two blog posts on doing this which are https://medium.com/better-programming/how-to-build-an-openvpn-client-on-ios-c8f927c11e80 & https://kean.blog/post/vpn-configuration-manager. I was able to configure the VPN settings but still, it does not connect. Here are the images for my project Signing & Capabilities one for the App and the other is for the network extension.
my ViewController looks like this. There is a folder in the user app document directory that is named user which gets downloaded from the internet and these are the files that are in the folder. I am not sure if I am supposed to use it other than the user.ovpn file
import UIKit
import Zip
import Alamofire
import NetworkExtension
class ViewController: UIViewController {
var selectedServer: Server?
var providerManager: NETunnelProviderManager!
override func viewDidLoad() {
super.viewDidLoad()
self.loadProviderManager {
self.configureVPN(serverAddress: "openvpn://\(self.selectedServer!.server.address):\(self.selectedServer!.server.port)", username: "\(self.username)", password: "\(self.password)")
}
}
func loadProviderManager(completion:#escaping () -> Void) {
NETunnelProviderManager.loadAllFromPreferences { (managers, error) in
if error == nil {
self.providerManager = managers?.first ?? NETunnelProviderManager()
completion()
}
}
}
func configureVPN(serverAddress: String, username: String, password: String) {
guard let configData = self.readFile(path: "user/user.ovpn") else { return }
self.providerManager?.loadFromPreferences { error in
if error == nil {
let tunnelProtocol = NETunnelProviderProtocol()
tunnelProtocol.username = username
tunnelProtocol.serverAddress = serverAddress
tunnelProtocol.providerBundleIdentifier = "com.example.Networking.tunnel"
tunnelProtocol.providerConfiguration = ["ovpn": configData, "username": username, "password": password]
tunnelProtocol.disconnectOnSleep = false
self.providerManager.protocolConfiguration = tunnelProtocol
self.providerManager.localizedDescription = "OpenVPN"
self.providerManager.isEnabled = true
self.providerManager.saveToPreferences(completionHandler: { (error) in
if error == nil {
self.providerManager.loadFromPreferences(completionHandler: { (error) in
do {
try self.providerManager.connection.startVPNTunnel()
} catch let error {
print(error.localizedDescription)
}
})
}
})
}
}
}
func readFile(path: String) -> Data? {
let fileManager = FileManager.default
do {
let documentDirectory = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let fileURL = documentDirectory.appendingPathComponent(path)
return try Data(contentsOf: fileURL, options: .uncached)
}
catch let error {
print(error.localizedDescription)
}
return nil
}
}
And my PacketTunnelProvider looks like this
import NetworkExtension
import OpenVPNAdapter
class PacketTunnelProvider: NEPacketTunnelProvider {
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
var vpnReachability = OpenVPNReachability()
var configuration: OpenVPNConfiguration!
var properties: OpenVPNProperties!
var UDPSession: NWUDPSession!
var TCPConnection: NWTCPConnection!
lazy var vpnAdapter: OpenVPNAdapter = {
let adapter = OpenVPNAdapter()
adapter.delegate = self
return adapter
}()
override func startTunnel(options: [String : NSObject]?, completionHandler: #escaping (Error?) -> Void) {
guard
let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration
else {
fatalError()
}
guard let ovpnFileContent: Data = providerConfiguration["ovpn"] as? Data else { return }
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnFileContent
do {
properties = try vpnAdapter.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
configuration.tunPersist = true
if !properties.autologin {
if let username: String = providerConfiguration["username"] as? String, let password: String = providerConfiguration["password"] as? String {
let credentials = OpenVPNCredentials()
credentials.username = username
credentials.password = password
do {
try vpnAdapter.provide(credentials: credentials)
} catch {
completionHandler(error)
return
}
}
}
vpnReachability.startTracking { [weak self] status in
guard status != .notReachable else { return }
self?.vpnAdapter.reconnect(afterTimeInterval: 5)
}
startHandler = completionHandler
vpnAdapter.connect()
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: #escaping () -> Void) {
stopHandler = completionHandler
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
vpnAdapter.disconnect()
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
if let handler = completionHandler {
handler(messageData)
}
}
override func sleep(completionHandler: #escaping () -> Void) {
completionHandler()
}
override func wake() {
}
}
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?, completionHandler: #escaping (OpenVPNAdapterPacketFlow?) -> Void) {
setTunnelNetworkSettings(networkSettings) { (error) in
completionHandler(error == nil ? self.packetFlow : nil)
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleEvent event: OpenVPNAdapterEvent, message: String?) {
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool, fatal == true else {
return
}
NSLog("Error: \(error.localizedDescription)")
NSLog("Connection Info: \(vpnAdapter.connectionInformation.debugDescription)")
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler = startHandler {
startHandler(error)
self.startHandler = nil
} else {
cancelTunnelWithError(error)
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
NSLog("Log: \(logMessage)")
}
}
extension PacketTunnelProvider: OpenVPNAdapterPacketFlow {
func readPackets(completionHandler: #escaping ([Data], [NSNumber]) -> Void) {
packetFlow.readPackets(completionHandler: completionHandler)
}
func writePackets(_ packets: [Data], withProtocols protocols: [NSNumber]) -> Bool {
return packetFlow.writePackets(packets, withProtocols: protocols)
}
}
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
in watchOS, i am just trying to get HR value in real-time while a workoutSession is running.
func startHeartRateQuery(updateHandler: #escaping ([HKQuantitySample]?) -> Void) {
guard let quantityType = HKObjectType.quantityType(forIdentifier: .heartRate) else {
return
}
let heartRateQuery = HKAnchoredObjectQuery(type: quantityType, predicate: nil, anchor: anchor, limit: Int(HKObjectQueryNoLimit)) { (query, sampleObjects, deletedObjects, newAnchor, error) -> Void in
guard let newAnchor = newAnchor else {return}
self.anchor = newAnchor
updateHandler(sampleObjects as? [HKQuantitySample])
}
heartRateQuery.updateHandler = {(query, samples, deleteObjects, newAnchor, error) -> Void in
self.anchor = newAnchor!
updateHandler(samples as? [HKQuantitySample])
}
healthStore.execute(heartRateQuery)
activeDataQueries.append(heartRateQuery)
}
And This is how i do start workoutSession.
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
guard let d = delegate else {
return
}
if let u = userInfo["status"] as? String {
d.receivedWorkoutRunningStatus(u)
}
if let c = userInfo["clock"] as? String {
d.receiveWorkoutRestPeriodClock(c)
}
}
You can find receivedWorkoutRunningStatus function on InterfaceController.
This is InterfaceController.swift which is first screen on Watch App.
class InterfaceController: WKInterfaceController, HKWorkoutSessionDelegate {
#IBOutlet var lblHeartRate: WKInterfaceLabel!
#IBOutlet var lblSplitIntervalNumber: WKInterfaceLabel!
#IBOutlet var lblRestPeriodClock: WKInterfaceLabel!
private let healthStoreManager = WatchHealthKitManager()
private let parentConnector = ParentConnector()
private var workoutSession: HKWorkoutSession!
override func awake(withContext context: Any?) {
super.awake(withContext: context)
// Create a workout session with the workout configuration
do {
let workoutConfiguration = HKWorkoutConfiguration()
workoutConfiguration.locationType = .indoor
workoutConfiguration.activityType = .rowing
workoutSession = try HKWorkoutSession(configuration: workoutConfiguration)
} catch {
fatalError(error.localizedDescription)
}
//// initial setup
parentConnector.activate()
parentConnector.delegate = self
workoutSession.delegate = self
}
// MARK: - Data Accumulation
private func startAccumulatingData() {
healthStoreManager.startHeartRateQuery() { quantitySamples in
DispatchQueue.main.async {
guard !self.isPaused() else {
return
}
guard let heartRateSamples = quantitySamples else {
return
}
let hrUnit = HKUnit(from: "count/min")
guard let sample = heartRateSamples.first else {
return
}
let value = sample.quantity.doubleValue(for: hrUnit)
self.updateHeartRate(value: value)
self.parentConnector.transfer(value: value)
}
}
}
func workoutSession(_ workoutSession: HKWorkoutSession,
didChangeTo toState: HKWorkoutSessionState,
from fromState: HKWorkoutSessionState,
date: Date) {
switch (toState) {
case .running:
startAccumulatingData()
case .ended:
stopAccumulatingData()
default:
print("Error")
}
}
func receivedWorkoutRunningStatus(_ status: String) {
if (status == "Start") {
healthStoreManager.start(workoutSession)
} else if (status == "Finish") {
healthStoreManager.end(workoutSession)
lblHeartRate.setText("No Active Workout")
}
DispatchQueue.main.async {
self.lblSplitIntervalNumber.setText(status)
}
}
On the iPhone App, I send a "Start" string using transferUserInfo function to trigger the workoutSession beginning. This is not working properly, it only sometimes works, it is very inconsistent.
I would appreciate if you have any advice or alternative approaches.
Thank you in advance.
Everytime I build an adhoc build and runs it on ios 11 it keeps popping up the message saying "Sign-In Required - Enter the password for ***** [Environment:Sandbox]". Thus, this keep activating like a loop and not going away. tried siging out from iCloud also but didn't work.My inAppPurchase code as bellow.
class InAppPurchase: NSObject,SKProductsRequestDelegate,
SKPaymentTransactionObserver {
///Singleton
private static var inappPurChase:InAppPurchase?;
public static var shared:InAppPurchase {
if inappPurChase == nil{
inappPurChase = InAppPurchase()
}
return inappPurChase!
}
override private init() {
//Singleton complete
}
////////////
var productsList = [SKProduct]()
var productToPurchase = SKProduct()
var productID = ""
let PURCHASE_ID_PREFIX = ""//"com.purelightbeta."
var onInAppPurchaseSuccess: (()->Void )? = nil
var onInAppPurchaseError: (()->Void )? = nil
var onNoProductsIDs: (()->Void )? = nil
var appEnvironment: ((String,Bool)->Void )? = nil
var onReceiptVarificationError: (()->Void )? = nil
var onRestorePurchaseError: ((String)->Void )? = nil
var onRestoreProductsIDs: ((NSMutableArray)->Void )? = nil
public func initPayment (productID: String) {
self.productID = PURCHASE_ID_PREFIX + productID
print("Initializing Purchase Product ID: \(self.productID)")
if SKPaymentQueue.canMakePayments() {
let productID: NSSet = NSSet(objects: self.productID)
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
}else {
print("In App Purchases not enabled")
}
}
func buyProduct() {
let payment = SKPayment(product: productToPurchase)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment as SKPayment)
}
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
let myProducts = response.products
print(myProducts)
productsList = []
if myProducts.count == 0 {
print("No products")
if(onNoProductsIDs != nil){
onNoProductsIDs!()
}
}
for product in myProducts {
print("Prod Details :\(product.productIdentifier) \(product.price) \(product.localizedDescription)")
productsList.append(product)
}
for product in productsList {
let productID = product.productIdentifier
if productID == self.productID {
productToPurchase = product
buyProduct()
}
}
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
let productIDs: NSMutableArray = []
for trasacton in queue.transactions {
if trasacton.transactionState == SKPaymentTransactionState.restored
{
let t: SKPaymentTransaction = trasacton as SKPaymentTransaction
let prodID = t.payment.productIdentifier as String
print("prodID:",prodID)
//let appStoreId = prodID.components(separatedBy: "com.purelightbeta.")
productIDs.add(prodID)
SKPaymentQueue .default().finishTransaction(trasacton)
}
}
if onRestoreProductsIDs != nil {
print(productIDs)
onRestoreProductsIDs! (productIDs)
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction: AnyObject in transactions {
let trans = transaction as! SKPaymentTransaction
print(trans.error ?? "No in app error")
switch trans.transactionState {
case .purchased:
print("buy ok, AIP unlocked for item :", productToPurchase.productIdentifier)
let productID = productToPurchase.productIdentifier
switch productID {
case self.productID:
print("Item found")
// startValidatingReceipt()
default:
print("Item not found")
}
queue.finishTransaction(trans)
if self.onInAppPurchaseSuccess != nil {
self.onInAppPurchaseSuccess! ()
self.startValidatingReceipt()
}
case .failed:
print("buy error")
if self.onInAppPurchaseError != nil {
self.onInAppPurchaseError! ()
}
queue.finishTransaction(trans)
break
case .restored:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
// break
default:
print("default")
break
}
}
}
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
print("ERROR: \(error.localizedDescription)")
if onRestorePurchaseError != nil {
onRestorePurchaseError! (error.localizedDescription)
}
}
func restorePurchase() {
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
func startValidatingReceipt() {
if let isExists = try? self.getReceiptURL()?.checkResourceIsReachable(), isExists == true {
do {
let data = try Data(contentsOf: self.getReceiptURL()!)
// self.startValidatingData(data: data)
self.sendReceiptToLocalServer(data: data)
}catch {
let appReceiptRefreshRequest = SKReceiptRefreshRequest(receiptProperties: nil)
appReceiptRefreshRequest.delegate = self
appReceiptRefreshRequest.start()
}
}else {
print("No receipt for this purchase")
}
}
func getReceiptURL() -> URL? {
return Bundle.main.appStoreReceiptURL
}
enum receiptValidationURLs:String {
case sandbox = "https://sandbox.itunes.apple.com/verifyReceipt"
case production = "https://buy.itunes.apple.com/verifyReceipt"
static var url:URL{
if isDebug {
return URL.init(string: self.sandbox.rawValue)!
}else{
return URL.init(string: self.production.rawValue)!
}
}
}
func sendReceiptToLocalServer(data:Data) {
let base64encodedReceipt = data.base64EncodedString()
if self.appEnvironment != nil {
self.appEnvironment! (base64encodedReceipt,isDebug)
}
}
func startValidatingData(data:Data){
let base64encodedReceipt = data.base64EncodedString()
print(base64encodedReceipt)
let requestDictionary = ["receipt-data":base64encodedReceipt]
guard JSONSerialization.isValidJSONObject(requestDictionary) else { print("requestDictionary is not valid JSON"); return }
do {
let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
// let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt" // this works but as noted above it's best to use your own trusted server
NSLog("The Environment is: %#", receiptValidationURLs.url.absoluteString)
guard let validationURL = URL(string: receiptValidationURLs.url.absoluteString) else { print("the validation url could not be created, unlikely error"); return }
let session = URLSession(configuration: URLSessionConfiguration.default)
var request = URLRequest(url: validationURL)
request.httpMethod = "POST"
request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringCacheData
let task = session.uploadTask(with: request, from: requestData) { (data, response, error) in
if let data = data , error == nil {
do {
let appReceiptJSON = try JSONSerialization.jsonObject(with: data)
print((appReceiptJSON as AnyObject).count)
print("success. here is the json representation of the app receipt: \(appReceiptJSON)")
NSLog("Receipt is: %#", appReceiptJSON as! NSDictionary)
if let environment = (appReceiptJSON as? NSDictionary)?["environment"]{
if self.appEnvironment != nil {
self.appEnvironment! (environment as? String ?? "No value",isDebug)
}
}
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
} else {
print("the upload task returned an error: \(error)")
}
}
task.resume()
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
}
// func requestDidFinish(_ request: SKRequest) {
// // a fresh receipt should now be present at the url
// do {
//
//
//
// } catch {
// // still no receipt, possible but unlikely to occur since this is the "success" delegate method
// }
// }
func request(_ request: SKRequest, didFailWithError error: Error) {
print("app receipt refresh request did fail with error: \(error)")
// for some clues see here: https://samritchie.net/2015/01/29/the-operation-couldnt-be-completed-sserrordomain-error-100/
if self.onReceiptVarificationError != nil {
self.onReceiptVarificationError! ()
}
}
}
This may not be the answer but would be a workaround. Best is to create a new sandbox user, sign out the old one and put the new.