From 73c6047b78852837cf7af7b65f6a908c25f00233 Mon Sep 17 00:00:00 2001 From: FabianLars Date: Mon, 11 Aug 2025 22:08:58 +0200 Subject: [PATCH] fix(nfc): use xcode project instead of swift package --- .../project.pbxproj | 319 +++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../ExamplePlugin.swift} | 0 plugins/nfc/{ios => iosold}/.gitignore | 0 plugins/nfc/{ios => iosold}/Package.swift | 0 plugins/nfc/{ios => iosold}/README.md | 0 plugins/nfc/iosold/Sources/NfcPlugin.swift | 523 ++++++++++++++++++ .../Tests/PluginTests/PluginTests.swift | 0 9 files changed, 857 insertions(+) create mode 100644 plugins/nfc/ios/tauri-plugin-nfc.xcodeproj/project.pbxproj create mode 100644 plugins/nfc/ios/tauri-plugin-nfc.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 plugins/nfc/ios/tauri-plugin-nfc.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename plugins/nfc/ios/{Sources/NfcPlugin.swift => tauri-plugin-nfc/ExamplePlugin.swift} (100%) rename plugins/nfc/{ios => iosold}/.gitignore (100%) rename plugins/nfc/{ios => iosold}/Package.swift (100%) rename plugins/nfc/{ios => iosold}/README.md (100%) create mode 100644 plugins/nfc/iosold/Sources/NfcPlugin.swift rename plugins/nfc/{ios => iosold}/Tests/PluginTests/PluginTests.swift (100%) diff --git a/plugins/nfc/ios/tauri-plugin-nfc.xcodeproj/project.pbxproj b/plugins/nfc/ios/tauri-plugin-nfc.xcodeproj/project.pbxproj new file mode 100644 index 00000000..966a4e34 --- /dev/null +++ b/plugins/nfc/ios/tauri-plugin-nfc.xcodeproj/project.pbxproj @@ -0,0 +1,319 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 60; + objects = { + +/* Begin PBXBuildFile section */ + A0E2115A2BF552D2003BCF4D /* ExamplePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0E211592BF552D2003BCF4D /* ExamplePlugin.swift */; }; + A0E211622BF55305003BCF4D /* Tauri in Frameworks */ = {isa = PBXBuildFile; productRef = A0E211612BF55305003BCF4D /* Tauri */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + A0E211542BF552D2003BCF4D /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + A0E211562BF552D2003BCF4D /* libtauri-plugin-nfc.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libtauri-plugin-nfc.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A0E211592BF552D2003BCF4D /* ExamplePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExamplePlugin.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A0E211532BF552D2003BCF4D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A0E211622BF55305003BCF4D /* Tauri in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A0E2114D2BF552D2003BCF4D = { + isa = PBXGroup; + children = ( + A0E211582BF552D2003BCF4D /* tauri-plugin-nfc */, + A0E211572BF552D2003BCF4D /* Products */, + ); + sourceTree = ""; + }; + A0E211572BF552D2003BCF4D /* Products */ = { + isa = PBXGroup; + children = ( + A0E211562BF552D2003BCF4D /* libtauri-plugin-nfc.a */, + ); + name = Products; + sourceTree = ""; + }; + A0E211582BF552D2003BCF4D /* tauri-plugin-nfc */ = { + isa = PBXGroup; + children = ( + A0E211592BF552D2003BCF4D /* ExamplePlugin.swift */, + ); + path = "tauri-plugin-nfc"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + A0E211552BF552D2003BCF4D /* tauri-plugin-nfc */ = { + isa = PBXNativeTarget; + buildConfigurationList = A0E2115D2BF552D2003BCF4D /* Build configuration list for PBXNativeTarget "tauri-plugin-nfc" */; + buildPhases = ( + A0E211522BF552D2003BCF4D /* Sources */, + A0E211532BF552D2003BCF4D /* Frameworks */, + A0E211542BF552D2003BCF4D /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "tauri-plugin-nfc"; + packageProductDependencies = ( + A0E211612BF55305003BCF4D /* Tauri */, + ); + productName = "tauri-plugin-nfc"; + productReference = A0E211562BF552D2003BCF4D /* libtauri-plugin-nfc.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A0E2114E2BF552D2003BCF4D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1540; + LastUpgradeCheck = 1540; + TargetAttributes = { + A0E211552BF552D2003BCF4D = { + CreatedOnToolsVersion = 15.4; + }; + }; + }; + buildConfigurationList = A0E211512BF552D2003BCF4D /* Build configuration list for PBXProject "tauri-plugin-nfc" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = A0E2114D2BF552D2003BCF4D; + packageReferences = ( + A0E211602BF55305003BCF4D /* XCLocalSwiftPackageReference "../.tauri/tauri-api" */, + ); + productRefGroup = A0E211572BF552D2003BCF4D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A0E211552BF552D2003BCF4D /* tauri-plugin-nfc */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + A0E211522BF552D2003BCF4D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A0E2115A2BF552D2003BCF4D /* ExamplePlugin.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A0E2115B2BF552D2003BCF4D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = 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_DOCUMENTATION_COMMENTS = YES; + 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + 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 = 13.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + A0E2115C2BF552D2003BCF4D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = 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_DOCUMENTATION_COMMENTS = YES; + 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + 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 = 13.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + A0E2115E2BF552D2003BCF4D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + A0E2115F2BF552D2003BCF4D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A0E211512BF552D2003BCF4D /* Build configuration list for PBXProject "tauri-plugin-nfc" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A0E2115B2BF552D2003BCF4D /* Debug */, + A0E2115C2BF552D2003BCF4D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A0E2115D2BF552D2003BCF4D /* Build configuration list for PBXNativeTarget "tauri-plugin-nfc" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A0E2115E2BF552D2003BCF4D /* Debug */, + A0E2115F2BF552D2003BCF4D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + A0E211602BF55305003BCF4D /* XCLocalSwiftPackageReference "../.tauri/tauri-api" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../.tauri/tauri-api"; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + A0E211612BF55305003BCF4D /* Tauri */ = { + isa = XCSwiftPackageProductDependency; + productName = Tauri; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = A0E2114E2BF552D2003BCF4D /* Project object */; +} diff --git a/plugins/nfc/ios/tauri-plugin-nfc.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/plugins/nfc/ios/tauri-plugin-nfc.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/plugins/nfc/ios/tauri-plugin-nfc.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/plugins/nfc/ios/tauri-plugin-nfc.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/plugins/nfc/ios/tauri-plugin-nfc.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/plugins/nfc/ios/tauri-plugin-nfc.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/plugins/nfc/ios/Sources/NfcPlugin.swift b/plugins/nfc/ios/tauri-plugin-nfc/ExamplePlugin.swift similarity index 100% rename from plugins/nfc/ios/Sources/NfcPlugin.swift rename to plugins/nfc/ios/tauri-plugin-nfc/ExamplePlugin.swift diff --git a/plugins/nfc/ios/.gitignore b/plugins/nfc/iosold/.gitignore similarity index 100% rename from plugins/nfc/ios/.gitignore rename to plugins/nfc/iosold/.gitignore diff --git a/plugins/nfc/ios/Package.swift b/plugins/nfc/iosold/Package.swift similarity index 100% rename from plugins/nfc/ios/Package.swift rename to plugins/nfc/iosold/Package.swift diff --git a/plugins/nfc/ios/README.md b/plugins/nfc/iosold/README.md similarity index 100% rename from plugins/nfc/ios/README.md rename to plugins/nfc/iosold/README.md diff --git a/plugins/nfc/iosold/Sources/NfcPlugin.swift b/plugins/nfc/iosold/Sources/NfcPlugin.swift new file mode 100644 index 00000000..af1a68d5 --- /dev/null +++ b/plugins/nfc/iosold/Sources/NfcPlugin.swift @@ -0,0 +1,523 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +// https://developer.apple.com/documentation/corenfc/building_an_nfc_tag-reader_app + +import CoreNFC +import SwiftRs +import Tauri +import UIKit +import WebKit + +enum ScanKind: Decodable { + case ndef, tag +} + +struct ScanOptions: Decodable { + let kind: ScanKind + var keepSessionAlive: Bool? + var message: String? + var successMessage: String? +} + +struct NDEFRecord: Decodable { + var format: UInt8? + var kind: [UInt8]? + var identifier: [UInt8]? + var payload: [UInt8]? +} + +struct WriteOptions: Decodable { + var kind: ScanKind? + let records: [NDEFRecord] + var message: String? + var successMessage: String? + var successfulReadMessage: String? +} + +enum TagProcessMode { + case write(message: NFCNDEFMessage) + case read +} + +class Session { + let nfcSession: NFCReaderSession? + let invoke: Invoke + var keepAlive: Bool + let tagProcessMode: TagProcessMode + var tagStatus: NFCNDEFStatus? + var tag: NFCNDEFTag? + let successfulReadMessage: String? + let successfulWriteAlertMessage: String? + + init( + nfcSession: NFCReaderSession?, + invoke: Invoke, + keepAlive: Bool, + tagProcessMode: TagProcessMode, + successfulReadMessage: String?, + successfulWriteAlertMessage: String? + ) { + self.nfcSession = nfcSession + self.invoke = invoke + self.keepAlive = keepAlive + self.tagProcessMode = tagProcessMode + self.successfulReadMessage = successfulReadMessage + self.successfulWriteAlertMessage = successfulWriteAlertMessage + } +} + +class NfcStatus { + let available: Bool + let errorReason: String? + + init(available: Bool, errorReason: String?) { + self.available = available + self.errorReason = errorReason + } +} + +class NfcPlugin: Plugin, NFCTagReaderSessionDelegate, NFCNDEFReaderSessionDelegate { + var session: Session? + var status: NfcStatus! + + public override func load(webview: WKWebView) { + var available = false + var errorReason: String? + + let entry = Bundle.main.infoDictionary?["NFCReaderUsageDescription"] as? String + + if entry == nil || entry?.count == 0 { + errorReason = "missing NFCReaderUsageDescription configuration on the Info.plist file" + } else if !NFCNDEFReaderSession.readingAvailable { + errorReason = + "NFC tag reading unavailable, make sure the Near-Field Communication capability on Xcode is enabled and the device supports NFC tag reading" + } else { + available = true + } + + if let error = errorReason { + Logger.error("\(error)") + } + + self.status = NfcStatus(available: available, errorReason: errorReason) + } + + func tagReaderSessionDidBecomeActive( + _ session: NFCTagReaderSession + ) { + Logger.info("tagReaderSessionDidBecomeActive") + } + + func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { + let tag = tags.first! + + session.connect( + to: tag, + completionHandler: { [self] (error) in + if let error = error { + self.closeSession(session, error: "cannot connect to tag: \(error)") + + } else { + let ndefTag: NFCNDEFTag + switch tag { + case let .feliCa(tag): + ndefTag = tag as NFCNDEFTag + break + case let .miFare(tag): + ndefTag = tag as NFCNDEFTag + break + case let .iso15693(tag): + ndefTag = tag as NFCNDEFTag + break + case let .iso7816(tag): + ndefTag = tag as NFCNDEFTag + break + default: + return + } + + self.processTag( + session: session, tag: ndefTag, metadata: tagMetadata(tag), + mode: self.session!.tagProcessMode) + } + } + ) + } + + func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) { + Logger.error("Tag reader session error \(error)") + self.session?.invoke.reject("session invalidated with error: \(error)") + self.session = nil + } + + func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) { + let message = messages.first! + // TODO: do we really need this hook? + self.session?.invoke.resolve(["records": ndefMessageRecords(message)]) + } + + func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) { + let tag = tags.first! + + session.connect( + to: tag, + completionHandler: { [self] (error) in + if let error = error { + self.closeSession(session, error: "cannot connect to tag: \(error)") + + } else { + var metadata: JsonObject = [:] + if tag.isKind(of: NFCFeliCaTag.self) { + metadata["kind"] = ["FeliCa"] + metadata["id"] = nil + } else if let t = tag as? NFCMiFareTag { + metadata["kind"] = ["MiFare"] + metadata["id"] = byteArrayFromData(t.identifier) + } else if let t = tag as? NFCISO15693Tag { + metadata["kind"] = ["ISO15693"] + metadata["id"] = byteArrayFromData(t.identifier) + } else if let t = tag as? NFCISO7816Tag { + metadata["kind"] = ["ISO7816Compatible"] + metadata["id"] = byteArrayFromData(t.identifier) + } + + self.processTag( + session: session, tag: tag, metadata: metadata, + mode: self.session!.tagProcessMode) + } + } + ) + + } + + func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) { + if (error as NSError).code + == NFCReaderError.Code.readerSessionInvalidationErrorFirstNDEFTagRead.rawValue + { + // not an error because we're using invalidateAfterFirstRead: true + Logger.debug("readerSessionInvalidationErrorFirstNDEFTagRead") + } else { + Logger.error("NDEF reader session error \(error)") + self.session?.invoke.reject("session invalidated with error: \(error)") + self.session = nil + } + } + + private func tagMetadata(_ tag: NFCTag) -> JsonObject { + var metadata: JsonObject = [:] + + switch tag { + case .feliCa: + metadata["kind"] = ["FeliCa"] + metadata["id"] = [] + break + case let .miFare(tag): + metadata["kind"] = ["MiFare"] + metadata["id"] = byteArrayFromData(tag.identifier) + break + case let .iso15693(tag): + metadata["kind"] = ["ISO15693"] + metadata["id"] = byteArrayFromData(tag.identifier) + break + case let .iso7816(tag): + metadata["kind"] = ["ISO7816Compatible"] + metadata["id"] = byteArrayFromData(tag.identifier) + break + default: + metadata["kind"] = ["Unknown"] + metadata["id"] = [] + break + } + + return metadata + } + + private func closeSession(_ session: NFCReaderSession) { + session.invalidate() + self.session = nil + } + + private func closeSession(_ session: NFCReaderSession, error: String) { + session.invalidate(errorMessage: error) + self.session = nil + } + + private func processTag( + session: NFCReaderSession, tag: T, metadata: JsonObject, mode: TagProcessMode + ) { + tag.queryNDEFStatus(completionHandler: { + [self] (status, capacity, error) in + if let error = error { + self.closeSession(session, error: "cannot connect to tag: \(error)") + } else { + switch mode { + case .write(let message): + self.writeNDEFTag( + session: session, status: status, tag: tag, message: message, + alertMessage: self.session?.successfulWriteAlertMessage) + break + case .read: + if self.session?.keepAlive == true { + self.session!.tagStatus = status + self.session!.tag = tag + } + self.readNDEFTag( + session: session, status: status, tag: tag, metadata: metadata, + alertMessage: self.session?.successfulReadMessage) + break + } + } + }) + } + + private func writeNDEFTag( + session: NFCReaderSession, status: NFCNDEFStatus, tag: T, message: NFCNDEFMessage, + alertMessage: String? + ) { + switch status { + case .notSupported: + self.closeSession(session, error: "Tag is not an NDEF-formatted tag") + break + case .readOnly: + self.closeSession(session, error: "Read only tag") + break + case .readWrite: + if let currentSession = self.session { + tag.writeNDEF( + message, + completionHandler: { (error) in + if let error = error { + self.closeSession(session, error: "cannot write to tag: \(error)") + } else { + if let message = alertMessage { + session.alertMessage = message + } + currentSession.invoke.resolve() + + self.closeSession(session) + + } + }) + } + break + default: + return + } + } + + private func readNDEFTag( + session: NFCReaderSession, status: NFCNDEFStatus, tag: T, metadata m: JsonObject, + alertMessage: String? + ) { + var metadata: JsonObject = [:] + metadata.merge(m) { (_, new) in new } + + switch status { + case .notSupported: + self.resolveInvoke(message: nil, metadata: metadata) + self.closeSession(session) + return + case .readOnly: + metadata["readOnly"] = true + break + case .readWrite: + metadata["readOnly"] = false + break + default: + break + } + + tag.readNDEF(completionHandler: { + [self] (message, error) in + if let error = error { + let code = (error as NSError).code + if code != 403 { + self.closeSession(session, error: "Failed to read: \(error)") + return + } + } + + if let message = alertMessage { + session.alertMessage = message + } + self.resolveInvoke(message: message, metadata: metadata) + + if self.session?.keepAlive != true { + self.closeSession(session) + } + }) + } + + private func resolveInvoke(message: NFCNDEFMessage?, metadata: JsonObject) { + var data: JsonObject = [:] + + data.merge(metadata) { (_, new) in new } + + if let message = message { + data["records"] = ndefMessageRecords(message) + } else { + data["records"] = [] + } + + self.session?.invoke.resolve(data) + } + + private func ndefMessageRecords(_ message: NFCNDEFMessage) -> [JsonObject] { + var records: [JsonObject] = [] + for record in message.records { + var recordJson: JsonObject = [:] + recordJson["tnf"] = record.typeNameFormat.rawValue + recordJson["kind"] = byteArrayFromData(record.type) + recordJson["id"] = byteArrayFromData(record.identifier) + recordJson["payload"] = byteArrayFromData(record.payload) + + records.append(recordJson) + } + + return records + } + + private func byteArrayFromData(_ data: Data) -> [UInt8] { + var arr: [UInt8] = [] + for b in data { + arr.append(b) + } + return arr + } + + private func dataFromByteArray(_ array: [UInt8]) -> Data { + var data = Data(capacity: array.count) + + data.append(contentsOf: array) + + return data + } + + @objc func isAvailable(_ invoke: Invoke) { + invoke.resolve([ + "available": self.status.available + ]) + } + + @objc public func write(_ invoke: Invoke) throws { + if !self.status.available { + invoke.reject("NFC reading unavailable: \(self.status.errorReason ?? "")") + return + } + + let args = try invoke.parseArgs(WriteOptions.self) + + var ndefPayloads = [NFCNDEFPayload]() + + for record in args.records { + ndefPayloads.append( + NFCNDEFPayload( + format: NFCTypeNameFormat(rawValue: record.format ?? 0) ?? .unknown, + type: dataFromByteArray(record.kind ?? []), + identifier: dataFromByteArray(record.identifier ?? []), + payload: dataFromByteArray(record.payload ?? []) + ) + ) + } + + if let session = self.session { + if let nfcSession = session.nfcSession, let tagStatus = session.tagStatus, + let tag = session.tag + { + session.keepAlive = false + self.writeNDEFTag( + session: nfcSession, status: tagStatus, tag: tag, + message: NFCNDEFMessage(records: ndefPayloads), + alertMessage: args.successMessage + ) + } else { + invoke.reject( + "connected tag not found, please wait for it to be available and then call write()") + } + } else { + self.startScanSession( + invoke: invoke, + kind: args.kind ?? .ndef, + keepAlive: true, + invalidateAfterFirstRead: false, + tagProcessMode: .write( + message: NFCNDEFMessage(records: ndefPayloads) + ), + alertMessage: args.message, + successfulReadMessage: args.successfulReadMessage, + successfulWriteAlertMessage: args.successMessage + ) + } + } + + @objc public func scan(_ invoke: Invoke) throws { + if !self.status.available { + invoke.reject("NFC reading unavailable: \(self.status.errorReason ?? "")") + return + } + + let args = try invoke.parseArgs(ScanOptions.self) + + self.startScanSession( + invoke: invoke, + kind: args.kind, + keepAlive: args.keepSessionAlive ?? false, + invalidateAfterFirstRead: true, + tagProcessMode: .read, + alertMessage: args.message, + successfulReadMessage: args.successMessage, + successfulWriteAlertMessage: nil + ) + } + + private func startScanSession( + invoke: Invoke, + kind: ScanKind, + keepAlive: Bool, + invalidateAfterFirstRead: Bool, + tagProcessMode: TagProcessMode, + alertMessage: String?, + successfulReadMessage: String?, + successfulWriteAlertMessage: String? + ) { + let nfcSession: NFCReaderSession? + + switch kind { + case .tag: + nfcSession = NFCTagReaderSession( + pollingOption: [.iso14443, .iso15693], + delegate: self, + queue: DispatchQueue.main + ) + break + case .ndef: + nfcSession = NFCNDEFReaderSession( + delegate: self, + queue: DispatchQueue.main, + invalidateAfterFirstRead: invalidateAfterFirstRead + ) + break + } + + if let message = alertMessage { + nfcSession?.alertMessage = message + } + nfcSession?.begin() + + self.session = Session( + nfcSession: nfcSession, + invoke: invoke, + keepAlive: keepAlive, + tagProcessMode: tagProcessMode, + successfulReadMessage: successfulReadMessage, + successfulWriteAlertMessage: successfulWriteAlertMessage + ) + } +} + +@_cdecl("init_plugin_nfc") +func initPlugin() -> Plugin { + return NfcPlugin() +} diff --git a/plugins/nfc/ios/Tests/PluginTests/PluginTests.swift b/plugins/nfc/iosold/Tests/PluginTests/PluginTests.swift similarity index 100% rename from plugins/nfc/ios/Tests/PluginTests/PluginTests.swift rename to plugins/nfc/iosold/Tests/PluginTests/PluginTests.swift