diff --git a/.spm-version b/.spm-version index 6618ab54..cf869073 100644 --- a/.spm-version +++ b/.spm-version @@ -1 +1 @@ -2.17.0 \ No newline at end of file +2.18.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 08ef51ec..c6905598 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,8 @@ | モジュール名 | Description | 最新のバージョン | | :-- | :-- | :-- | -| KarteCore | イベントトラッキング機能を提供します。 | 2.29.0 | -| KarteInAppMessaging | アプリ内メッセージ機能を提供します。 | 2.18.0 | +| KarteCore | イベントトラッキング機能を提供します。 | 2.30.0 | +| KarteInAppMessaging | アプリ内メッセージ機能を提供します。 | 2.19.0 | | KarteRemoteNotification | プッシュ通知の受信および効果測定機能を提供します。 | 2.11.0 | | KarteVariables | 設定値配信機能を提供します。 | 2.10.0 | | KarteVisualTracking | ビジュアルトラッキング機能を提供します。 | 2.12.0 | @@ -11,6 +11,17 @@ | KarteUtilities | KarteCore モジュール等が利用するUtility機能を提供します。通常直接参照する必要はありません。 | 3.12.0 | | KarteNotificationServiceExtension | リッチプッシュ通知機能を提供します。 | 1.2.0 | +# Releases - 2024.12.05 +## Version 2.18.0 + +### Core 2.30.0 +** 🎉 FEATURE** +- Native機能呼び出しにATT許諾ダイアログ表示を追加しました。 + +### InAppMessaging 2.19.0 +** 🔨CHANGED** +- InAppMessagingDelegateの処理をMainActorに隔離しました。 + # Releases - 2024.08.26 ## Version 2.17.0 diff --git a/Karte.xcodeproj/project.pbxproj b/Karte.xcodeproj/project.pbxproj index 3e7ad43f..915e1788 100644 --- a/Karte.xcodeproj/project.pbxproj +++ b/Karte.xcodeproj/project.pbxproj @@ -434,6 +434,8 @@ AF0A5F2F29546BB000209D4E /* Logger+Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF0A5F2E29546BB000209D4E /* Logger+Tag.swift */; }; AF0A5F31295475B300209D4E /* KarteInbox.m in Sources */ = {isa = PBXBuildFile; fileRef = AF0A5F30295475B300209D4E /* KarteInbox.m */; }; AF0A5F352959986F00209D4E /* InboxLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = AF0A5F342959986F00209D4E /* InboxLoader.m */; }; + AF38FD9E2C7C7313009C1C20 /* RequestATTCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF38FD9D2C7C7313009C1C20 /* RequestATTCommand.swift */; }; + AF38FDA02C7C8108009C1C20 /* RequestATTCommandSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF38FD9F2C7C8108009C1C20 /* RequestATTCommandSpec.swift */; }; AF49FAA1296ED953005A7575 /* CallerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF49FAA0296ED953005A7575 /* CallerSpec.swift */; }; AF5EDEB329C1C49000D05825 /* OpenMessagesRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF5EDEB229C1C49000D05825 /* OpenMessagesRequestSpec.swift */; }; AF5EDEB529C1C5A600D05825 /* OpenMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF5EDEB429C1C5A600D05825 /* OpenMessagesRequest.swift */; }; @@ -1031,6 +1033,8 @@ AF0A5F2E29546BB000209D4E /* Logger+Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Tag.swift"; sourceTree = ""; }; AF0A5F30295475B300209D4E /* KarteInbox.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KarteInbox.m; sourceTree = ""; }; AF0A5F342959986F00209D4E /* InboxLoader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InboxLoader.m; sourceTree = ""; }; + AF38FD9D2C7C7313009C1C20 /* RequestATTCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestATTCommand.swift; sourceTree = ""; }; + AF38FD9F2C7C8108009C1C20 /* RequestATTCommandSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestATTCommandSpec.swift; sourceTree = ""; }; AF49FAA0296ED953005A7575 /* CallerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallerSpec.swift; sourceTree = ""; }; AF5EDEB229C1C49000D05825 /* OpenMessagesRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenMessagesRequestSpec.swift; sourceTree = ""; }; AF5EDEB429C1C5A600D05825 /* OpenMessagesRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenMessagesRequest.swift; sourceTree = ""; }; @@ -2255,6 +2259,7 @@ 0CE33A0523F40C2200E2EB98 /* UserSyncParameterSpec.swift */, 681E83982483B54800A95083 /* RequestReviewCommandSpec.swift */, 681E839A2483B5C800A95083 /* OpenSettingsCommandSpec.swift */, + AF38FD9F2C7C8108009C1C20 /* RequestATTCommandSpec.swift */, ); path = Core; sourceTree = ""; @@ -2383,6 +2388,7 @@ 681E83942482A90E00A95083 /* OpenSettingsCommand.swift */, 680DB3C72480F74E00AE6800 /* Command.swift */, 681E838C248246B700A95083 /* RequestReviewCommand.swift */, + AF38FD9D2C7C7313009C1C20 /* RequestATTCommand.swift */, 681E838E2482510200A95083 /* CommandHandler.swift */, 681E8390248252EE00A95083 /* CommandHandlerLoader.m */, ); @@ -3112,6 +3118,7 @@ 777D605D274267FC00339D7E /* InvalidEventFieldNameFilterRule.swift in Sources */, 0C69D40F24EB9DB200D75893 /* InitializationEventFilterRule.swift in Sources */, 0C0822FB24EA615C0038EBDA /* CommandBundlerApplicationStateProvider.swift in Sources */, + AF38FD9E2C7C7313009C1C20 /* RequestATTCommand.swift in Sources */, 0C83928F2406C6AA0014C2BF /* InstallationStatus.swift in Sources */, 0C69D3FC24EB9D9D00D75893 /* TrackClientState.swift in Sources */, 0C8392B72406C7010014C2BF /* Module.swift in Sources */, @@ -3380,6 +3387,7 @@ 0CE79327238653AD00F0D932 /* SpecConfiguration.swift in Sources */, 0CC648B8238CCFC3009EB5DF /* StubBuilder.swift in Sources */, 0CCFA7B524A5814100E0F184 /* FCMTokenRegistrarSpec.swift in Sources */, + AF38FDA02C7C8108009C1C20 /* RequestATTCommandSpec.swift in Sources */, 0C839316240838050014C2BF /* ActionAppropriateViewDetectorSpec.swift in Sources */, 363F25F12582A1EB007E6E4B /* DefinitionsRequestSpec.swift in Sources */, 0C520BCC2990995000480B77 /* JSONConvertibleSpec.swift in Sources */, @@ -3852,7 +3860,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 2.29.0; + MARKETING_VERSION = 2.30.0; PRODUCT_BUNDLE_IDENTIFIER = io.karte.KarteCore; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -3887,7 +3895,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 2.29.0; + MARKETING_VERSION = 2.30.0; PRODUCT_BUNDLE_IDENTIFIER = io.karte.KarteCore; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -3920,7 +3928,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 2.18.0; + MARKETING_VERSION = 2.19.0; PRODUCT_BUNDLE_IDENTIFIER = io.karte.KarteInAppMessaging; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -3953,7 +3961,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 2.18.0; + MARKETING_VERSION = 2.19.0; PRODUCT_BUNDLE_IDENTIFIER = io.karte.KarteInAppMessaging; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; diff --git a/KarteCore.podspec b/KarteCore.podspec index 2c080587..276233de 100644 --- a/KarteCore.podspec +++ b/KarteCore.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'KarteCore' - s.version = '2.29.0' + s.version = '2.30.0' s.summary = 'KARTE Core SDK' s.homepage = 'https://karte.io' s.author = { 'PLAID' => 'dev.share@plaid.co.jp' } diff --git a/KarteCore/Core/Command/CommandHandler.swift b/KarteCore/Core/Command/CommandHandler.swift index 992e42dd..bc7a20e8 100644 --- a/KarteCore/Core/Command/CommandHandler.swift +++ b/KarteCore/Core/Command/CommandHandler.swift @@ -62,7 +62,8 @@ extension CommandHandler: DeepLinkModule { public func handle(app: UIApplication, open url: URL) -> Bool { let commands: [Command] = [ RequestReviewCommand(), - OpenSettingsCommand() + OpenSettingsCommand(), + RequestATTCommand() ] return commands.map { $0.run(url: url) }.contains(true) } diff --git a/KarteCore/Core/Command/RequestATTCommand.swift b/KarteCore/Core/Command/RequestATTCommand.swift new file mode 100644 index 00000000..48ff435b --- /dev/null +++ b/KarteCore/Core/Command/RequestATTCommand.swift @@ -0,0 +1,49 @@ +// +// Copyright 2024 PLAID, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AppTrackingTransparency + +internal struct RequestATTCommand: Command { + func validate(_ url: URL) -> Bool { + url.host == "request-att" + } + + func execute() { + if #available(iOS 14.5, *) { + guard ATTrackingManager.trackingAuthorizationStatus == .notDetermined else { + Logger.warn(tag: .core, message: "ATT is already determined, status = \(ATTrackingManager.trackingAuthorizationStatus)") + return + } + + ATTrackingManager.requestTrackingAuthorization { status in + switch status { + case .authorized: + Logger.info(tag: .core, message: "ATT status is authorized") + case .denied: + Logger.info(tag: .core, message: "ATT status is denied") + case .notDetermined: + Logger.info(tag: .core, message: "ATT status is notDetermined") + case .restricted: + Logger.info(tag: .core, message: "ATT status is restricted") + @unknown default: + Logger.warn(tag: .core, message: "ATT status is unknown: \(status)") + } + } + } else { + Logger.warn(tag: .core, message: "iOS version must be over 14.5, ATT is not available") + } + } +} diff --git a/KarteInAppMessaging.podspec b/KarteInAppMessaging.podspec index 1e2a2c8f..c3d75482 100644 --- a/KarteInAppMessaging.podspec +++ b/KarteInAppMessaging.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'KarteInAppMessaging' - s.version = '2.18.0' + s.version = '2.19.0' s.summary = 'KARTE In-app messaging SDK' s.homepage = 'https://karte.io' s.author = { 'PLAID' => 'dev.share@plaid.co.jp' } diff --git a/KarteInAppMessaging/IAMProcess.swift b/KarteInAppMessaging/IAMProcess.swift index 2321c46a..29a06664 100644 --- a/KarteInAppMessaging/IAMProcess.swift +++ b/KarteInAppMessaging/IAMProcess.swift @@ -247,7 +247,13 @@ extension IAMProcess { private func handleJsMessageEvent(_ data: JsMessage.EventData) { Tracker.track(view: window, data: data) - notifyCampaignOpenOrClose(data) + if Thread.isMainThread { + notifyCampaignOpenOrClose(data) + } else { + DispatchQueue.main.async { + self.notifyCampaignOpenOrClose(data) + } + } } private func handleJsMessageOpenURL(_ data: JsMessage.OpenURLData) { @@ -347,6 +353,7 @@ extension IAMProcess: IAMWebViewDelegate { return true } + @MainActor func inAppMessagingWebView(_ webView: IAMWebView, shouldOpenURL url: URL) -> Bool { let iam = InAppMessaging.shared guard let delegate = iam.delegate else { diff --git a/KarteInAppMessaging/InAppMessaging.swift b/KarteInAppMessaging/InAppMessaging.swift index ee5408cd..3e79f511 100644 --- a/KarteInAppMessaging/InAppMessaging.swift +++ b/KarteInAppMessaging/InAppMessaging.swift @@ -222,7 +222,7 @@ extension InAppMessaging { } } - @objc + @MainActor @objc private func observeWindowDidBecomeVisibleNotification(_ notification: Notification) { guard let window = notification.object as? UIWindow else { return @@ -255,7 +255,7 @@ extension InAppMessaging { pool.storeProcess(process) } - @objc + @MainActor @objc private func observeWindowDidBecomeHiddenNotification(_ notification: Notification) { guard let window = notification.object as? UIWindow else { return diff --git a/KarteInAppMessaging/InAppMessagingDelegate.swift b/KarteInAppMessaging/InAppMessagingDelegate.swift index f5c75418..98cbcef8 100644 --- a/KarteInAppMessaging/InAppMessagingDelegate.swift +++ b/KarteInAppMessaging/InAppMessagingDelegate.swift @@ -17,7 +17,7 @@ import UIKit /// アプリ内メッセージで発生するイベントを委譲するためのタイプです。 -@objc(KRTInAppMessagingDelegate) +@preconcurrency @MainActor @objc(KRTInAppMessagingDelegate) public protocol InAppMessagingDelegate: AnyObject { /// アプリ内メッセージ用のWindowが表示されたことを通知します。 /// diff --git a/KarteInAppMessaging/View/IAMWebView.swift b/KarteInAppMessaging/View/IAMWebView.swift index 13d80daa..be742acd 100644 --- a/KarteInAppMessaging/View/IAMWebView.swift +++ b/KarteInAppMessaging/View/IAMWebView.swift @@ -172,34 +172,36 @@ internal class IAMWebView: WKWebView { } func openURL(_ url: URL?, isReset: Bool) { - guard let url = url else { - Logger.debug(tag: .inAppMessaging, message: "Can't open URL because URL is nil.") - return - } + DispatchQueue.main.async { + guard let url = url else { + Logger.debug(tag: .inAppMessaging, message: "Can't open URL because URL is nil.") + return + } - if isReset { - reset(mode: .soft) - } + if isReset { + self.reset(mode: .soft) + } - if let delegate = delegate, !delegate.inAppMessagingWebView(self, shouldOpenURL: url) { - Logger.info(tag: .inAppMessaging, message: "SDK delegates openURL to client app. URL=\(url)") - return - } + if let delegate = self.delegate, !delegate.inAppMessagingWebView(self, shouldOpenURL: url) { + Logger.info(tag: .inAppMessaging, message: "SDK delegates openURL to client app. URL=\(url)") + return + } - if #available(iOS 10.0, *) { - UIApplication.shared.open(url, options: [:]) { successful in - if successful { + if #available(iOS 10.0, *) { + UIApplication.shared.open(url, options: [:]) { successful in + if successful { + Logger.info(tag: .inAppMessaging, message: "Success to open URL: \(url)") + } else { + Logger.error(tag: .inAppMessaging, message: "Failed to open URL: \(url)") + } + } + } else { + if UIApplication.shared.openURL(url) { Logger.info(tag: .inAppMessaging, message: "Success to open URL: \(url)") } else { Logger.error(tag: .inAppMessaging, message: "Failed to open URL: \(url)") } } - } else { - if UIApplication.shared.openURL(url) { - Logger.info(tag: .inAppMessaging, message: "Success to open URL: \(url)") - } else { - Logger.error(tag: .inAppMessaging, message: "Failed to open URL: \(url)") - } } } diff --git a/KarteTests/KarteCoreTests/UnitTests/Core/RequestATTCommandSpec.swift b/KarteTests/KarteCoreTests/UnitTests/Core/RequestATTCommandSpec.swift new file mode 100644 index 00000000..1400d596 --- /dev/null +++ b/KarteTests/KarteCoreTests/UnitTests/Core/RequestATTCommandSpec.swift @@ -0,0 +1,61 @@ +// +// Copyright 2024 PLAID, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Quick +import Nimble +@testable import KarteCore + +class RequestATTCommandSpec: QuickSpec { + override func spec() { + describe("its run") { + context("when invalid value passed") { + let examples = [ + "test:", + "krt:", + "krt://request-att", + "aaa-karte-sdk://request-att", + "krt-hSZM://request-att", + "krt-hSZMcVyjwg6Y7pdYMa4YPqmyQ77EpALw://request-review", + ] + examples.forEach { (input) in + context("\(input)") { + it("returns false") { + let c = RequestATTCommand() + let u = URL(string: input)! + expect(c.run(url: u)).to(beFalse()) + } + } + } + } + + context("when valid value passed") { + let examples = [ + "krt-hSZMcVyjwg6Y7pdYMa4YPqmyQ77EpALw://request-att", + "krt-HRTwj9QEZGJrTaTkADrtdxFTyuXUJVMh://request-att" + ] + examples.forEach { (input) in + context("\(input)") { + it("returns true") { + let c = RequestATTCommand() + let u = URL(string: input)! + expect(c.run(url: u)).to(beTrue()) + } + } + } + } + } + } +} diff --git a/Package.swift b/Package.swift index acfb070d..acd29a8b 100644 --- a/Package.swift +++ b/Package.swift @@ -54,10 +54,10 @@ let package = Package( name: "KarteUtilities", url: "https://sdk.karte.io/ios/swiftpm/Utilities-3.12.0/KarteUtilities.xcframework.zip", checksum: "850abc71a8bc28e415f4b11e3072ff741a1d908c13b951a4676bc2e675eaf634" ), .binaryTarget( - name: "KarteCore", url: "https://sdk.karte.io/ios/swiftpm/Core-2.29.0/KarteCore.xcframework.zip", checksum: "5b4c1f3e7157a95e9fa6ff2227854a1c5cec76fb4b1571db787b1345e7efde61" + name: "KarteCore", url: "https://sdk.karte.io/ios/swiftpm/Core-2.30.0/KarteCore.xcframework.zip", checksum: "b4814553acd31092d29a50b75b34680484d55eece5c7152dca694590c9263c60" ), .binaryTarget( - name: "KarteInAppMessaging", url: "https://sdk.karte.io/ios/swiftpm/InAppMessaging-2.18.0/KarteInAppMessaging.xcframework.zip", checksum: "d1625e74bac54bd63744c6297b742bb781ea1ab0bf4a4f652fd765245094ffcf" + name: "KarteInAppMessaging", url: "https://sdk.karte.io/ios/swiftpm/InAppMessaging-2.19.0/KarteInAppMessaging.xcframework.zip", checksum: "b87a10043ac75d9c7ced8f7493bd8c8e8d4f590facc7dd51f843675157ecfad9" ), .binaryTarget( name: "KarteVariables", url: "https://sdk.karte.io/ios/swiftpm/Variables-2.10.0/KarteVariables.xcframework.zip", checksum: "19665bf1c9eb5e04719b660839de4ca1145589a62669de6f12efb38fc39a4aad"