From 6e21892baa2857024fb7227c5598a79d96445aa6 Mon Sep 17 00:00:00 2001 From: Sergei Semko <28645140+justSmK@users.noreply.github.com> Date: Fri, 29 Nov 2024 13:32:34 +0300 Subject: [PATCH 1/4] MBX-3751: First test implementation --- Example/Example.xcodeproj/project.pbxproj | 106 ++------- Example/Example/AppDelegate.swift | 12 +- Example/Example/Models/Item+SwiftData.swift | 35 --- Example/Example/Models/Payload.swift | 14 -- Example/Example/SceneDelegate.swift | 5 +- .../Example/Services/SwiftDataManager.swift | 117 ---------- .../ChooseInAppMessagesDelegate.swift | 46 ---- .../Example/ViewModels/MainViewModel.swift | 69 ------ .../NotificationCenterViewModel.swift | 70 ------ Example/Example/ViewModels/ViewModel.swift | 154 +++++++++++++ Example/Example/Views/ContentView.swift | 95 ++++++++ .../Views/CustomViews/ButtonsView.swift | 55 ----- .../Views/CustomViews/SDKDataView.swift | 74 ------ Example/Example/Views/MainView.swift | 55 ----- .../NotificationCellView.swift | 41 ---- .../NotificationCenterView.swift | 83 ------- Example/Example/Views/TestPage.swift | 210 ++++++++++++++++++ Example/Example/Views/WebView.swift | 86 +++++++ .../NotificationService.swift | 28 --- 19 files changed, 572 insertions(+), 783 deletions(-) delete mode 100644 Example/Example/Models/Item+SwiftData.swift delete mode 100644 Example/Example/Models/Payload.swift delete mode 100644 Example/Example/Services/SwiftDataManager.swift delete mode 100644 Example/Example/ViewModels/ChooseInAppMessagesDelegate/ChooseInAppMessagesDelegate.swift delete mode 100644 Example/Example/ViewModels/MainViewModel.swift delete mode 100644 Example/Example/ViewModels/NotificationCenterViewModel.swift create mode 100644 Example/Example/ViewModels/ViewModel.swift create mode 100644 Example/Example/Views/ContentView.swift delete mode 100644 Example/Example/Views/CustomViews/ButtonsView.swift delete mode 100644 Example/Example/Views/CustomViews/SDKDataView.swift delete mode 100644 Example/Example/Views/MainView.swift delete mode 100644 Example/Example/Views/NotificationCenterViews/NotificationCellView.swift delete mode 100644 Example/Example/Views/NotificationCenterViews/NotificationCenterView.swift create mode 100644 Example/Example/Views/TestPage.swift create mode 100644 Example/Example/Views/WebView.swift diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index d7deeed3..38d60134 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -9,7 +9,6 @@ /* Begin PBXBuildFile section */ 0A0DE3482BB8455A00812E73 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0DE3472BB8455A00812E73 /* NotificationService.swift */; }; 0A0DE34C2BB8455A00812E73 /* MindboxNotificationServiceExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0A0DE3452BB8455A00812E73 /* MindboxNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 0A4B681A2BBC82B500639BC5 /* ChooseInAppMessagesDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A4B68192BBC82B500639BC5 /* ChooseInAppMessagesDelegate.swift */; }; 0A4B682A2BBD7D5100639BC5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0A4B68292BBD7D5100639BC5 /* LaunchScreen.storyboard */; }; 0AD271D02BB9D81E00750279 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AD271CF2BB9D81D00750279 /* UserNotifications.framework */; }; 0AD271D22BB9D81E00750279 /* UserNotificationsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AD271D12BB9D81E00750279 /* UserNotificationsUI.framework */; }; @@ -19,20 +18,11 @@ 0AEDBC7A2BB6F8F200EE8722 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEDBC792BB6F8F200EE8722 /* AppDelegate.swift */; }; 0AEDBC7C2BB6F8F200EE8722 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEDBC7B2BB6F8F200EE8722 /* SceneDelegate.swift */; }; 0AEDBC832BB6F8F400EE8722 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0AEDBC822BB6F8F400EE8722 /* Assets.xcassets */; }; - 0AEDBC922BB6FA4800EE8722 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEDBC912BB6FA4800EE8722 /* MainView.swift */; }; - 0AEDBC962BB6FE4900EE8722 /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEDBC952BB6FE4900EE8722 /* MainViewModel.swift */; }; - 0AEDBC992BB7058B00EE8722 /* ButtonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEDBC982BB7058B00EE8722 /* ButtonsView.swift */; }; - 0AEDBC9B2BB70D6E00EE8722 /* SDKDataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEDBC9A2BB70D6E00EE8722 /* SDKDataView.swift */; }; + 0AEDBC922BB6FA4800EE8722 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEDBC912BB6FA4800EE8722 /* ContentView.swift */; }; + 0AEDBC962BB6FE4900EE8722 /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEDBC952BB6FE4900EE8722 /* ViewModel.swift */; }; 43E1C73D7E87903545C2ACBC /* Pods_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9AF8ABB9430FE76D72FE17D /* Pods_Example.framework */; }; - 472423992C185B8400B2A9BC /* Item+SwiftData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472423982C185B8400B2A9BC /* Item+SwiftData.swift */; }; - 474F37A22C16F5A700F38BB0 /* NotificationCenterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474F37A12C16F5A700F38BB0 /* NotificationCenterViewModel.swift */; }; - 474F37A42C16F5B000F38BB0 /* NotificationCenterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474F37A32C16F5B000F38BB0 /* NotificationCenterView.swift */; }; - 474F37AE2C170FB600F38BB0 /* NotificationCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474F37AD2C170FB600F38BB0 /* NotificationCellView.swift */; }; - 4774807A2C174BAA00580FB2 /* Payload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 477480792C174BAA00580FB2 /* Payload.swift */; }; - 47FFE2232C187B650007E2F6 /* Item+SwiftData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472423982C185B8400B2A9BC /* Item+SwiftData.swift */; }; - 47FFE2262C187D3C0007E2F6 /* SwiftDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FFE2252C187D3C0007E2F6 /* SwiftDataManager.swift */; }; - 47FFE2282C187D5C0007E2F6 /* SwiftDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FFE2252C187D3C0007E2F6 /* SwiftDataManager.swift */; }; - 47FFE22C2C1886550007E2F6 /* Payload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 477480792C174BAA00580FB2 /* Payload.swift */; }; + 472137C12CF784D400354602 /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472137C02CF784D400354602 /* WebView.swift */; }; + 476FC2002CF89E340097EEBD /* TestPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 476FC1FF2CF89E340097EEBD /* TestPage.swift */; }; 9E3108F96D4F26745D3B37A4 /* Pods_MindboxNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AF2BFD8ABA97C73F589C5DB /* Pods_MindboxNotificationServiceExtension.framework */; }; D9585975AC05213E1682C760 /* Pods_MindboxNotificationContentExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 91EB2315545CE887D05CB505 /* Pods_MindboxNotificationContentExtension.framework */; }; /* End PBXBuildFile section */ @@ -74,7 +64,6 @@ 0A0DE3472BB8455A00812E73 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; 0A0DE3492BB8455A00812E73 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0A0DE3512BB8457100812E73 /* MindboxNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MindboxNotificationServiceExtension.entitlements; sourceTree = ""; }; - 0A4B68192BBC82B500639BC5 /* ChooseInAppMessagesDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseInAppMessagesDelegate.swift; sourceTree = ""; }; 0A4B68292BBD7D5100639BC5 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 0AD271CE2BB9D81D00750279 /* MindboxNotificationContentExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = MindboxNotificationContentExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 0AD271CF2BB9D81D00750279 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; @@ -88,20 +77,14 @@ 0AEDBC7B2BB6F8F200EE8722 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 0AEDBC822BB6F8F400EE8722 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 0AEDBC872BB6F8F400EE8722 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 0AEDBC912BB6FA4800EE8722 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; - 0AEDBC952BB6FE4900EE8722 /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = ""; }; - 0AEDBC982BB7058B00EE8722 /* ButtonsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonsView.swift; sourceTree = ""; }; - 0AEDBC9A2BB70D6E00EE8722 /* SDKDataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKDataView.swift; sourceTree = ""; }; + 0AEDBC912BB6FA4800EE8722 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 0AEDBC952BB6FE4900EE8722 /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = ""; }; 0AEDBC9C2BB7177A00EE8722 /* Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Example.entitlements; sourceTree = ""; }; 1B29989833584963EC18BDF2 /* Pods-MindboxNotificationContentExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MindboxNotificationContentExtension.release.xcconfig"; path = "Target Support Files/Pods-MindboxNotificationContentExtension/Pods-MindboxNotificationContentExtension.release.xcconfig"; sourceTree = ""; }; - 472423982C185B8400B2A9BC /* Item+SwiftData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Item+SwiftData.swift"; sourceTree = ""; }; - 474F37A12C16F5A700F38BB0 /* NotificationCenterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationCenterViewModel.swift; sourceTree = ""; }; - 474F37A32C16F5B000F38BB0 /* NotificationCenterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationCenterView.swift; sourceTree = ""; }; - 474F37AD2C170FB600F38BB0 /* NotificationCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationCellView.swift; sourceTree = ""; }; - 477480792C174BAA00580FB2 /* Payload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Payload.swift; sourceTree = ""; }; + 472137C02CF784D400354602 /* WebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebView.swift; sourceTree = ""; }; + 476FC1FF2CF89E340097EEBD /* TestPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestPage.swift; sourceTree = ""; }; 47912FD92CB42D640063387D /* AppDelegate_IDFA.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate_IDFA.swift; sourceTree = ""; }; 47D63E2C2C2EAD220055E7D8 /* Mindbox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Mindbox.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 47FFE2252C187D3C0007E2F6 /* SwiftDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftDataManager.swift; sourceTree = ""; }; 4AF2BFD8ABA97C73F589C5DB /* Pods_MindboxNotificationServiceExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MindboxNotificationServiceExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4C06401BC9C282DDDDADEA9D /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = ""; }; 91EB2315545CE887D05CB505 /* Pods_MindboxNotificationContentExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MindboxNotificationContentExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -195,29 +178,18 @@ 0AEDBC822BB6F8F400EE8722 /* Assets.xcassets */, 0A4B68292BBD7D5100639BC5 /* LaunchScreen.storyboard */, 0AEDBC872BB6F8F400EE8722 /* Info.plist */, - 47FFE2242C187D360007E2F6 /* Services */, - 474F37AF2C1710BB00F38BB0 /* Models */, 0AEDBC9D2BB71FC600EE8722 /* Views */, 0AEDBC9E2BB71FD700EE8722 /* ViewModels */, ); path = Example; sourceTree = ""; }; - 0AEDBC972BB7050F00EE8722 /* CustomViews */ = { - isa = PBXGroup; - children = ( - 0AEDBC982BB7058B00EE8722 /* ButtonsView.swift */, - 0AEDBC9A2BB70D6E00EE8722 /* SDKDataView.swift */, - ); - path = CustomViews; - sourceTree = ""; - }; 0AEDBC9D2BB71FC600EE8722 /* Views */ = { isa = PBXGroup; children = ( - 0AEDBC972BB7050F00EE8722 /* CustomViews */, - 474F37AC2C170FA600F38BB0 /* NotificationCenterViews */, - 0AEDBC912BB6FA4800EE8722 /* MainView.swift */, + 0AEDBC912BB6FA4800EE8722 /* ContentView.swift */, + 476FC1FF2CF89E340097EEBD /* TestPage.swift */, + 472137C02CF784D400354602 /* WebView.swift */, ); path = Views; sourceTree = ""; @@ -225,39 +197,11 @@ 0AEDBC9E2BB71FD700EE8722 /* ViewModels */ = { isa = PBXGroup; children = ( - 477480842C1760BE00580FB2 /* ChooseInAppMessagesDelegate */, - 0AEDBC952BB6FE4900EE8722 /* MainViewModel.swift */, - 474F37A12C16F5A700F38BB0 /* NotificationCenterViewModel.swift */, + 0AEDBC952BB6FE4900EE8722 /* ViewModel.swift */, ); path = ViewModels; sourceTree = ""; }; - 474F37AC2C170FA600F38BB0 /* NotificationCenterViews */ = { - isa = PBXGroup; - children = ( - 474F37AD2C170FB600F38BB0 /* NotificationCellView.swift */, - 474F37A32C16F5B000F38BB0 /* NotificationCenterView.swift */, - ); - path = NotificationCenterViews; - sourceTree = ""; - }; - 474F37AF2C1710BB00F38BB0 /* Models */ = { - isa = PBXGroup; - children = ( - 477480792C174BAA00580FB2 /* Payload.swift */, - 472423982C185B8400B2A9BC /* Item+SwiftData.swift */, - ); - path = Models; - sourceTree = ""; - }; - 477480842C1760BE00580FB2 /* ChooseInAppMessagesDelegate */ = { - isa = PBXGroup; - children = ( - 0A4B68192BBC82B500639BC5 /* ChooseInAppMessagesDelegate.swift */, - ); - path = ChooseInAppMessagesDelegate; - sourceTree = ""; - }; 47912FD82CB4130C0063387D /* Samples */ = { isa = PBXGroup; children = ( @@ -266,14 +210,6 @@ path = Samples; sourceTree = ""; }; - 47FFE2242C187D360007E2F6 /* Services */ = { - isa = PBXGroup; - children = ( - 47FFE2252C187D3C0007E2F6 /* SwiftDataManager.swift */, - ); - path = Services; - sourceTree = ""; - }; 52C896E46FFA4E326FC946A8 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -521,10 +457,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 47FFE2282C187D5C0007E2F6 /* SwiftDataManager.swift in Sources */, - 47FFE2232C187B650007E2F6 /* Item+SwiftData.swift in Sources */, 0A0DE3482BB8455A00812E73 /* NotificationService.swift in Sources */, - 47FFE22C2C1886550007E2F6 /* Payload.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -540,19 +473,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 474F37A22C16F5A700F38BB0 /* NotificationCenterViewModel.swift in Sources */, 0AEDBC7A2BB6F8F200EE8722 /* AppDelegate.swift in Sources */, - 0AEDBC992BB7058B00EE8722 /* ButtonsView.swift in Sources */, - 474F37A42C16F5B000F38BB0 /* NotificationCenterView.swift in Sources */, - 0AEDBC922BB6FA4800EE8722 /* MainView.swift in Sources */, - 472423992C185B8400B2A9BC /* Item+SwiftData.swift in Sources */, - 47FFE2262C187D3C0007E2F6 /* SwiftDataManager.swift in Sources */, - 474F37AE2C170FB600F38BB0 /* NotificationCellView.swift in Sources */, - 4774807A2C174BAA00580FB2 /* Payload.swift in Sources */, - 0AEDBC962BB6FE4900EE8722 /* MainViewModel.swift in Sources */, + 0AEDBC922BB6FA4800EE8722 /* ContentView.swift in Sources */, + 0AEDBC962BB6FE4900EE8722 /* ViewModel.swift in Sources */, 0AEDBC7C2BB6F8F200EE8722 /* SceneDelegate.swift in Sources */, - 0AEDBC9B2BB70D6E00EE8722 /* SDKDataView.swift in Sources */, - 0A4B681A2BBC82B500639BC5 /* ChooseInAppMessagesDelegate.swift in Sources */, + 476FC2002CF89E340097EEBD /* TestPage.swift in Sources */, + 472137C12CF784D400354602 /* WebView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/Example/AppDelegate.swift b/Example/Example/AppDelegate.swift index 6ab3bd53..9c899de6 100644 --- a/Example/Example/AppDelegate.swift +++ b/Example/Example/AppDelegate.swift @@ -18,14 +18,14 @@ final class AppDelegate: MindboxAppDelegate { didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { super.application(application, didFinishLaunchingWithOptions: launchOptions) - + print(#function) do { // To run the application on a physical device you need to change the endpoint // You should also change the application bundle ID in all targets, more details in the readme // You can still run the application on the simulator to see In-Apps let mindboxSdkConfig = try MBConfiguration( - endpoint: "Mpush-test.ReleaseExample.IosApp", - domain: "api.mindbox.ru", + endpoint: "Test-staging.Test01", + domain: "api-staging.mindbox.ru", subscribeCustomerIfCreated: true, shouldCreateCustomer: true ) @@ -33,7 +33,11 @@ final class AppDelegate: MindboxAppDelegate { } catch { print(error.localizedDescription) } - + + Mindbox.shared.getDeviceUUID { deviceUUID in + print("DeviceUUID: \(deviceUUID)") + } + // https://developers.mindbox.ru/docs/ios-send-push-notifications-appdelegate registerForRemoteNotifications() diff --git a/Example/Example/Models/Item+SwiftData.swift b/Example/Example/Models/Item+SwiftData.swift deleted file mode 100644 index 207c4674..00000000 --- a/Example/Example/Models/Item+SwiftData.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// Item+SwiftData.swift -// Example -// -// Created by Sergei Semko on 6/11/24. -// Copyright © 2024 Mindbox. All rights reserved. -// - -import Foundation -import SwiftData - -@Model -public final class Item { - public var timestamp: Date - public var mbPushNotification: PushNotification - - public init(timestamp: Date, pushNotification: PushNotification) { - self.timestamp = timestamp - self.mbPushNotification = pushNotification - } -} - -public struct PushNotification: Codable { - public let title: String? - public let body: String? - public let clickUrl: String? - public let imageUrl: String? - public let payload: String? - public let uniqueKey: String? - - var decodedPayload: Payload? { - guard let payloadData = payload?.data(using: .utf8) else { return nil } - return try? JSONDecoder().decode(Payload.self, from: payloadData) - } -} diff --git a/Example/Example/Models/Payload.swift b/Example/Example/Models/Payload.swift deleted file mode 100644 index 717e1b10..00000000 --- a/Example/Example/Models/Payload.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Payload.swift -// Example -// -// Created by Sergei Semko on 6/10/24. -// Copyright © 2024 Mindbox. All rights reserved. -// - -import Foundation - -public struct Payload: Codable { - var pushName: String - var pushDate: String -} diff --git a/Example/Example/SceneDelegate.swift b/Example/Example/SceneDelegate.swift index e48bf989..1c93c9b9 100644 --- a/Example/Example/SceneDelegate.swift +++ b/Example/Example/SceneDelegate.swift @@ -18,10 +18,11 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions ) { + print(#function) guard let windowScene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: windowScene) - let viewModel = MainViewModel() - window.rootViewController = UIHostingController(rootView: MainView(viewModel: viewModel)) + let viewModel = ViewModel() + window.rootViewController = UIHostingController(rootView: ContentView(webViewModel: viewModel)) self.window = window window.makeKeyAndVisible() } diff --git a/Example/Example/Services/SwiftDataManager.swift b/Example/Example/Services/SwiftDataManager.swift deleted file mode 100644 index 5ff6221e..00000000 --- a/Example/Example/Services/SwiftDataManager.swift +++ /dev/null @@ -1,117 +0,0 @@ -// -// SwiftDataManager.swift -// Example -// -// Created by Sergei Semko on 6/11/24. -// Copyright © 2024 Mindbox. All rights reserved. -// - -import Foundation -import SwiftData -import SwiftUI - -public struct SwiftDataManager { - public static let shared = SwiftDataManager() - public var container: ModelContainer - - private var mockDataSaved: Bool { - UserDefaults.standard.bool(forKey: "isMockDataSaved") - } - - private init() { - SwiftDataManager.createDirectoryForSwiftData() - - let schema = Schema([ - Item.self, - ]) - - let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) - - do { - container = try ModelContainer(for: schema, configurations: [modelConfiguration]) - } catch { - fatalError("Could not create ModelContainer: \(error)") - } - } - - public func saveMockDataIfNeeded() { - guard !mockDataSaved else { - print("MockData already saved") - return - } - Task { - await saveMockData() - } - UserDefaults.standard.set(true, forKey: "isMockDataSaved") - } - - @MainActor - private func saveMockData() { - let context = SwiftDataManager.shared.container.mainContext - - mockNotifications.forEach { notification in - let newItem = Item(timestamp: Date(), pushNotification: notification) - - context.insert(newItem) - - do { - try context.save() - } catch { - print("Failed to save context: \(error.localizedDescription)") - } - } - } - - private static func createDirectoryForSwiftData() { - let fileManager = FileManager.default - let appGroupIdentifier = "group.cloud.Mindbox.cloud.Mindbox.ReleaseExampleIos" - if let appGroupURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) { - let applicationSupportURL = appGroupURL.appendingPathComponent("Library/Application Support") - - if !fileManager.fileExists(atPath: applicationSupportURL.path) { - do { - try fileManager.createDirectory(at: applicationSupportURL, withIntermediateDirectories: true, attributes: nil) - } catch { - print("Failed to create Application Support directory: \(error)") - } - } else { - print("Application Support directory already exists at \(applicationSupportURL.path)") - } - } else { - fatalError("Could not find App Group container URL.") - } - } -} - -private extension SwiftDataManager { - var mockNotifications: [PushNotification] { - [ - PushNotification( - title: "First notification title", - body: "First notification body", - clickUrl: "https://mindbox.ru/", - imageUrl: "https://mobpush-images.mindbox.ru/Mpush-test/1a73ebaa-3e5f-49f4-ae6c-462c9b64d34c/307be696-77e6-4d83-b7eb-c94be85f7a03.png", - payload: "{\"pushName\": \"\", \"pushDate\": \"\"}", - uniqueKey: "Push unique key: 1" - ), - - PushNotification( - title: "Second notification title", - body: "Second notification body", - clickUrl: "https://mindbox.ru/", - imageUrl: "https://mobpush-images.mindbox.ru/Mpush-test/1a73ebaa-3e5f-49f4-ae6c-462c9b64d34c/2397fea9-383d-49bf-a6a0-181a267faa94.png", - payload: "{\"pushName\": \"\", \"pushDate\": \"\"}", - uniqueKey: "Push unique key: 2" - ), - - PushNotification( - title: "Third notification title", - body: "Third notification body", - clickUrl: "https://mindbox.ru/", - imageUrl: "https://mobpush-images.mindbox.ru/Mpush-test/1a73ebaa-3e5f-49f4-ae6c-462c9b64d34c/bd4250b1-a7ac-4b8a-b91b-481b3b5c565c.png", - payload: "{\"pushName\": \"\", \"pushDate\": \"\"}", - uniqueKey: "Push unique key: 3" - ), - ] - } -} diff --git a/Example/Example/ViewModels/ChooseInAppMessagesDelegate/ChooseInAppMessagesDelegate.swift b/Example/Example/ViewModels/ChooseInAppMessagesDelegate/ChooseInAppMessagesDelegate.swift deleted file mode 100644 index bfd0f066..00000000 --- a/Example/Example/ViewModels/ChooseInAppMessagesDelegate/ChooseInAppMessagesDelegate.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// ChooseInAppMessagesDelegate.swift -// Example -// -// Created by Дмитрий Ерофеев on 02.04.2024. -// Copyright © 2024 Mindbox. All rights reserved. -// - -import Foundation -import Mindbox - -class ChooseInAppMessagesDelegate: InAppMessagesDelegate { - - private init() {} - static let shared = ChooseInAppMessagesDelegate() - - // https://developers.mindbox.ru/docs/in-app - func inAppMessageTapAction(id: String, url: URL?, payload: String) { - //Here you can add your custom logic - print("inAppMessageTapAction") - print("InApp ID: \(id)") - print("InApp URL: \(String(describing: url))") - print("InApp Payload: \(payload)") - } - - // https://developers.mindbox.ru/docs/in-app - func inAppMessageDismissed(id: String) { - //Here you can add your custom logic - print("inAppMessageDismissed") - print("InApp ID: \(id)") - } - - func select(chooseInappMessageDelegate: ChooseInappMessageDelegate) { - switch chooseInappMessageDelegate { - case .DefaultInappMessageDelegate: - break - case .InAppMessagesDelegate: - Mindbox.shared.inAppMessagesDelegate = self - } - } -} - -enum ChooseInappMessageDelegate { - case DefaultInappMessageDelegate - case InAppMessagesDelegate -} diff --git a/Example/Example/ViewModels/MainViewModel.swift b/Example/Example/ViewModels/MainViewModel.swift deleted file mode 100644 index b0287024..00000000 --- a/Example/Example/ViewModels/MainViewModel.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// MainViewModel.swift -// Example -// -// Created by Дмитрий Ерофеев on 29.03.2024. -// Copyright © 2024 Mindbox. All rights reserved. -// - -import Foundation -import Mindbox -import Observation - -@Observable final class MainViewModel { - - var SDKVersion: String = "" - var deviceUUID: String = "" - var APNSToken: String = "" - - // https://developers.mindbox.ru/docs/ios-sdk-methods - func setupData() { - self.SDKVersion = Mindbox.shared.sdkVersion - Mindbox.shared.getDeviceUUID { deviceUUID in - DispatchQueue.main.async { - self.deviceUUID = deviceUUID - } - } - Mindbox.shared.getAPNSToken { APNSToken in - DispatchQueue.main.async { - self.APNSToken = APNSToken - } - } - ChooseInAppMessagesDelegate.shared.select(chooseInappMessageDelegate: .InAppMessagesDelegate) - } - - // https://developers.mindbox.ru/docs/in-app-targeting-by-custom-operation - func showInAppWithExecuteSyncOperation () { - let json = """ - { "viewProduct": - { "product": - { "ids": - { "website": "1" } - } - } - } - """ - Mindbox.shared.executeSyncOperation(operationSystemName: "APIMethodForReleaseExampleIos", json: json) { result in - switch result { - case .success(let success): - Mindbox.logger.log(level: .info, message: "\(success)") - case .failure(let error): - Mindbox.logger.log(level: .error, message: "\(error)") - } - } - } - - // https://developers.mindbox.ru/docs/in-app-targeting-by-custom-operation - func showInAppWithExecuteAsyncOperation () { - let json = """ - { "viewProduct": - { "product": - { "ids": - { "website": "2" } - } - } - } - """ - Mindbox.shared.executeAsyncOperation(operationSystemName: "APIMethodForReleaseExampleIos", json: json) - } -} diff --git a/Example/Example/ViewModels/NotificationCenterViewModel.swift b/Example/Example/ViewModels/NotificationCenterViewModel.swift deleted file mode 100644 index 882ac0b8..00000000 --- a/Example/Example/ViewModels/NotificationCenterViewModel.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// NotificationCenterViewModel.swift -// Example -// -// Created by Sergei Semko on 6/10/24. -// Copyright © 2024 Mindbox. All rights reserved. -// - -import Mindbox -import Observation - -protocol NotificationCenterViewModelProtocol: AnyObject { - var lastTappedNotification: PushNotification? { get } - var errorMessage: String? { get } - - func sendOperationNCPushOpen(notification: PushNotification) - func sendOperationNCOpen() -} - -@Observable final class NotificationCenterViewModel: NotificationCenterViewModelProtocol { - - // MARK: - NotificationCenterViewModelProtocol - var errorMessage: String? - var lastTappedNotification: PushNotification? - - func sendOperationNCPushOpen(notification: PushNotification) { - lastTappedNotification = notification - - /*Assuming payload of push notification has this structure: - { - "pushName":"", - "pushDate":"" - }*/ - guard let dateTime = notification.decodedPayload?.pushDate, - let translateName = notification.decodedPayload?.pushName else { - errorMessage = "Payload isn't valid for operation" - print("Payload isn't valid for operation") - return - } - - let json = """ - { - "data": { - "customAction": { - "customFields": { - "mobPushSendDateTime": "\(dateTime)", - "mobPushTranslateName": "\(translateName)" - } - } - } - } - """ - - let operationName = "mobileapp.NCPushOpen" - - Mindbox.shared.executeAsyncOperation(operationSystemName: operationName, json: json) - - errorMessage = nil - } - - func sendOperationNCOpen() { - let operationName = "mobileapp.NCOpen" - - Mindbox.shared.executeAsyncOperation(operationSystemName: operationName, json: "{}") - } - - init() { - SwiftDataManager.shared.saveMockDataIfNeeded() - } -} diff --git a/Example/Example/ViewModels/ViewModel.swift b/Example/Example/ViewModels/ViewModel.swift new file mode 100644 index 00000000..08d16a9a --- /dev/null +++ b/Example/Example/ViewModels/ViewModel.swift @@ -0,0 +1,154 @@ +// +// MainViewModel.swift +// Example +// +// Created by Дмитрий Ерофеев on 29.03.2024. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation +import Mindbox +import Observation +import WebKit + +@Observable final class ViewModel { + + var isLoading: Bool = false + var errorMessage: String? = nil + + var webView: WKWebView? + + func setWebView(_ webView: WKWebView) { + self.webView = webView + } + + func clearAllWebData(_ webView: WKWebView) { + let dataStore = WKWebsiteDataStore.default() + let dataTypes = WKWebsiteDataStore.allWebsiteDataTypes() + + dataStore.removeData(ofTypes: dataTypes, modifiedSince: Date.distantPast) { + print("All web data cleared") + } + } + + func setupWebViewForSync() { + guard let webView = webView else { return } + + Mindbox.shared.getDeviceUUID { uuid in + guard !uuid.isEmpty else { + print("Device UUID is empty or invalid") + return + } + let script = """ + document.cookie = "mindboxDeviceUUID=\(uuid); path=/"; + window.localStorage.setItem('mindboxDeviceUUID', '\(uuid)'); + """ + webView.evaluateJavaScript(script) { _, error in + if let error = error { + print("Error setting cookies and localStorage: \(error)") + } else { + print("Cookies and localStorage set successfully.") + } + } + } + } + + func setCookie() { + guard let webView = webView else { return } + + Mindbox.shared.getDeviceUUID { uuid in + guard !uuid.isEmpty else { + print("Device UUID is empty or invalid") + return + } + + let script = """ + document.cookie = "mindboxDeviceUUID=\(uuid); path=/"; + window.localStorage.setItem('mindboxDeviceUUID', '\(uuid)'); + """ + webView.evaluateJavaScript(script) { _, error in + if let error = error { + print("Error setting cookies: \(error)") + } else { + print("Cookies set successfully.") + } + } + } + } + + func viewCookiesAndLocalStorage() { + guard let webView = webView else { return } + + let script = """ + JSON.stringify({ + cookies: document.cookie || "No cookies found", + localStorage: window.localStorage.getItem('mindboxDeviceUUID') || "No value found" + }) + """ + webView.evaluateJavaScript(script) { result, error in + if let error = error { + print("Error retrieving cookies and localStorage: \(error)") + } else { + print("\nStart===============") + print("Cookies and LocalStorage: \(result ?? "nil")") + print("===============End\n") + } + } + } + +// var webView: WKWebView? + + var SDKVersion: String = "" + var deviceUUID: String = "" + var APNSToken: String = "" + + // https://developers.mindbox.ru/docs/ios-sdk-methods + func setupData() { + self.SDKVersion = Mindbox.shared.sdkVersion + Mindbox.shared.getDeviceUUID { deviceUUID in + DispatchQueue.main.async { + self.deviceUUID = deviceUUID + } + } + Mindbox.shared.getAPNSToken { APNSToken in + DispatchQueue.main.async { + self.APNSToken = APNSToken + } + } + } + + // https://developers.mindbox.ru/docs/in-app-targeting-by-custom-operation + func showInAppWithExecuteSyncOperation () { + let json = """ + { "viewProduct": + { "product": + { "ids": + { "website": "1" } + } + } + } + """ + Mindbox.shared.executeSyncOperation(operationSystemName: "APIMethodForReleaseExampleIos", json: json) { result in + switch result { + case .success(let success): + Mindbox.logger.log(level: .info, message: "\(success)") + case .failure(let error): + Mindbox.logger.log(level: .error, message: "\(error)") + } + } + } + + // https://developers.mindbox.ru/docs/in-app-targeting-by-custom-operation + func showInAppWithExecuteAsyncOperation () { + let json = """ + { "viewProduct": + { "product": + { "ids": + { "website": "2" } + } + } + } + """ + Mindbox.shared.executeAsyncOperation(operationSystemName: "APIMethodForReleaseExampleIos", json: json) + } +} diff --git a/Example/Example/Views/ContentView.swift b/Example/Example/Views/ContentView.swift new file mode 100644 index 00000000..dc0c56c2 --- /dev/null +++ b/Example/Example/Views/ContentView.swift @@ -0,0 +1,95 @@ +// +// ContentView.swift +// Example +// +// Created by Дмитрий Ерофеев on 29.03.2024. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import SwiftUI + +struct ContentView: View { + + var webViewModel: ViewModel + let url: URL = URL(string: "https://personalization-test-site-staging.mindbox.ru/")! + + var body: some View { + VStack { + let _ = print(#function) + WebView(url: url, viewModel: webViewModel) + .overlay(loadingOverlay) + + if let errorMessage = webViewModel.errorMessage { + Text("Error: \(errorMessage)") + .foregroundColor(.red) + .padding() + } + + Button("Print in console Cookies and LocalStorage") { + webViewModel.viewCookiesAndLocalStorage() + print("LOL") + }.padding() + } + } + + @ViewBuilder + private var loadingOverlay: some View { + if webViewModel.isLoading { + ZStack { + Color.black.opacity(0.3) + ProgressView("Loading...") + } + } + } + +} + +//struct MainView: View { +// +// var viewModel: MainViewModel +// @State private var showingAlert = !UserDefaults.standard.bool(forKey: "ShownAlert") +// +// var body: some View { +// NavigationStack { +// ZStack { +// Color.mbBackground.ignoresSafeArea() +// VStack(spacing: 32) { +// ButtonsView(viewModel: viewModel) +// SDKDataView(viewModel: viewModel) +// ZStack { +// RoundedRectangle(cornerRadius: 20) +// .frame(maxWidth: 350) +// .frame(maxHeight: 110) +// .foregroundColor(.mbView) +// NavigationLink("Open Notification Center") { +// NotificationCenterView(viewModel: NotificationCenterViewModel()) +// .modelContainer(SwiftDataManager.shared.container) +// } +// .frame(maxWidth: 250) +// .frame(maxHeight: 50) +// .background(Color.mbGreen) +// .cornerRadius(16) +// .tint(.white) +// } +// } +// }.onAppear { +// viewModel.setupData() +// let alertShown = UserDefaults.standard.bool(forKey: "ShownAlert") +// if !alertShown { +// UserDefaults.standard.set(true, forKey: "ShownAlert") +// } +// } +// .alert("In-App can only be shown once per session", isPresented: $showingAlert) { +// Button("OK", role: .cancel) {} +// } +// } +// } +//} +// +//#Preview { +// MainView(viewModel: MainViewModel()) +//} + +#Preview { + ContentView(webViewModel: ViewModel()) +} diff --git a/Example/Example/Views/CustomViews/ButtonsView.swift b/Example/Example/Views/CustomViews/ButtonsView.swift deleted file mode 100644 index def0c97b..00000000 --- a/Example/Example/Views/CustomViews/ButtonsView.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// ButtonsView.swift -// Example -// -// Created by Дмитрий Ерофеев on 29.03.2024. -// Copyright © 2024 Mindbox. All rights reserved. -// - -import SwiftUI - -struct ButtonsViewline: View { - - var label: String - var action: () -> () - - var body: some View { - HStack { - Text(label) - .foregroundStyle(.mbText) - Spacer() - Button("Show In-App") { - action() - } - .frame(width: 120) - .frame(height: 30) - .background(Color.mbGreen) - .cornerRadius(16) - .tint(.white) - } - } -} - -struct ButtonsView: View { - - var viewModel: MainViewModel - - var body: some View { - ZStack { - RoundedRectangle(cornerRadius: 20) - .frame(width: 350) - .frame(height: 110) - .foregroundColor(.mbView) - VStack { - ButtonsViewline(label: "Async operation") { - viewModel.showInAppWithExecuteAsyncOperation() - } - Divider() - ButtonsViewline(label: "Sync operation") { - viewModel.showInAppWithExecuteSyncOperation() - } - } - .frame(width: 310) - } - } -} diff --git a/Example/Example/Views/CustomViews/SDKDataView.swift b/Example/Example/Views/CustomViews/SDKDataView.swift deleted file mode 100644 index becf8ae5..00000000 --- a/Example/Example/Views/CustomViews/SDKDataView.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// SDKDataView.swift -// Example -// -// Created by Дмитрий Ерофеев on 29.03.2024. -// Copyright © 2024 Mindbox. All rights reserved. -// - -import SwiftUI - -struct TitleDescrptionView: View { - - var title: String - var message: String - - var body: some View { - HStack { - VStack(alignment: .leading) { - Text(title) - .font(.system(size: 9)) - .padding(.bottom, 2) - .lineLimit(1) - .foregroundStyle(.mbText) - Text(message) - .font(.system(size: 13)) - .contextMenu { - Button { - UIPasteboard.general.string = message - } label: { - Label("Copy", systemImage: "doc.on.doc.fill") - } - } - .lineLimit(1) - .foregroundStyle(.mbText) - } - Spacer() - } - } -} - -struct SDKDataView: View { - - var viewModel: MainViewModel - - var body: some View { - ZStack { - RoundedRectangle(cornerRadius: 20) - .frame(width: 350) - .frame(height: 190) - .foregroundColor(.mbView) - VStack { - HStack { - Text("Data from SDK:") - .foregroundStyle(.mbText) - .padding(.leading, -1) - .padding(.bottom, 5) - Spacer() - } - TitleDescrptionView(title: "SDK version", message: viewModel.SDKVersion) - Divider() - TitleDescrptionView(title: "APNS token", message: viewModel.APNSToken) - Divider() - TitleDescrptionView(title: "Device UUID", message: viewModel.deviceUUID) - } - .frame(width: 310) - } - } -} - -struct SDKDataView_Preview: PreviewProvider { - static var previews: some View { - SDKDataView(viewModel: MainViewModel()) - } -} diff --git a/Example/Example/Views/MainView.swift b/Example/Example/Views/MainView.swift deleted file mode 100644 index 06da3c19..00000000 --- a/Example/Example/Views/MainView.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// MainView.swift -// Example -// -// Created by Дмитрий Ерофеев on 29.03.2024. -// Copyright © 2024 Mindbox. All rights reserved. -// - -import SwiftUI - -struct MainView: View { - - var viewModel: MainViewModel - @State private var showingAlert = !UserDefaults.standard.bool(forKey: "ShownAlert") - - var body: some View { - NavigationStack { - ZStack { - Color.mbBackground.ignoresSafeArea() - VStack(spacing: 32) { - ButtonsView(viewModel: viewModel) - SDKDataView(viewModel: viewModel) - ZStack { - RoundedRectangle(cornerRadius: 20) - .frame(maxWidth: 350) - .frame(maxHeight: 110) - .foregroundColor(.mbView) - NavigationLink("Open Notification Center") { - NotificationCenterView(viewModel: NotificationCenterViewModel()) - .modelContainer(SwiftDataManager.shared.container) - } - .frame(maxWidth: 250) - .frame(maxHeight: 50) - .background(Color.mbGreen) - .cornerRadius(16) - .tint(.white) - } - } - }.onAppear { - viewModel.setupData() - let alertShown = UserDefaults.standard.bool(forKey: "ShownAlert") - if !alertShown { - UserDefaults.standard.set(true, forKey: "ShownAlert") - } - } - .alert("In-App can only be shown once per session", isPresented: $showingAlert) { - Button("OK", role: .cancel) {} - } - } - } -} - -#Preview { - MainView(viewModel: MainViewModel()) -} diff --git a/Example/Example/Views/NotificationCenterViews/NotificationCellView.swift b/Example/Example/Views/NotificationCenterViews/NotificationCellView.swift deleted file mode 100644 index cd477a7d..00000000 --- a/Example/Example/Views/NotificationCenterViews/NotificationCellView.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// NotificationCellView.swift -// Example -// -// Created by Sergei Semko on 6/10/24. -// Copyright © 2024 Mindbox. All rights reserved. -// - -import SwiftUI -import Mindbox - -struct NotificationCellView: View { - var notification: PushNotification - - var body: some View { - HStack(alignment: .center, content: { - if let imageUrl = notification.imageUrl, let url = URL(string: imageUrl) { - AsyncImage(url: url) { image in - image - .resizable() - .scaledToFill() - } placeholder: { - ProgressView() - } - .frame(maxWidth: 64, maxHeight: 64) - .clipShape(Circle()) - } - - VStack(alignment: .leading, content: { - Text(notification.title ?? "Empty") - .font(.headline) - Text(notification.body ?? "Empty") - .font(.subheadline) - .foregroundStyle(.gray) - Text(notification.clickUrl ?? "Empty") - .font(.footnote) - .foregroundStyle(.blue) - }) - }) - } -} diff --git a/Example/Example/Views/NotificationCenterViews/NotificationCenterView.swift b/Example/Example/Views/NotificationCenterViews/NotificationCenterView.swift deleted file mode 100644 index 8f9fad84..00000000 --- a/Example/Example/Views/NotificationCenterViews/NotificationCenterView.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// NotificationCenterView.swift -// Example -// -// Created by Sergei Semko on 6/10/24. -// Copyright © 2024 Mindbox. All rights reserved. -// - -import SwiftUI -import SwiftData - -struct NotificationCenterView: View { - - var viewModel: NotificationCenterViewModelProtocol - - @State private var showAlert = false - @State private var alertTitle = String() - @State private var alertMessage = String() - - @Environment(\.modelContext) private var modelContext - @Query private var items: [Item] - - var body: some View { - NavigationStack { - List { - ForEach(items.reversed()) { item in - ZStack { - Color.clear - HStack { - NotificationCellView(notification: item.mbPushNotification) - Spacer() - } - } - - .contentShape(Rectangle()) - .onTapGesture { - viewModel.sendOperationNCPushOpen(notification: item.mbPushNotification) - if let errorMessage = viewModel.errorMessage { - alertMessage = errorMessage - } else { - alertMessage = "Operation NCPushOpen sent to Mindbox" - } - showAlert = true - UIImpactFeedbackGenerator(style: .soft).impactOccurred() - } - } - .onDelete(perform: deleteItems) - } - } - .onAppear { - viewModel.sendOperationNCOpen() - } - .navigationTitle("Notification Center") - .navigationBarTitleDisplayMode(.large) - .alert( - alertMessage, - isPresented: $showAlert, - presenting: viewModel.lastTappedNotification) { notification in - Text("Unique key: \(notification.uniqueKey ?? "Empty")") - Button("OK", action: {}) - } - } - - private func deleteItems(offsets: IndexSet) { - withAnimation { - let originalOffsets = IndexSet(offsets.map { items.count - 1 - $0 }) - for index in originalOffsets { - let item = items[index] - if item.mbPushNotification.clickUrl == "https://mindbox.ru/" { - return - } else { - modelContext.delete(items[index]) - - } - } - } - UIImpactFeedbackGenerator(style: .heavy).impactOccurred() - } -} - -#Preview { - NotificationCenterView(viewModel: NotificationCenterViewModel()) -} diff --git a/Example/Example/Views/TestPage.swift b/Example/Example/Views/TestPage.swift new file mode 100644 index 00000000..607b7284 --- /dev/null +++ b/Example/Example/Views/TestPage.swift @@ -0,0 +1,210 @@ +// +// TestPage.swift +// Example +// +// Created by Sergei Semko on 11/28/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + + +// webViewTestApp.swift +// webViewTest +// +// Created by Sergey Sozinov on 20.11.2024. +// + +import SwiftUI +import WebKit +import Mindbox + +//@main +struct webViewTestApp: App { +// let persistenceController = PersistenceController.shared + let mindboxSdkConfig: MBConfiguration + + init() { + // Логика инициализации Mindbox + do { + mindboxSdkConfig = try MBConfiguration( + endpoint: "Test-staging.Test01", + domain: "api-staging.mindbox.ru", + subscribeCustomerIfCreated: true, + shouldCreateCustomer: true + ) + Mindbox.shared.initialization(configuration: mindboxSdkConfig) + Mindbox.shared.getDeviceUUID { deviceUUID in + print("Device UUID: \(deviceUUID)") + } + } catch { + fatalError("Failed to initialize MBConfiguration: \(error.localizedDescription)") + } + } + + var body: some Scene { + WindowGroup { + ContentViewTest() +// .environment(\.managedObjectContext, persistenceController.container.viewContext) + } + } +} + +struct ContentViewTest: View { + @StateObject private var webViewModel = WebViewModelTest() + + var body: some View { + VStack { + WebViewContainer(webViewModel: webViewModel) + .frame(maxHeight: .infinity) + + HStack { + Button("SetCookie") { + webViewModel.setCookie() + } + Button("View") { + webViewModel.viewCookiesAndLocalStorage() + } + } + } + } +} + +struct WebViewContainer: UIViewRepresentable { + @ObservedObject var webViewModel: WebViewModelTest + + func makeUIView(context: Context) -> WKWebView { + let preferences = WKPreferences() + preferences.javaScriptEnabled = true + + // Настройка WKWebViewConfiguration + let configuration = WKWebViewConfiguration() + configuration.preferences = preferences + let webView = WKWebView(frame: .zero, configuration: configuration) + if #available(iOS 16.4, *) { + webView.isInspectable = true + } + webView.navigationDelegate = context.coordinator + webViewModel.webView = webView + + loadInitialURL(webView) + return webView + } + + func updateUIView(_ uiView: WKWebView, context: Context) {} + + private func loadInitialURL(_ webView: WKWebView) { + let url = URL(string: "https://personalization-test-site-staging.mindbox.ru/")! + let request = URLRequest(url: url) + webView.load(request) + } + + func makeCoordinator() -> WebViewCoordinator { + WebViewCoordinator(webViewModel: webViewModel) + } +} + +class WebViewCoordinator: NSObject, WKNavigationDelegate { + private let webViewModel: WebViewModelTest + + init(webViewModel: WebViewModelTest) { + self.webViewModel = webViewModel + } + + func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { + print("Page started loading: \(webView.url?.absoluteString ?? "Unknown URL")") + + } + + func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { + webViewModel.clearAllWebData(webView) + webViewModel.setupWebViewForSync() + print("Content started arriving for: \(webView.url?.absoluteString ?? "Unknown URL")") + } + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + print("Page finished loading: \(webView.url?.absoluteString ?? "Unknown URL")") + webViewModel.viewCookiesAndLocalStorage() + } + + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + if let url = navigationAction.request.url?.absoluteString, url.contains("tracker.js") { + print("Intercepted tracker.js: \(url)") + } + decisionHandler(.allow) + } +} + +class WebViewModelTest: ObservableObject { + var webView: WKWebView? + + + func clearAllWebData(_ webView: WKWebView) { + let dataStore = WKWebsiteDataStore.default() + let dataTypes = WKWebsiteDataStore.allWebsiteDataTypes() + + dataStore.removeData(ofTypes: dataTypes, modifiedSince: Date.distantPast) { + print("All web data cleared") + } + } + + func setupWebViewForSync() { + guard let webView = webView else { return } + + Mindbox.shared.getDeviceUUID { uuid in + guard !uuid.isEmpty else { + print("Device UUID is empty or invalid") + return + } + let script = """ + document.cookie = "mindboxDeviceUUID=\(uuid); path=/"; + window.localStorage.setItem('mindboxDeviceUUID', '\(uuid)'); + """ + webView.evaluateJavaScript(script) { result, error in + if let error = error { + print("Error setting cookies and localStorage: \(error)") + } else { + print("Cookies and localStorage set successfully: \(result ?? "nil")") + } + } + } + } + + func setCookie() { + guard let webView = webView else { return } + + Mindbox.shared.getDeviceUUID { uuid in + guard !uuid.isEmpty else { + print("Device UUID is empty or invalid") + return + } + + let script = """ + document.cookie = "mindboxDeviceUUID=\(uuid); path=/"; + window.localStorage.setItem('mindboxDeviceUUID', '\(uuid)'); + """ + webView.evaluateJavaScript(script) { result, error in + if let error = error { + print("Error setting cookies: \(error)") + } else { + print("Cookies set successfully: \(result ?? "nil")") + } + } + } + } + + func viewCookiesAndLocalStorage() { + guard let webView = webView else { return } + + let script = """ + JSON.stringify({ + cookies: document.cookie, + localStorage: window.localStorage.getItem('mindboxDeviceUUID') + }) + """ + webView.evaluateJavaScript(script) { result, error in + if let error = error { + print("Error retrieving cookies and localStorage: \(error)") + } else { + print("Cookies and LocalStorage: \(result ?? "nil")") + } + } + } +} diff --git a/Example/Example/Views/WebView.swift b/Example/Example/Views/WebView.swift new file mode 100644 index 00000000..e2e2de58 --- /dev/null +++ b/Example/Example/Views/WebView.swift @@ -0,0 +1,86 @@ +// +// WebView.swift +// Example +// +// Created by Sergei Semko on 11/27/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import SwiftUI +@preconcurrency import WebKit + +struct WebView: UIViewRepresentable { + let url: URL + + var viewModel: ViewModel + + func makeCoordinator() -> Coordinator { + Coordinator(viewModel: viewModel) + } + + func makeUIView(context: Context) -> WKWebView { + let wkWebView = WKWebView() + wkWebView.navigationDelegate = context.coordinator + +#if DEBUG + wkWebView.isInspectable = true +#endif + + let request = URLRequest(url: url) + wkWebView.load(request) + + viewModel.setWebView(wkWebView) + + return wkWebView + } + + func updateUIView(_ uiView: WKWebView, context: Context) { } + + final class Coordinator: NSObject, WKNavigationDelegate { + var viewModel: ViewModel + + init(viewModel: ViewModel) { + self.viewModel = viewModel + } + + func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { + print(#function) + viewModel.isLoading = true + viewModel.errorMessage = nil + print("Page started loading: \(webView.url?.absoluteString ?? "Unknown URL")") + } + + func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { + viewModel.clearAllWebData(webView) + viewModel.setupWebViewForSync() + print(#function) + print("Content started arriving for: \(webView.url?.absoluteString ?? "Unknown URL")") + } + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + print(#function) + print("Page finished loading: \(webView.url?.absoluteString ?? "Unknown URL")") + viewModel.isLoading = false + } + + func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: any Error) { + print(#function) + viewModel.isLoading = false + viewModel.errorMessage = error.localizedDescription + } + + func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { + print(#function) + print("loading error: \(error)") + viewModel.isLoading = false + viewModel.errorMessage = error.localizedDescription + } + + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + if let url = navigationAction.request.url?.absoluteString, url.contains("tracker.js") { + print("Intercepted tracker.js: \(url)") + } + decisionHandler(.allow) + } + } +} diff --git a/Example/MindboxNotificationServiceExtension/NotificationService.swift b/Example/MindboxNotificationServiceExtension/NotificationService.swift index ab6ef4b9..5496fe03 100644 --- a/Example/MindboxNotificationServiceExtension/NotificationService.swift +++ b/Example/MindboxNotificationServiceExtension/NotificationService.swift @@ -14,14 +14,6 @@ class NotificationService: UNNotificationServiceExtension { lazy var mindboxService = MindboxNotificationService() override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { - let userInfo = request.content.userInfo - - if mindboxService.isMindboxPush(userInfo: userInfo), let mindboxPushNotification = mindboxService.getMindboxPushData(userInfo: userInfo) { - Task { - await saveSwiftDataItem(mindboxPushNotification) - } - } - mindboxService.didReceive(request, withContentHandler: contentHandler) } @@ -30,24 +22,4 @@ class NotificationService: UNNotificationServiceExtension { // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. mindboxService.serviceExtensionTimeWillExpire() } - - @MainActor - private func saveSwiftDataItem(_ mindboxPushNotification: MBPushNotification) async { - let context = SwiftDataManager.shared.container.mainContext - - let push = PushNotification(title: mindboxPushNotification.aps?.alert?.title, - body: mindboxPushNotification.aps?.alert?.body, - clickUrl: mindboxPushNotification.clickUrl, - imageUrl: mindboxPushNotification.imageUrl, - payload: mindboxPushNotification.payload, - uniqueKey: mindboxPushNotification.uniqueKey) - let newItem = Item(timestamp: Date(), pushNotification: push) - - context.insert(newItem) - do { - try context.save() - } catch { - print("Failed to save context: \(error.localizedDescription)") - } - } } From 9385bf25294c582feb33b1ed8b838e81a92a5d31 Mon Sep 17 00:00:00 2001 From: Sergei Semko <28645140+justSmK@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:41:47 +0300 Subject: [PATCH 2/4] MBX-3751: Example with WebView --- Example/Example.xcodeproj/project.pbxproj | 4 - Example/Example/AppDelegate.swift | 6 +- Example/Example/SceneDelegate.swift | 3 +- Example/Example/ViewModels/ViewModel.swift | 166 +++++++--------- Example/Example/Views/ContentView.swift | 68 +------ Example/Example/Views/TestPage.swift | 210 --------------------- Example/Example/Views/WebView.swift | 47 +---- 7 files changed, 80 insertions(+), 424 deletions(-) delete mode 100644 Example/Example/Views/TestPage.swift diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index 38d60134..d0f64be0 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -22,7 +22,6 @@ 0AEDBC962BB6FE4900EE8722 /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEDBC952BB6FE4900EE8722 /* ViewModel.swift */; }; 43E1C73D7E87903545C2ACBC /* Pods_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9AF8ABB9430FE76D72FE17D /* Pods_Example.framework */; }; 472137C12CF784D400354602 /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472137C02CF784D400354602 /* WebView.swift */; }; - 476FC2002CF89E340097EEBD /* TestPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 476FC1FF2CF89E340097EEBD /* TestPage.swift */; }; 9E3108F96D4F26745D3B37A4 /* Pods_MindboxNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AF2BFD8ABA97C73F589C5DB /* Pods_MindboxNotificationServiceExtension.framework */; }; D9585975AC05213E1682C760 /* Pods_MindboxNotificationContentExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 91EB2315545CE887D05CB505 /* Pods_MindboxNotificationContentExtension.framework */; }; /* End PBXBuildFile section */ @@ -82,7 +81,6 @@ 0AEDBC9C2BB7177A00EE8722 /* Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Example.entitlements; sourceTree = ""; }; 1B29989833584963EC18BDF2 /* Pods-MindboxNotificationContentExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MindboxNotificationContentExtension.release.xcconfig"; path = "Target Support Files/Pods-MindboxNotificationContentExtension/Pods-MindboxNotificationContentExtension.release.xcconfig"; sourceTree = ""; }; 472137C02CF784D400354602 /* WebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebView.swift; sourceTree = ""; }; - 476FC1FF2CF89E340097EEBD /* TestPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestPage.swift; sourceTree = ""; }; 47912FD92CB42D640063387D /* AppDelegate_IDFA.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate_IDFA.swift; sourceTree = ""; }; 47D63E2C2C2EAD220055E7D8 /* Mindbox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Mindbox.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4AF2BFD8ABA97C73F589C5DB /* Pods_MindboxNotificationServiceExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MindboxNotificationServiceExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -188,7 +186,6 @@ isa = PBXGroup; children = ( 0AEDBC912BB6FA4800EE8722 /* ContentView.swift */, - 476FC1FF2CF89E340097EEBD /* TestPage.swift */, 472137C02CF784D400354602 /* WebView.swift */, ); path = Views; @@ -477,7 +474,6 @@ 0AEDBC922BB6FA4800EE8722 /* ContentView.swift in Sources */, 0AEDBC962BB6FE4900EE8722 /* ViewModel.swift in Sources */, 0AEDBC7C2BB6F8F200EE8722 /* SceneDelegate.swift in Sources */, - 476FC2002CF89E340097EEBD /* TestPage.swift in Sources */, 472137C12CF784D400354602 /* WebView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Example/Example/AppDelegate.swift b/Example/Example/AppDelegate.swift index 9c899de6..b0f8ccf9 100644 --- a/Example/Example/AppDelegate.swift +++ b/Example/Example/AppDelegate.swift @@ -18,7 +18,7 @@ final class AppDelegate: MindboxAppDelegate { didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { super.application(application, didFinishLaunchingWithOptions: launchOptions) - print(#function) + do { // To run the application on a physical device you need to change the endpoint // You should also change the application bundle ID in all targets, more details in the readme @@ -34,10 +34,6 @@ final class AppDelegate: MindboxAppDelegate { print(error.localizedDescription) } - Mindbox.shared.getDeviceUUID { deviceUUID in - print("DeviceUUID: \(deviceUUID)") - } - // https://developers.mindbox.ru/docs/ios-send-push-notifications-appdelegate registerForRemoteNotifications() diff --git a/Example/Example/SceneDelegate.swift b/Example/Example/SceneDelegate.swift index 1c93c9b9..60250494 100644 --- a/Example/Example/SceneDelegate.swift +++ b/Example/Example/SceneDelegate.swift @@ -18,11 +18,12 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions ) { - print(#function) guard let windowScene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: windowScene) + let viewModel = ViewModel() window.rootViewController = UIHostingController(rootView: ContentView(webViewModel: viewModel)) + self.window = window window.makeKeyAndVisible() } diff --git a/Example/Example/ViewModels/ViewModel.swift b/Example/Example/ViewModels/ViewModel.swift index 08d16a9a..7977a273 100644 --- a/Example/Example/ViewModels/ViewModel.swift +++ b/Example/Example/ViewModels/ViewModel.swift @@ -6,59 +6,49 @@ // Copyright © 2024 Mindbox. All rights reserved. // -import Foundation -import Mindbox import Observation import WebKit +import Mindbox +import MindboxLogger @Observable final class ViewModel { - var isLoading: Bool = false - var errorMessage: String? = nil - - var webView: WKWebView? + weak var webView: WKWebView? func setWebView(_ webView: WKWebView) { self.webView = webView } - func clearAllWebData(_ webView: WKWebView) { - let dataStore = WKWebsiteDataStore.default() - let dataTypes = WKWebsiteDataStore.allWebsiteDataTypes() - - dataStore.removeData(ofTypes: dataTypes, modifiedSince: Date.distantPast) { - print("All web data cleared") - } - } - - func setupWebViewForSync() { - guard let webView = webView else { return } - - Mindbox.shared.getDeviceUUID { uuid in - guard !uuid.isEmpty else { - print("Device UUID is empty or invalid") + /// Synchronize deviceUUID + func syncMindboxDeviceUUIDs() { + Mindbox.shared.getDeviceUUID { [weak webView] uuid in + guard let webView, !uuid.isEmpty else { + Logger.common(message: "[WebView]: Device UUID is empty or invalid", level: .error) return } + let script = """ - document.cookie = "mindboxDeviceUUID=\(uuid); path=/"; - window.localStorage.setItem('mindboxDeviceUUID', '\(uuid)'); + document.cookie = "mindboxDeviceUUID=\(uuid); path=/"; + window.localStorage.setItem('mindboxDeviceUUID', '\(uuid)'); """ - webView.evaluateJavaScript(script) { _, error in - if let error = error { - print("Error setting cookies and localStorage: \(error)") - } else { - print("Cookies and localStorage set successfully.") + + DispatchQueue.main.async { + webView.evaluateJavaScript(script) { _, error in + if let error = error { + Logger.common(message: "[WebView]: Error setting cookies and localStorage: \(error)", level: .error) + } else { + Logger.common(message: "[WebView]: Cookies and localStorage set successfully.") + } } } + } } func setCookie() { - guard let webView = webView else { return } - - Mindbox.shared.getDeviceUUID { uuid in - guard !uuid.isEmpty else { - print("Device UUID is empty or invalid") + Mindbox.shared.getDeviceUUID { [weak webView] uuid in + guard let webView, !uuid.isEmpty else { + Logger.common(message: "[WebView]: Device UUID is empty or invalid", level: .error) return } @@ -66,18 +56,45 @@ import WebKit document.cookie = "mindboxDeviceUUID=\(uuid); path=/"; window.localStorage.setItem('mindboxDeviceUUID', '\(uuid)'); """ - webView.evaluateJavaScript(script) { _, error in - if let error = error { - print("Error setting cookies: \(error)") - } else { - print("Cookies set successfully.") + + DispatchQueue.main.async { + webView.evaluateJavaScript(script) { _, error in + if let error = error { + Logger.common(message: "[WebView]: Error setting cookies: \(error)", level: .error) + } else { + Logger.common(message: "[WebView]: Cookies set successfully.") + } } } } } + /// Use this method to clear WebView data + func clearAllWebsiteData() { + let dataStore = WKWebsiteDataStore.default() + let dataTypes = WKWebsiteDataStore.allWebsiteDataTypes() + + dataStore.removeData(ofTypes: dataTypes, modifiedSince: Date.distantPast) { + Logger.common(message: "[WebView]: All web data cleared") + } + } +} + +// MARK: - Auxiliary functions for debugging + +extension ViewModel { + + /// Use it to debug data after tracker initialize. + /// For example add button for debug func viewCookiesAndLocalStorage() { guard let webView = webView else { return } + print("\n" + #function) + + Mindbox.shared.getDeviceUUID { uuid in + let message = "[WebView]: Mobile SDK UUID: \(uuid)" + print(message) + Logger.common(message: message) + } let script = """ JSON.stringify({ @@ -85,70 +102,21 @@ import WebKit localStorage: window.localStorage.getItem('mindboxDeviceUUID') || "No value found" }) """ - webView.evaluateJavaScript(script) { result, error in - if let error = error { - print("Error retrieving cookies and localStorage: \(error)") - } else { - print("\nStart===============") - print("Cookies and LocalStorage: \(result ?? "nil")") - print("===============End\n") - } - } - } - -// var webView: WKWebView? - var SDKVersion: String = "" - var deviceUUID: String = "" - var APNSToken: String = "" - - // https://developers.mindbox.ru/docs/ios-sdk-methods - func setupData() { - self.SDKVersion = Mindbox.shared.sdkVersion - Mindbox.shared.getDeviceUUID { deviceUUID in - DispatchQueue.main.async { - self.deviceUUID = deviceUUID - } - } - Mindbox.shared.getAPNSToken { APNSToken in - DispatchQueue.main.async { - self.APNSToken = APNSToken - } - } - } - - // https://developers.mindbox.ru/docs/in-app-targeting-by-custom-operation - func showInAppWithExecuteSyncOperation () { - let json = """ - { "viewProduct": - { "product": - { "ids": - { "website": "1" } - } - } - } - """ - Mindbox.shared.executeSyncOperation(operationSystemName: "APIMethodForReleaseExampleIos", json: json) { result in - switch result { - case .success(let success): - Mindbox.logger.log(level: .info, message: "\(success)") - case .failure(let error): - Mindbox.logger.log(level: .error, message: "\(error)") - } - } - } - - // https://developers.mindbox.ru/docs/in-app-targeting-by-custom-operation - func showInAppWithExecuteAsyncOperation () { - let json = """ - { "viewProduct": - { "product": - { "ids": - { "website": "2" } + DispatchQueue.main.async { + webView.evaluateJavaScript(script) { result, error in + if let error = error { + let message = "[WebView]: Error retrieving cookies and localStorage: \(error)" + print(message) + Logger.common(message: message, level: .error) + } else { + let message = "[WebView]: Cookies and LocalStorage: \(result ?? "nil")" + print("Start===============") + print("Cookies and LocalStorage: \(result ?? "nil")") + print("===============End\n") + Logger.common(message: message) } } } - """ - Mindbox.shared.executeAsyncOperation(operationSystemName: "APIMethodForReleaseExampleIos", json: json) } } diff --git a/Example/Example/Views/ContentView.swift b/Example/Example/Views/ContentView.swift index dc0c56c2..7ce412e6 100644 --- a/Example/Example/Views/ContentView.swift +++ b/Example/Example/Views/ContentView.swift @@ -11,85 +11,19 @@ import SwiftUI struct ContentView: View { var webViewModel: ViewModel - let url: URL = URL(string: "https://personalization-test-site-staging.mindbox.ru/")! + let url = URL(string: "https://your-website.com/") var body: some View { VStack { - let _ = print(#function) WebView(url: url, viewModel: webViewModel) - .overlay(loadingOverlay) - - if let errorMessage = webViewModel.errorMessage { - Text("Error: \(errorMessage)") - .foregroundColor(.red) - .padding() - } Button("Print in console Cookies and LocalStorage") { webViewModel.viewCookiesAndLocalStorage() - print("LOL") }.padding() } } - - @ViewBuilder - private var loadingOverlay: some View { - if webViewModel.isLoading { - ZStack { - Color.black.opacity(0.3) - ProgressView("Loading...") - } - } - } - } -//struct MainView: View { -// -// var viewModel: MainViewModel -// @State private var showingAlert = !UserDefaults.standard.bool(forKey: "ShownAlert") -// -// var body: some View { -// NavigationStack { -// ZStack { -// Color.mbBackground.ignoresSafeArea() -// VStack(spacing: 32) { -// ButtonsView(viewModel: viewModel) -// SDKDataView(viewModel: viewModel) -// ZStack { -// RoundedRectangle(cornerRadius: 20) -// .frame(maxWidth: 350) -// .frame(maxHeight: 110) -// .foregroundColor(.mbView) -// NavigationLink("Open Notification Center") { -// NotificationCenterView(viewModel: NotificationCenterViewModel()) -// .modelContainer(SwiftDataManager.shared.container) -// } -// .frame(maxWidth: 250) -// .frame(maxHeight: 50) -// .background(Color.mbGreen) -// .cornerRadius(16) -// .tint(.white) -// } -// } -// }.onAppear { -// viewModel.setupData() -// let alertShown = UserDefaults.standard.bool(forKey: "ShownAlert") -// if !alertShown { -// UserDefaults.standard.set(true, forKey: "ShownAlert") -// } -// } -// .alert("In-App can only be shown once per session", isPresented: $showingAlert) { -// Button("OK", role: .cancel) {} -// } -// } -// } -//} -// -//#Preview { -// MainView(viewModel: MainViewModel()) -//} - #Preview { ContentView(webViewModel: ViewModel()) } diff --git a/Example/Example/Views/TestPage.swift b/Example/Example/Views/TestPage.swift deleted file mode 100644 index 607b7284..00000000 --- a/Example/Example/Views/TestPage.swift +++ /dev/null @@ -1,210 +0,0 @@ -// -// TestPage.swift -// Example -// -// Created by Sergei Semko on 11/28/24. -// Copyright © 2024 Mindbox. All rights reserved. -// - - -// webViewTestApp.swift -// webViewTest -// -// Created by Sergey Sozinov on 20.11.2024. -// - -import SwiftUI -import WebKit -import Mindbox - -//@main -struct webViewTestApp: App { -// let persistenceController = PersistenceController.shared - let mindboxSdkConfig: MBConfiguration - - init() { - // Логика инициализации Mindbox - do { - mindboxSdkConfig = try MBConfiguration( - endpoint: "Test-staging.Test01", - domain: "api-staging.mindbox.ru", - subscribeCustomerIfCreated: true, - shouldCreateCustomer: true - ) - Mindbox.shared.initialization(configuration: mindboxSdkConfig) - Mindbox.shared.getDeviceUUID { deviceUUID in - print("Device UUID: \(deviceUUID)") - } - } catch { - fatalError("Failed to initialize MBConfiguration: \(error.localizedDescription)") - } - } - - var body: some Scene { - WindowGroup { - ContentViewTest() -// .environment(\.managedObjectContext, persistenceController.container.viewContext) - } - } -} - -struct ContentViewTest: View { - @StateObject private var webViewModel = WebViewModelTest() - - var body: some View { - VStack { - WebViewContainer(webViewModel: webViewModel) - .frame(maxHeight: .infinity) - - HStack { - Button("SetCookie") { - webViewModel.setCookie() - } - Button("View") { - webViewModel.viewCookiesAndLocalStorage() - } - } - } - } -} - -struct WebViewContainer: UIViewRepresentable { - @ObservedObject var webViewModel: WebViewModelTest - - func makeUIView(context: Context) -> WKWebView { - let preferences = WKPreferences() - preferences.javaScriptEnabled = true - - // Настройка WKWebViewConfiguration - let configuration = WKWebViewConfiguration() - configuration.preferences = preferences - let webView = WKWebView(frame: .zero, configuration: configuration) - if #available(iOS 16.4, *) { - webView.isInspectable = true - } - webView.navigationDelegate = context.coordinator - webViewModel.webView = webView - - loadInitialURL(webView) - return webView - } - - func updateUIView(_ uiView: WKWebView, context: Context) {} - - private func loadInitialURL(_ webView: WKWebView) { - let url = URL(string: "https://personalization-test-site-staging.mindbox.ru/")! - let request = URLRequest(url: url) - webView.load(request) - } - - func makeCoordinator() -> WebViewCoordinator { - WebViewCoordinator(webViewModel: webViewModel) - } -} - -class WebViewCoordinator: NSObject, WKNavigationDelegate { - private let webViewModel: WebViewModelTest - - init(webViewModel: WebViewModelTest) { - self.webViewModel = webViewModel - } - - func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { - print("Page started loading: \(webView.url?.absoluteString ?? "Unknown URL")") - - } - - func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { - webViewModel.clearAllWebData(webView) - webViewModel.setupWebViewForSync() - print("Content started arriving for: \(webView.url?.absoluteString ?? "Unknown URL")") - } - func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - print("Page finished loading: \(webView.url?.absoluteString ?? "Unknown URL")") - webViewModel.viewCookiesAndLocalStorage() - } - - func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - if let url = navigationAction.request.url?.absoluteString, url.contains("tracker.js") { - print("Intercepted tracker.js: \(url)") - } - decisionHandler(.allow) - } -} - -class WebViewModelTest: ObservableObject { - var webView: WKWebView? - - - func clearAllWebData(_ webView: WKWebView) { - let dataStore = WKWebsiteDataStore.default() - let dataTypes = WKWebsiteDataStore.allWebsiteDataTypes() - - dataStore.removeData(ofTypes: dataTypes, modifiedSince: Date.distantPast) { - print("All web data cleared") - } - } - - func setupWebViewForSync() { - guard let webView = webView else { return } - - Mindbox.shared.getDeviceUUID { uuid in - guard !uuid.isEmpty else { - print("Device UUID is empty or invalid") - return - } - let script = """ - document.cookie = "mindboxDeviceUUID=\(uuid); path=/"; - window.localStorage.setItem('mindboxDeviceUUID', '\(uuid)'); - """ - webView.evaluateJavaScript(script) { result, error in - if let error = error { - print("Error setting cookies and localStorage: \(error)") - } else { - print("Cookies and localStorage set successfully: \(result ?? "nil")") - } - } - } - } - - func setCookie() { - guard let webView = webView else { return } - - Mindbox.shared.getDeviceUUID { uuid in - guard !uuid.isEmpty else { - print("Device UUID is empty or invalid") - return - } - - let script = """ - document.cookie = "mindboxDeviceUUID=\(uuid); path=/"; - window.localStorage.setItem('mindboxDeviceUUID', '\(uuid)'); - """ - webView.evaluateJavaScript(script) { result, error in - if let error = error { - print("Error setting cookies: \(error)") - } else { - print("Cookies set successfully: \(result ?? "nil")") - } - } - } - } - - func viewCookiesAndLocalStorage() { - guard let webView = webView else { return } - - let script = """ - JSON.stringify({ - cookies: document.cookie, - localStorage: window.localStorage.getItem('mindboxDeviceUUID') - }) - """ - webView.evaluateJavaScript(script) { result, error in - if let error = error { - print("Error retrieving cookies and localStorage: \(error)") - } else { - print("Cookies and LocalStorage: \(result ?? "nil")") - } - } - } -} diff --git a/Example/Example/Views/WebView.swift b/Example/Example/Views/WebView.swift index e2e2de58..1ae2b3ef 100644 --- a/Example/Example/Views/WebView.swift +++ b/Example/Example/Views/WebView.swift @@ -7,10 +7,11 @@ // import SwiftUI -@preconcurrency import WebKit +import WebKit +import MindboxLogger struct WebView: UIViewRepresentable { - let url: URL + let url: URL? var viewModel: ViewModel @@ -23,9 +24,12 @@ struct WebView: UIViewRepresentable { wkWebView.navigationDelegate = context.coordinator #if DEBUG + // Use this to enable debug wkWebView.isInspectable = true #endif + guard let url else { return wkWebView } + let request = URLRequest(url: url) wkWebView.load(request) @@ -43,44 +47,11 @@ struct WebView: UIViewRepresentable { self.viewModel = viewModel } - func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { - print(#function) - viewModel.isLoading = true - viewModel.errorMessage = nil - print("Page started loading: \(webView.url?.absoluteString ?? "Unknown URL")") - } - func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { - viewModel.clearAllWebData(webView) - viewModel.setupWebViewForSync() - print(#function) - print("Content started arriving for: \(webView.url?.absoluteString ?? "Unknown URL")") - } - - func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - print(#function) - print("Page finished loading: \(webView.url?.absoluteString ?? "Unknown URL")") - viewModel.isLoading = false - } - - func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: any Error) { - print(#function) - viewModel.isLoading = false - viewModel.errorMessage = error.localizedDescription - } - - func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { - print(#function) - print("loading error: \(error)") - viewModel.isLoading = false - viewModel.errorMessage = error.localizedDescription - } + viewModel.syncMindboxDeviceUUIDs() - func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - if let url = navigationAction.request.url?.absoluteString, url.contains("tracker.js") { - print("Intercepted tracker.js: \(url)") - } - decisionHandler(.allow) + let message = "[WebView]: \(#function): Content started arriving for: \(webView.url?.absoluteString ?? "Unknown URL")" + Logger.common(message: message) } } } From 3af03fb4cf6ada95b404fc50c3108be7628bc2dd Mon Sep 17 00:00:00 2001 From: Sergei Semko <28645140+justSmK@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:27:42 +0300 Subject: [PATCH 3/4] MBX-3751: Fix `MindboxInitConfiguration` --- Example/Example/AppDelegate.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Example/Example/AppDelegate.swift b/Example/Example/AppDelegate.swift index b0f8ccf9..a2751e9e 100644 --- a/Example/Example/AppDelegate.swift +++ b/Example/Example/AppDelegate.swift @@ -24,8 +24,8 @@ final class AppDelegate: MindboxAppDelegate { // You should also change the application bundle ID in all targets, more details in the readme // You can still run the application on the simulator to see In-Apps let mindboxSdkConfig = try MBConfiguration( - endpoint: "Test-staging.Test01", - domain: "api-staging.mindbox.ru", + endpoint: "Mpush-test.ReleaseExample.IosApp", + domain: "api.mindbox.ru", subscribeCustomerIfCreated: true, shouldCreateCustomer: true ) From bbac308b7e92b833295cb474b30f430fa5aed5e5 Mon Sep 17 00:00:00 2001 From: Sergei Semko <28645140+justSmK@users.noreply.github.com> Date: Wed, 4 Dec 2024 15:13:28 +0300 Subject: [PATCH 4/4] MBX-3751: Fix comments --- Example/Example/ViewModels/ViewModel.swift | 67 ++++++---------------- Example/Example/Views/ContentView.swift | 12 +++- Example/Example/Views/WebView.swift | 27 +++++---- 3 files changed, 42 insertions(+), 64 deletions(-) diff --git a/Example/Example/ViewModels/ViewModel.swift b/Example/Example/ViewModels/ViewModel.swift index 7977a273..d422cd09 100644 --- a/Example/Example/ViewModels/ViewModel.swift +++ b/Example/Example/ViewModels/ViewModel.swift @@ -9,21 +9,14 @@ import Observation import WebKit import Mindbox -import MindboxLogger @Observable final class ViewModel { - weak var webView: WKWebView? - - func setWebView(_ webView: WKWebView) { - self.webView = webView - } - /// Synchronize deviceUUID - func syncMindboxDeviceUUIDs() { - Mindbox.shared.getDeviceUUID { [weak webView] uuid in - guard let webView, !uuid.isEmpty else { - Logger.common(message: "[WebView]: Device UUID is empty or invalid", level: .error) + func syncMindboxDeviceUUIDs(with webView: WKWebView) { + Mindbox.shared.getDeviceUUID { uuid in + guard !uuid.isEmpty else { + Mindbox.logger.log(level: .error, message: "[WebView]: Device UUID is empty or invalid") return } @@ -35,34 +28,9 @@ import MindboxLogger DispatchQueue.main.async { webView.evaluateJavaScript(script) { _, error in if let error = error { - Logger.common(message: "[WebView]: Error setting cookies and localStorage: \(error)", level: .error) + Mindbox.logger.log(level: .error, message: "[WebView]: Error setting cookies and localStorage: \(error)") } else { - Logger.common(message: "[WebView]: Cookies and localStorage set successfully.") - } - } - } - - } - } - - func setCookie() { - Mindbox.shared.getDeviceUUID { [weak webView] uuid in - guard let webView, !uuid.isEmpty else { - Logger.common(message: "[WebView]: Device UUID is empty or invalid", level: .error) - return - } - - let script = """ - document.cookie = "mindboxDeviceUUID=\(uuid); path=/"; - window.localStorage.setItem('mindboxDeviceUUID', '\(uuid)'); - """ - - DispatchQueue.main.async { - webView.evaluateJavaScript(script) { _, error in - if let error = error { - Logger.common(message: "[WebView]: Error setting cookies: \(error)", level: .error) - } else { - Logger.common(message: "[WebView]: Cookies set successfully.") + Mindbox.logger.log(level: .default, message: "[WebView]: Cookies and localStorage set successfully.") } } } @@ -75,7 +43,7 @@ import MindboxLogger let dataTypes = WKWebsiteDataStore.allWebsiteDataTypes() dataStore.removeData(ofTypes: dataTypes, modifiedSince: Date.distantPast) { - Logger.common(message: "[WebView]: All web data cleared") + Mindbox.logger.log(level: .default, message: "[WebView]: All web data cleared") } } } @@ -86,35 +54,34 @@ extension ViewModel { /// Use it to debug data after tracker initialize. /// For example add button for debug - func viewCookiesAndLocalStorage() { - guard let webView = webView else { return } + func viewCookiesAndLocalStorage(with webView: WKWebView) { print("\n" + #function) Mindbox.shared.getDeviceUUID { uuid in let message = "[WebView]: Mobile SDK UUID: \(uuid)" print(message) - Logger.common(message: message) + Mindbox.logger.log(level: .default, message: message) } - + let script = """ - JSON.stringify({ - cookies: document.cookie || "No cookies found", - localStorage: window.localStorage.getItem('mindboxDeviceUUID') || "No value found" - }) - """ + JSON.stringify({ + cookies: document.cookie || "No cookies found", + localStorage: window.localStorage.getItem('mindboxDeviceUUID') || "No value found" + }) + """ DispatchQueue.main.async { webView.evaluateJavaScript(script) { result, error in if let error = error { let message = "[WebView]: Error retrieving cookies and localStorage: \(error)" print(message) - Logger.common(message: message, level: .error) + Mindbox.logger.log(level: .error, message: message) } else { let message = "[WebView]: Cookies and LocalStorage: \(result ?? "nil")" print("Start===============") print("Cookies and LocalStorage: \(result ?? "nil")") print("===============End\n") - Logger.common(message: message) + Mindbox.logger.log(level: .default, message: message) } } } diff --git a/Example/Example/Views/ContentView.swift b/Example/Example/Views/ContentView.swift index 7ce412e6..b90599e2 100644 --- a/Example/Example/Views/ContentView.swift +++ b/Example/Example/Views/ContentView.swift @@ -7,18 +7,24 @@ // import SwiftUI +import WebKit struct ContentView: View { - var webViewModel: ViewModel - let url = URL(string: "https://your-website.com/") + let webViewModel: ViewModel + + private let url = URL(string: "https://your-website.com/") var body: some View { VStack { WebView(url: url, viewModel: webViewModel) Button("Print in console Cookies and LocalStorage") { - webViewModel.viewCookiesAndLocalStorage() + guard let webView = WebView.currentWebView else { + print("WebView is not initialized yet") + return + } + webViewModel.viewCookiesAndLocalStorage(with: webView) }.padding() } } diff --git a/Example/Example/Views/WebView.swift b/Example/Example/Views/WebView.swift index 1ae2b3ef..67d101bc 100644 --- a/Example/Example/Views/WebView.swift +++ b/Example/Example/Views/WebView.swift @@ -8,11 +8,13 @@ import SwiftUI import WebKit -import MindboxLogger +import Mindbox struct WebView: UIViewRepresentable { - let url: URL? + static var currentWebView: WKWebView? + + let url: URL? var viewModel: ViewModel func makeCoordinator() -> Coordinator { @@ -33,25 +35,28 @@ struct WebView: UIViewRepresentable { let request = URLRequest(url: url) wkWebView.load(request) - viewModel.setWebView(wkWebView) - + WebView.currentWebView = wkWebView return wkWebView } - + func updateUIView(_ uiView: WKWebView, context: Context) { } - final class Coordinator: NSObject, WKNavigationDelegate { + final class Coordinator: NSObject { var viewModel: ViewModel init(viewModel: ViewModel) { self.viewModel = viewModel } + } +} - func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { - viewModel.syncMindboxDeviceUUIDs() +// MARK: - WKNavigationDelegate - let message = "[WebView]: \(#function): Content started arriving for: \(webView.url?.absoluteString ?? "Unknown URL")" - Logger.common(message: message) - } +extension WebView.Coordinator: WKNavigationDelegate { + func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { + viewModel.syncMindboxDeviceUUIDs(with: webView) + + let message = "[WebView]: \(#function): Content started arriving for: \(webView.url?.absoluteString ?? "Unknown URL")" + Mindbox.logger.log(level: .debug, message: message) } }