From 49f156204a6168a4425c382846c3f5db311e1559 Mon Sep 17 00:00:00 2001 From: Michelle Dayangco Date: Fri, 13 Feb 2026 17:56:54 +0800 Subject: [PATCH 01/11] Integrate sentry --- Virtusize.xcodeproj/project.pbxproj | 50 +++++++++++++++++-- Virtusize/Sources/Info.plist | 4 ++ .../Models/VirtusizeParamsBuilder.swift | 17 +++++++ 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/Virtusize.xcodeproj/project.pbxproj b/Virtusize.xcodeproj/project.pbxproj index 3d3bb340..d610bb7b 100644 --- a/Virtusize.xcodeproj/project.pbxproj +++ b/Virtusize.xcodeproj/project.pbxproj @@ -24,7 +24,6 @@ 52D1E09B2D6DCEA90073737B /* I18nTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D1E09A2D6DCEA30073737B /* I18nTests.swift */; }; 52D1E09E2D6DD09A0073737B /* MockURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D1E09D2D6DD0940073737B /* MockURLSession.swift */; }; 52D1E0A02D6DD5900073737B /* I18nFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D1E09F2D6DD58A0073737B /* I18nFixtures.swift */; }; - 52FDA2882D3A5C8F007F5AC8 /* VirtusizeAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52FDA2872D3A5C8F007F5AC8 /* VirtusizeAuth.framework */; }; 741DD0692DA3D06B007DF2C3 /* VirtusizeFlutter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741DD0682DA3D06B007DF2C3 /* VirtusizeFlutter.swift */; }; 741DD06D2DA42E63007DF2C3 /* VirtusizeFlutterProductEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741DD06C2DA42E63007DF2C3 /* VirtusizeFlutterProductEventHandler.swift */; }; 748DF81A2E3FF2A9002CA7FC /* VirtusizeGetSizeParamsShoe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748DF8192E3FF2A9002CA7FC /* VirtusizeGetSizeParamsShoe.swift */; }; @@ -77,7 +76,6 @@ 9C787FCD25DB7A3D00346F2A /* SizeRecommendationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C787FCC25DB7A3D00346F2A /* SizeRecommendationType.swift */; }; 9C84B3FD26C2566B00DDF434 /* VirtusizeViewEventProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C84B3FC26C2566B00DDF434 /* VirtusizeViewEventProtocol.swift */; }; 9C84B40126C276CA00DDF434 /* VirtusizeNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C84B40026C276CA00DDF434 /* VirtusizeNotification.swift */; }; - 9C92B3A52CB2DF7F001DD4A2 /* VirtusizeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C92B3A42CB2DF7F001DD4A2 /* VirtusizeCore.framework */; }; 9C969FD124EB5F8A00DD642F /* VirtusizeI18nLocalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C969FD024EB5F8A00DD642F /* VirtusizeI18nLocalization.swift */; }; 9C969FD324EBE0C400DD642F /* DeserializerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C969FD224EBE0C300DD642F /* DeserializerTests.swift */; }; 9C969FD824EBF71200DD642F /* i18n_en.json in Resources */ = {isa = PBXBuildFile; fileRef = 9C969FD524EBF71200DD642F /* i18n_en.json */; }; @@ -119,8 +117,13 @@ 9CFF40FC25B974D200B21D4E /* VirtusizeViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CFF40FB25B974D200B21D4E /* VirtusizeViewStyle.swift */; }; 9CFF410D25B98D4E00B21D4E /* VirtusizeInPageStandardViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CFF410C25B98D4E00B21D4E /* VirtusizeInPageStandardViewModel.swift */; }; 9CFF411325B98FA900B21D4E /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CFF411225B98FA900B21D4E /* Observable.swift */; }; + D760129E2F3F2A1700AF72AA /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = D760129D2F3F2A1700AF72AA /* Sentry */; }; D7BC74B42EDDD063004616BF /* product_types.json in Resources */ = {isa = PBXBuildFile; fileRef = D7BC74B32EDDD063004616BF /* product_types.json */; }; D7BC74B52EDDD063004616BF /* product_types.json in Resources */ = {isa = PBXBuildFile; fileRef = D7BC74B32EDDD063004616BF /* product_types.json */; }; + D7C67BE72F3DC56E0007167B /* VirtusizeAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52FDA2872D3A5C8F007F5AC8 /* VirtusizeAuth.framework */; }; + D7C67BE82F3DC56E0007167B /* VirtusizeAuth.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 52FDA2872D3A5C8F007F5AC8 /* VirtusizeAuth.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D7C67BEA2F3DC5700007167B /* VirtusizeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C92B3A42CB2DF7F001DD4A2 /* VirtusizeCore.framework */; }; + D7C67BEB2F3DC5700007167B /* VirtusizeCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9C92B3A42CB2DF7F001DD4A2 /* VirtusizeCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DF71642A21A28858002C7202 /* VirtusizeEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF71642921A28858002C7202 /* VirtusizeEvent.swift */; }; DF71642C21A28939002C7202 /* VirtusizeEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF71642B21A28939002C7202 /* VirtusizeEnvironment.swift */; }; DF71642E21A28DE8002C7202 /* VirtusizeProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF71642D21A28DE8002C7202 /* VirtusizeProduct.swift */; }; @@ -139,6 +142,21 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + D7C67BE92F3DC56E0007167B /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + D7C67BE82F3DC56E0007167B /* VirtusizeAuth.framework in Embed Frameworks */, + D7C67BEB2F3DC5700007167B /* VirtusizeCore.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 4D6C5799205FCBE900DA910E /* VirtusizeAPIRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VirtusizeAPIRequest.swift; sourceTree = ""; }; 4DED8D1C205755C3001CA7DF /* Virtusize.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Virtusize.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -269,8 +287,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 52FDA2882D3A5C8F007F5AC8 /* VirtusizeAuth.framework in Frameworks */, - 9C92B3A52CB2DF7F001DD4A2 /* VirtusizeCore.framework in Frameworks */, + D7C67BE72F3DC56E0007167B /* VirtusizeAuth.framework in Frameworks */, + D7C67BEA2F3DC5700007167B /* VirtusizeCore.framework in Frameworks */, + D760129E2F3F2A1700AF72AA /* Sentry in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -646,6 +665,7 @@ 4DED8D19205755C3001CA7DF /* Headers */, 4DED8D1A205755C3001CA7DF /* Resources */, DF6B5C91219BD262000B3402 /* ShellScript */, + D7C67BE92F3DC56E0007167B /* Embed Frameworks */, ); buildRules = ( ); @@ -706,6 +726,9 @@ Base, ); mainGroup = 4DED8D12205755C3001CA7DF; + packageReferences = ( + D760129C2F3F2A1700AF72AA /* XCRemoteSwiftPackageReference "sentry-cocoa" */, + ); productRefGroup = 4DED8D1D205755C3001CA7DF /* Products */; projectDirPath = ""; projectRoot = ""; @@ -1180,6 +1203,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + D760129C2F3F2A1700AF72AA /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/getsentry/sentry-cocoa"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 9.4.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + D760129D2F3F2A1700AF72AA /* Sentry */ = { + isa = XCSwiftPackageProductDependency; + package = D760129C2F3F2A1700AF72AA /* XCRemoteSwiftPackageReference "sentry-cocoa" */; + productName = Sentry; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 4DED8D13205755C3001CA7DF /* Project object */; } diff --git a/Virtusize/Sources/Info.plist b/Virtusize/Sources/Info.plist index fcd36391..c2662653 100644 --- a/Virtusize/Sources/Info.plist +++ b/Virtusize/Sources/Info.plist @@ -20,5 +20,9 @@ $(CURRENT_PROJECT_VERSION) NSPrincipalClass + SentryDSN + https://f2ae6aac72a9ec47631fdbc4cd8589e6@o903.ingest.us.sentry.io/4510877679353856 + SentryTracesSampleRate + 1 diff --git a/Virtusize/Sources/Models/VirtusizeParamsBuilder.swift b/Virtusize/Sources/Models/VirtusizeParamsBuilder.swift index f7b0de3a..dd9ed452 100644 --- a/Virtusize/Sources/Models/VirtusizeParamsBuilder.swift +++ b/Virtusize/Sources/Models/VirtusizeParamsBuilder.swift @@ -22,6 +22,7 @@ // THE SOFTWARE. // +import Sentry /// The builder patten to help initialize the `VirtusizeParams` object public class VirtusizeParamsBuilder { private var region: VirtusizeRegion = VirtusizeRegion.JAPAN @@ -76,10 +77,26 @@ public class VirtusizeParamsBuilder { serviceEnvironment = value return self } + + private func initSentry(){ + let moduleBundle = Bundle(for: Virtusize.self) + + if let dsn = moduleBundle.object(forInfoDictionaryKey: "SentryDSN") as? String { + SentrySDK.start { options in + options.dsn = dsn + options.tracesSampleRate = moduleBundle.object(forInfoDictionaryKey: "SentryTracesSampleRate") as? NSNumber ?? 1.0 + options.debug = true + } + } + } + + public func build() -> VirtusizeParams { /// Assigns the region value to a default one corresponding the Virtusize environment region = Virtusize.environment.virtusizeRegion() + initSentry() + return VirtusizeParams( region: region, language: language ?? region.defaultLanguage(), From 7a2c3b6672e4777035e5e159c6289febb1ec837b Mon Sep 17 00:00:00 2001 From: OleS Date: Tue, 24 Feb 2026 01:55:36 +0200 Subject: [PATCH 02/11] Add Sentry logs + metrics to SDK Sentry Logs: - product check - API requests - SDK errors - WebView events - Request product cancellation Sentry Metrics: - user saw product counter - send order counter - SDK errors counter --- Package.swift | 13 +- Virtusize.podspec | 1 + Virtusize.xcodeproj/project.pbxproj | 4 + .../Internal/API/VirtusizeAPIService.swift | 1 - .../Internal/DefaultEventHandler.swift | 63 +++++++ .../Internal/VirtusizeSentryTracker.swift | 160 ++++++++++++++++++ .../Sources/Models/VirtusizeOrderItem.swift | 2 +- .../UI/VirtusizeWebViewController.swift | 2 +- Virtusize/Sources/Virtusize.swift | 50 +++++- 9 files changed, 285 insertions(+), 11 deletions(-) create mode 100644 Virtusize/Sources/Internal/VirtusizeSentryTracker.swift diff --git a/Package.swift b/Package.swift index 937e7d25..e6fd75fb 100644 --- a/Package.swift +++ b/Package.swift @@ -19,11 +19,20 @@ let package = Package( targets: ["VirtusizeCore"] ) ], - + dependencies: [ + .package( + url: "https://github.com/getsentry/sentry-cocoa.git", + from: "8.36.0" + ) + ], targets: [ .target( name: "Virtusize", - dependencies: ["VirtusizeCore", "VirtusizeAuth"], + dependencies: [ + "VirtusizeCore", + "VirtusizeAuth", + .product(name: "Sentry", package: "sentry-cocoa") + ], path: "Virtusize/Sources", exclude: ["Info.plist"], resources: [.process("Resources")] diff --git a/Virtusize.podspec b/Virtusize.podspec index 090becbc..868bdaa5 100644 --- a/Virtusize.podspec +++ b/Virtusize.podspec @@ -18,6 +18,7 @@ Pod::Spec.new do |s| s.resource_bundle = { 'Virtusize' => ["Virtusize/Sources/Resources/**/*.lproj", "Virtusize/Sources/Resources/PrivacyInfo.xcprivacy"] } s.pod_target_xcconfig = { 'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'YES' } + s.dependency 'Sentry', '~> 8.36' s.subspec 'VirtusizeCore' do |ss| ss.dependency 'VirtusizeCore', "#{s.version}" end diff --git a/Virtusize.xcodeproj/project.pbxproj b/Virtusize.xcodeproj/project.pbxproj index d610bb7b..f08c1f99 100644 --- a/Virtusize.xcodeproj/project.pbxproj +++ b/Virtusize.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 74F8E3D02E0EA477002CAE98 /* UIImageViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74F8E3CF2E0EA473002CAE98 /* UIImageViewExtensions.swift */; }; 8455FFD42B29A0C500019B1B /* VirtusizeGetSizeItemsParam.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8455FFD32B29A0C500019B1B /* VirtusizeGetSizeItemsParam.swift */; }; 84CF03B92BD7A64A00C08920 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 84CF03B82BD7A64A00C08920 /* PrivacyInfo.xcprivacy */; }; + 9BE02B152F4D013000083151 /* VirtusizeSentryTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE02B142F4D013000083151 /* VirtusizeSentryTracker.swift */; }; 9C0EBA2225BACADD00F1D746 /* VirtusizeProductImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C0EBA2125BACADD00F1D746 /* VirtusizeProductImage.swift */; }; 9C106EB024FCE0C70012EE51 /* VirtusizeInPageStandard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C106EAF24FCE0C70012EE51 /* VirtusizeInPageStandard.swift */; }; 9C106EB424FE41EC0012EE51 /* VirtusizeProductImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C106EB324FE41EC0012EE51 /* VirtusizeProductImageView.swift */; }; @@ -185,6 +186,7 @@ 74F8E3CF2E0EA473002CAE98 /* UIImageViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageViewExtensions.swift; sourceTree = ""; }; 8455FFD32B29A0C500019B1B /* VirtusizeGetSizeItemsParam.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VirtusizeGetSizeItemsParam.swift; sourceTree = ""; }; 84CF03B82BD7A64A00C08920 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 9BE02B142F4D013000083151 /* VirtusizeSentryTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VirtusizeSentryTracker.swift; sourceTree = ""; }; 9C0EBA2125BACADD00F1D746 /* VirtusizeProductImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VirtusizeProductImage.swift; sourceTree = ""; }; 9C106EAF24FCE0C70012EE51 /* VirtusizeInPageStandard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VirtusizeInPageStandard.swift; sourceTree = ""; }; 9C106EB324FE41EC0012EE51 /* VirtusizeProductImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VirtusizeProductImageView.swift; sourceTree = ""; }; @@ -306,6 +308,7 @@ 4D6C5794205FB9FC00DA910E /* Internal */ = { isa = PBXGroup; children = ( + 9BE02B142F4D013000083151 /* VirtusizeSentryTracker.swift */, 52ACB08F2D7083C60011E51E /* DefaultEventHandler.swift */, 9CEC8D88252AB8EE001CBDFB /* Models */, 9C106EB524FE424E0012EE51 /* Views */, @@ -841,6 +844,7 @@ 9C32A14B250279D00062C11D /* VirtusizeInPageView.swift in Sources */, 9C787FC925DB793400346F2A /* VirtusizeEventHandler.swift in Sources */, 9CFF40FC25B974D200B21D4E /* VirtusizeViewStyle.swift in Sources */, + 9BE02B152F4D013000083151 /* VirtusizeSentryTracker.swift in Sources */, 52ACB0902D7083D10011E51E /* DefaultEventHandler.swift in Sources */, 9CB98B9D24B2C5B400C51F20 /* VirtusizeLanguage.swift in Sources */, 9C5956CD2484F6280014DF51 /* VirtusizeOrderItem.swift in Sources */, diff --git a/Virtusize/Sources/Internal/API/VirtusizeAPIService.swift b/Virtusize/Sources/Internal/API/VirtusizeAPIService.swift index 0e0c8e29..fbcb0dda 100644 --- a/Virtusize/Sources/Internal/API/VirtusizeAPIService.swift +++ b/Virtusize/Sources/Internal/API/VirtusizeAPIService.swift @@ -75,7 +75,6 @@ class VirtusizeAPIService: APIService { guard let request = APIRequest.sendEvent(event, withContext: context) else { return nil } - let response = await VirtusizeAPIService.performAsync(request) guard response.virtusizeError == nil, let data = response.data else { // failed to perform request diff --git a/Virtusize/Sources/Internal/DefaultEventHandler.swift b/Virtusize/Sources/Internal/DefaultEventHandler.swift index ff721767..1c516743 100644 --- a/Virtusize/Sources/Internal/DefaultEventHandler.swift +++ b/Virtusize/Sources/Internal/DefaultEventHandler.swift @@ -23,50 +23,113 @@ // THE SOFTWARE. // +import VirtusizeCore + internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventProtocol { var virtusizeEventHandler: VirtusizeEventHandler? + private var sentryStoreId: String? { + APICache.shared.currentStoreId.map { String($0) } + } + public func userOpenedWidget() { + VirtusizeSentryTracker.trackWebViewEvent( + eventName: VirtusizeEventName.userOpenedWidget.rawValue, + sessionId: VirtusizeSentryTracker.currentSessionId, + storeId: sentryStoreId + ) handleUserOpenedWidget() } public func userAuthData(bid: String?, auth: String?) { + VirtusizeSentryTracker.trackWebViewEvent( + eventName: VirtusizeEventName.userAuthData.rawValue, + sessionId: VirtusizeSentryTracker.currentSessionId, + storeId: sentryStoreId + ) handleUserAuthData(bid: bid, auth: auth) } public func userSelectedProduct(userProductId: Int?) { + VirtusizeSentryTracker.trackWebViewEvent( + eventName: VirtusizeEventName.userSelectedProduct.rawValue, + sessionId: VirtusizeSentryTracker.currentSessionId, + storeId: sentryStoreId + ) handleUserSelectedProduct(userProductId: userProductId) } public func userAddedProduct() { + VirtusizeSentryTracker.trackWebViewEvent( + eventName: VirtusizeEventName.userAddedProduct.rawValue, + sessionId: VirtusizeSentryTracker.currentSessionId, + storeId: sentryStoreId + ) handleUserAddedProduct() } public func userDeletedProduct(userProductId: Int?) { + VirtusizeSentryTracker.trackWebViewEvent( + eventName: VirtusizeEventName.userDeletedProduct.rawValue, + sessionId: VirtusizeSentryTracker.currentSessionId, + storeId: sentryStoreId + ) handleUserDeletedProduct(userProductId: userProductId) } public func userChangedRecommendationType(changedType: SizeRecommendationType?) { + VirtusizeSentryTracker.trackWebViewEvent( + eventName: VirtusizeEventName.userChangedRecommendationType.rawValue, + sessionId: VirtusizeSentryTracker.currentSessionId, + storeId: sentryStoreId + ) handleUserChangedRecommendationType(changedType: changedType) } public func userUpdatedBodyMeasurements(recommendedSize: String?) { + VirtusizeSentryTracker.trackWebViewEvent( + eventName: VirtusizeEventName.userUpdatedBodyMeasurements.rawValue, + sessionId: VirtusizeSentryTracker.currentSessionId, + storeId: sentryStoreId + ) handleUserUpdatedBodyMeasurements(recommendedSize: recommendedSize) } public func userLoggedIn() { + VirtusizeSentryTracker.trackWebViewEvent( + eventName: VirtusizeEventName.userLoggedIn.rawValue, + sessionId: VirtusizeSentryTracker.currentSessionId, + storeId: sentryStoreId + ) handleUserLoggedIn() } public func clearUserData() { + VirtusizeSentryTracker.trackWebViewEvent( + eventName: "user-clear-data", + sessionId: VirtusizeSentryTracker.currentSessionId, + storeId: sentryStoreId + ) + VirtusizeSentryTracker.trackSessionEnd(sessionId: VirtusizeSentryTracker.currentSessionId, storeId: sentryStoreId) handleClearUserData() } public func userClosedWidget() { + VirtusizeSentryTracker.trackWebViewEvent( + eventName: VirtusizeEventName.userClosedWidget.rawValue, + sessionId: VirtusizeSentryTracker.currentSessionId, + storeId: sentryStoreId + ) + VirtusizeSentryTracker.trackSessionEnd(sessionId: VirtusizeSentryTracker.currentSessionId, storeId: sentryStoreId) handleUserClosedWidget() } public func userClickedLanguageSelector(language: VirtusizeLanguage) { + VirtusizeSentryTracker.trackWebViewEvent( + eventName: VirtusizeEventName.userClickedLanguageSelector.rawValue, + sessionId: VirtusizeSentryTracker.currentSessionId, + storeId: sentryStoreId + ) handleUserClickedLanguageSelector(language: language) } } diff --git a/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift b/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift new file mode 100644 index 00000000..e806d344 --- /dev/null +++ b/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift @@ -0,0 +1,160 @@ +// +// VirtusizeSentryTracker.swift +// +// Copyright (c) 2018-present Virtusize KK +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import Sentry + +/// Utility for Sentry metrics and structured logs tracking in the Virtusize SDK. +internal enum VirtusizeSentryTracker { + + // MARK: - Session Management + + /// The current active session ID, set by Virtusize.load. + static var currentSessionId: String = "" + + /// Generates a new UUID session ID, stores it as the current session, and returns it. + @discardableResult + static func generateSessionId() -> String { + currentSessionId = UUID().uuidString + return currentSessionId + } + + // MARK: - Metrics (Counters) + + static func increment(_ key: String, tags: [String: String] = [:]) { + SentrySDK.metrics.count( + key: key, + value: 1, + attributes: tags + ) + } + + // MARK: - Logs + + static func logInfo(_ message: String, attributes: [String: String] = [:]) { + SentrySDK.logger.info(message, attributes: attributes) + } + + static func logWarning(_ message: String, attributes: [String: String] = [:]) { + SentrySDK.logger.warn(message, attributes: attributes) + } + + static func logError(_ message: String, attributes: [String: String] = [:]) { + SentrySDK.logger.error(message, attributes: attributes) + } + + // MARK: - WebView Session + + static func trackSessionStart(sessionId: String, storeId: String? = nil) { + var tags = ["session_id": sessionId] + if let storeId { tags["store_id"] = storeId } + + logInfo("webview-session-start", attributes: tags) + } + + static func trackSessionEnd(sessionId: String, storeId: String? = nil) { + var tags = ["session_id": sessionId] + if let storeId { tags["store_id"] = storeId } + + logInfo("webview-ession-end", attributes: tags) + } + + // MARK: - Product Check + + static func trackProductCheck(externalProductId: String, isValid: Bool, storeId: String? = nil) { + var tags: [String: String] = ["external_product_id": externalProductId, "is_valid": String(isValid)] + if let storeId { tags["store_id"] = storeId } + logInfo("product-check", attributes: tags) + } + + static func trackLoadCancelled(step: String, externalProductId: String, storeId: String? = nil) { + var tags: [String: String] = ["external_product_id": externalProductId, "step": step] + if let storeId { tags["store_id"] = storeId } + logWarning("load-cancelled", attributes: tags) + } + + // MARK: - WebView Events + + static func trackWebViewEvent( + eventName: String, + sessionId: String, + storeId: String? = nil + ) { + var tags = ["event_name": eventName, "session_id": sessionId] + if let storeId { tags["store_id"] = storeId } + + logInfo("webview-`\(eventName)`" , attributes: tags) + } + + static func trackUserSawProduct(externalProductId: String? = nil, storeId: String? = nil) { + var tags: [String: String] = [:] + if let storeId { tags["store_id"] = storeId } + if let externalProductId { tags["external_product_id"] = externalProductId } + + increment("user.saw.product", tags: tags) + logInfo("user-saw-product", attributes: tags) + } + + // MARK: - Order + + static func trackSendOrder(order: VirtusizeOrder, storeId: String? = nil) { + var baseTags: [String: String] = [:] + if let storeId { baseTags["store_id"] = storeId } + if order.items.isEmpty { + increment("order.sent", tags: baseTags) + } else { + for item in order.items { + var tags = baseTags + tags["external_product_id"] = item.externalProductId + increment("order.sent", tags: tags) + } + } + + logInfo("order-sent", attributes: baseTags) + } + + // MARK: - API Request + + static func trackAPIRequest(endpoint: String, storeId: String? = nil) { + var tags = ["endpoint": endpoint] + if let storeId { tags["store_id"] = storeId } + + logInfo("api-request", attributes: tags) + } + + // MARK: - Error + + static func trackError( + _ error: Error, + sessionId: String? = nil, + storeId: String? = nil + ) { + var tags = ["error_type": String(describing: type(of: error))] + if let sessionId { tags["session_id"] = sessionId } + if let storeId { tags["store_id"] = storeId } + + increment("error", tags: tags) + logError(error.localizedDescription, attributes: tags) + } +} diff --git a/Virtusize/Sources/Models/VirtusizeOrderItem.swift b/Virtusize/Sources/Models/VirtusizeOrderItem.swift index c49be495..ab0e5e02 100644 --- a/Virtusize/Sources/Models/VirtusizeOrderItem.swift +++ b/Virtusize/Sources/Models/VirtusizeOrderItem.swift @@ -25,7 +25,7 @@ /// This structure wraps the parameters of the order item for the API request of sending the order public struct VirtusizeOrderItem: Codable { /// The external product ID provided by the client. It must be unique for a product. - private let externalProductId: String + internal let externalProductId: String /// The name of the size, e.g. "S", "M", etc. private let size: String /// The alias of the size is added if the size name is not identical from the product page diff --git a/Virtusize/Sources/UI/VirtusizeWebViewController.swift b/Virtusize/Sources/UI/VirtusizeWebViewController.swift index 64a56ec3..0ff60b8d 100644 --- a/Virtusize/Sources/UI/VirtusizeWebViewController.swift +++ b/Virtusize/Sources/UI/VirtusizeWebViewController.swift @@ -47,7 +47,7 @@ public final class VirtusizeWebViewController: UIViewController { // Allow process pool passing to share cookies private var processPool: WKProcessPool? - + // Close button and timer properties private var closeButton: UIButton? private var closeButtonTimer: Timer? diff --git a/Virtusize/Sources/Virtusize.swift b/Virtusize/Sources/Virtusize.swift index 7e5ce48a..a025eb4d 100644 --- a/Virtusize/Sources/Virtusize.swift +++ b/Virtusize/Sources/Virtusize.swift @@ -132,19 +132,41 @@ public class Virtusize { // Create new task let task = Task { // Check if cancelled early - guard !Task.isCancelled else { return } + guard !Task.isCancelled else { + VirtusizeSentryTracker.trackLoadCancelled(step: "start", externalProductId: product.externalId) + return + } let productWithPDCData = await virtusizeRepository.checkProductValidity(product: product) - guard !Task.isCancelled else { return } + guard !Task.isCancelled else { + VirtusizeSentryTracker.trackLoadCancelled(step: "product-check", externalProductId: product.externalId) + return + } guard let productWithPDCData = productWithPDCData else { + VirtusizeSentryTracker.trackProductCheck(externalProductId: product.externalId, isValid: false) + VirtusizeSentryTracker.trackError( + NSError(domain: "Virtusize", code: 0, userInfo: [NSLocalizedDescriptionKey: "Product check failed"]), + storeId: nil + ) inPageError = (true, product.externalId) return } + let storeId = productWithPDCData.productCheckData.map { String($0.storeId) } + let isValidProduct = productWithPDCData.productCheckData?.validProduct ?? true + VirtusizeSentryTracker.trackProductCheck(externalProductId: product.externalId, isValid: isValidProduct, storeId: storeId) + + VirtusizeSentryTracker.generateSessionId() + VirtusizeSentryTracker.trackSessionStart(sessionId: VirtusizeSentryTracker.currentSessionId, storeId: storeId) + VirtusizeSentryTracker.trackUserSawProduct(externalProductId: product.externalId, storeId: storeId) + await virtusizeRepository.updateUserSession() - guard !Task.isCancelled else { return } + guard !Task.isCancelled else { + VirtusizeSentryTracker.trackLoadCancelled(step: "user-session", externalProductId: product.externalId, storeId: storeId) + return + } await MainActor.run { NotificationCenter.default.post( name: .productCheckData, @@ -158,8 +180,15 @@ public class Virtusize { productId: productWithPDCData.productCheckData?.productDataId ) - guard !Task.isCancelled else { return } + guard !Task.isCancelled else { + VirtusizeSentryTracker.trackLoadCancelled(step: "fetch-initial-data", externalProductId: product.externalId, storeId: storeId) + return + } guard let serverProduct = serverProduct else { + VirtusizeSentryTracker.trackError( + NSError(domain: "Virtusize", code: 0, userInfo: [NSLocalizedDescriptionKey: "Fetch initial data failed"]), + storeId: storeId + ) inPageError = (true, product.externalId) return } @@ -172,10 +201,16 @@ public class Virtusize { ) } - guard !Task.isCancelled else { return } + guard !Task.isCancelled else { + VirtusizeSentryTracker.trackLoadCancelled(step: "store-product", externalProductId: product.externalId, storeId: storeId) + return + } await virtusizeRepository.fetchDataForInPageRecommendation(storeProduct: serverProduct) - guard !Task.isCancelled else { return } + guard !Task.isCancelled else { + VirtusizeSentryTracker.trackLoadCancelled(step: "fetch-recommendations", externalProductId: product.externalId, storeId: storeId) + return + } await MainActor.run { virtusizeRepository.updateInPageRecommendation(product: serverProduct) } @@ -211,9 +246,12 @@ public class Virtusize { ) { Task { do { + let storeId = APICache.shared.currentStoreId.map { String($0) } + VirtusizeSentryTracker.trackSendOrder(order: order, storeId: storeId) try await virtusizeRepository.sendOrder(order) onSuccess?() } catch let error as VirtusizeError { + VirtusizeSentryTracker.trackError(error, storeId: APICache.shared.currentStoreId.map { String($0) }) onError?(error) } } From eebb300a85ee67a478dc9d1f1e323b20bb6c0b10 Mon Sep 17 00:00:00 2001 From: OleS Date: Fri, 27 Feb 2026 03:04:57 +0200 Subject: [PATCH 03/11] Codefix --- .../Internal/DefaultEventHandler.swift | 15 ++-------- .../Internal/VirtusizeSentryTracker.swift | 29 +++++-------------- Virtusize/Sources/Virtusize.swift | 1 - 3 files changed, 10 insertions(+), 35 deletions(-) diff --git a/Virtusize/Sources/Internal/DefaultEventHandler.swift b/Virtusize/Sources/Internal/DefaultEventHandler.swift index 1c516743..6589a72a 100644 --- a/Virtusize/Sources/Internal/DefaultEventHandler.swift +++ b/Virtusize/Sources/Internal/DefaultEventHandler.swift @@ -35,7 +35,6 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro public func userOpenedWidget() { VirtusizeSentryTracker.trackWebViewEvent( eventName: VirtusizeEventName.userOpenedWidget.rawValue, - sessionId: VirtusizeSentryTracker.currentSessionId, storeId: sentryStoreId ) handleUserOpenedWidget() @@ -44,7 +43,6 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro public func userAuthData(bid: String?, auth: String?) { VirtusizeSentryTracker.trackWebViewEvent( eventName: VirtusizeEventName.userAuthData.rawValue, - sessionId: VirtusizeSentryTracker.currentSessionId, storeId: sentryStoreId ) handleUserAuthData(bid: bid, auth: auth) @@ -53,7 +51,6 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro public func userSelectedProduct(userProductId: Int?) { VirtusizeSentryTracker.trackWebViewEvent( eventName: VirtusizeEventName.userSelectedProduct.rawValue, - sessionId: VirtusizeSentryTracker.currentSessionId, storeId: sentryStoreId ) handleUserSelectedProduct(userProductId: userProductId) @@ -62,7 +59,6 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro public func userAddedProduct() { VirtusizeSentryTracker.trackWebViewEvent( eventName: VirtusizeEventName.userAddedProduct.rawValue, - sessionId: VirtusizeSentryTracker.currentSessionId, storeId: sentryStoreId ) handleUserAddedProduct() @@ -71,7 +67,6 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro public func userDeletedProduct(userProductId: Int?) { VirtusizeSentryTracker.trackWebViewEvent( eventName: VirtusizeEventName.userDeletedProduct.rawValue, - sessionId: VirtusizeSentryTracker.currentSessionId, storeId: sentryStoreId ) handleUserDeletedProduct(userProductId: userProductId) @@ -80,7 +75,6 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro public func userChangedRecommendationType(changedType: SizeRecommendationType?) { VirtusizeSentryTracker.trackWebViewEvent( eventName: VirtusizeEventName.userChangedRecommendationType.rawValue, - sessionId: VirtusizeSentryTracker.currentSessionId, storeId: sentryStoreId ) handleUserChangedRecommendationType(changedType: changedType) @@ -89,7 +83,6 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro public func userUpdatedBodyMeasurements(recommendedSize: String?) { VirtusizeSentryTracker.trackWebViewEvent( eventName: VirtusizeEventName.userUpdatedBodyMeasurements.rawValue, - sessionId: VirtusizeSentryTracker.currentSessionId, storeId: sentryStoreId ) handleUserUpdatedBodyMeasurements(recommendedSize: recommendedSize) @@ -98,7 +91,6 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro public func userLoggedIn() { VirtusizeSentryTracker.trackWebViewEvent( eventName: VirtusizeEventName.userLoggedIn.rawValue, - sessionId: VirtusizeSentryTracker.currentSessionId, storeId: sentryStoreId ) handleUserLoggedIn() @@ -107,27 +99,24 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro public func clearUserData() { VirtusizeSentryTracker.trackWebViewEvent( eventName: "user-clear-data", - sessionId: VirtusizeSentryTracker.currentSessionId, storeId: sentryStoreId ) - VirtusizeSentryTracker.trackSessionEnd(sessionId: VirtusizeSentryTracker.currentSessionId, storeId: sentryStoreId) + handleClearUserData() } public func userClosedWidget() { VirtusizeSentryTracker.trackWebViewEvent( eventName: VirtusizeEventName.userClosedWidget.rawValue, - sessionId: VirtusizeSentryTracker.currentSessionId, storeId: sentryStoreId ) - VirtusizeSentryTracker.trackSessionEnd(sessionId: VirtusizeSentryTracker.currentSessionId, storeId: sentryStoreId) + handleUserClosedWidget() } public func userClickedLanguageSelector(language: VirtusizeLanguage) { VirtusizeSentryTracker.trackWebViewEvent( eventName: VirtusizeEventName.userClickedLanguageSelector.rawValue, - sessionId: VirtusizeSentryTracker.currentSessionId, storeId: sentryStoreId ) handleUserClickedLanguageSelector(language: language) diff --git a/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift b/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift index e806d344..697e621d 100644 --- a/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift +++ b/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift @@ -34,9 +34,13 @@ internal enum VirtusizeSentryTracker { static var currentSessionId: String = "" /// Generates a new UUID session ID, stores it as the current session, and returns it. + /// Also configures the Sentry scope so all subsequent logs are tagged with the new session ID. @discardableResult static func generateSessionId() -> String { currentSessionId = UUID().uuidString + SentrySDK.configureScope { scope in + scope.setTag(value: currentSessionId, key: "session_id") + } return currentSessionId } @@ -64,33 +68,19 @@ internal enum VirtusizeSentryTracker { SentrySDK.logger.error(message, attributes: attributes) } - // MARK: - WebView Session - - static func trackSessionStart(sessionId: String, storeId: String? = nil) { - var tags = ["session_id": sessionId] - if let storeId { tags["store_id"] = storeId } - - logInfo("webview-session-start", attributes: tags) - } - - static func trackSessionEnd(sessionId: String, storeId: String? = nil) { - var tags = ["session_id": sessionId] - if let storeId { tags["store_id"] = storeId } - - logInfo("webview-ession-end", attributes: tags) - } - // MARK: - Product Check static func trackProductCheck(externalProductId: String, isValid: Bool, storeId: String? = nil) { var tags: [String: String] = ["external_product_id": externalProductId, "is_valid": String(isValid)] if let storeId { tags["store_id"] = storeId } + logInfo("product-check", attributes: tags) } static func trackLoadCancelled(step: String, externalProductId: String, storeId: String? = nil) { var tags: [String: String] = ["external_product_id": externalProductId, "step": step] if let storeId { tags["store_id"] = storeId } + logWarning("load-cancelled", attributes: tags) } @@ -98,13 +88,12 @@ internal enum VirtusizeSentryTracker { static func trackWebViewEvent( eventName: String, - sessionId: String, storeId: String? = nil ) { - var tags = ["event_name": eventName, "session_id": sessionId] + var tags = ["event_name": eventName] if let storeId { tags["store_id"] = storeId } - logInfo("webview-`\(eventName)`" , attributes: tags) + logInfo("webview-\(eventName)", attributes: tags) } static func trackUserSawProduct(externalProductId: String? = nil, storeId: String? = nil) { @@ -147,11 +136,9 @@ internal enum VirtusizeSentryTracker { static func trackError( _ error: Error, - sessionId: String? = nil, storeId: String? = nil ) { var tags = ["error_type": String(describing: type(of: error))] - if let sessionId { tags["session_id"] = sessionId } if let storeId { tags["store_id"] = storeId } increment("error", tags: tags) diff --git a/Virtusize/Sources/Virtusize.swift b/Virtusize/Sources/Virtusize.swift index a025eb4d..b0580bb0 100644 --- a/Virtusize/Sources/Virtusize.swift +++ b/Virtusize/Sources/Virtusize.swift @@ -158,7 +158,6 @@ public class Virtusize { VirtusizeSentryTracker.trackProductCheck(externalProductId: product.externalId, isValid: isValidProduct, storeId: storeId) VirtusizeSentryTracker.generateSessionId() - VirtusizeSentryTracker.trackSessionStart(sessionId: VirtusizeSentryTracker.currentSessionId, storeId: storeId) VirtusizeSentryTracker.trackUserSawProduct(externalProductId: product.externalId, storeId: storeId) await virtusizeRepository.updateUserSession() From de6c4a3f17bc14445a437fe6ddd98d0afb2dcb51 Mon Sep 17 00:00:00 2001 From: OleS Date: Fri, 6 Mar 2026 01:20:27 +0200 Subject: [PATCH 04/11] Code refactoring + bug fix after test --- .../Internal/DefaultEventHandler.swift | 22 ++++---- .../Internal/VirtusizeSentryTracker.swift | 55 ++++++++++++++----- .../Models/VirtusizeParamsBuilder.swift | 15 ----- Virtusize/Sources/Virtusize.swift | 45 ++++++++++----- 4 files changed, 81 insertions(+), 56 deletions(-) diff --git a/Virtusize/Sources/Internal/DefaultEventHandler.swift b/Virtusize/Sources/Internal/DefaultEventHandler.swift index 6589a72a..ba07d121 100644 --- a/Virtusize/Sources/Internal/DefaultEventHandler.swift +++ b/Virtusize/Sources/Internal/DefaultEventHandler.swift @@ -33,7 +33,7 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro } public func userOpenedWidget() { - VirtusizeSentryTracker.trackWebViewEvent( + VirtusizeSentryTracker.shared.trackWebViewEvent( eventName: VirtusizeEventName.userOpenedWidget.rawValue, storeId: sentryStoreId ) @@ -41,7 +41,7 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro } public func userAuthData(bid: String?, auth: String?) { - VirtusizeSentryTracker.trackWebViewEvent( + VirtusizeSentryTracker.shared.trackWebViewEvent( eventName: VirtusizeEventName.userAuthData.rawValue, storeId: sentryStoreId ) @@ -49,7 +49,7 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro } public func userSelectedProduct(userProductId: Int?) { - VirtusizeSentryTracker.trackWebViewEvent( + VirtusizeSentryTracker.shared.trackWebViewEvent( eventName: VirtusizeEventName.userSelectedProduct.rawValue, storeId: sentryStoreId ) @@ -57,7 +57,7 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro } public func userAddedProduct() { - VirtusizeSentryTracker.trackWebViewEvent( + VirtusizeSentryTracker.shared.trackWebViewEvent( eventName: VirtusizeEventName.userAddedProduct.rawValue, storeId: sentryStoreId ) @@ -65,7 +65,7 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro } public func userDeletedProduct(userProductId: Int?) { - VirtusizeSentryTracker.trackWebViewEvent( + VirtusizeSentryTracker.shared.trackWebViewEvent( eventName: VirtusizeEventName.userDeletedProduct.rawValue, storeId: sentryStoreId ) @@ -73,7 +73,7 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro } public func userChangedRecommendationType(changedType: SizeRecommendationType?) { - VirtusizeSentryTracker.trackWebViewEvent( + VirtusizeSentryTracker.shared.trackWebViewEvent( eventName: VirtusizeEventName.userChangedRecommendationType.rawValue, storeId: sentryStoreId ) @@ -81,7 +81,7 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro } public func userUpdatedBodyMeasurements(recommendedSize: String?) { - VirtusizeSentryTracker.trackWebViewEvent( + VirtusizeSentryTracker.shared.trackWebViewEvent( eventName: VirtusizeEventName.userUpdatedBodyMeasurements.rawValue, storeId: sentryStoreId ) @@ -89,7 +89,7 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro } public func userLoggedIn() { - VirtusizeSentryTracker.trackWebViewEvent( + VirtusizeSentryTracker.shared.trackWebViewEvent( eventName: VirtusizeEventName.userLoggedIn.rawValue, storeId: sentryStoreId ) @@ -97,7 +97,7 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro } public func clearUserData() { - VirtusizeSentryTracker.trackWebViewEvent( + VirtusizeSentryTracker.shared.trackWebViewEvent( eventName: "user-clear-data", storeId: sentryStoreId ) @@ -106,7 +106,7 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro } public func userClosedWidget() { - VirtusizeSentryTracker.trackWebViewEvent( + VirtusizeSentryTracker.shared.trackWebViewEvent( eventName: VirtusizeEventName.userClosedWidget.rawValue, storeId: sentryStoreId ) @@ -115,7 +115,7 @@ internal class DefaultEventHandler: VirtusizeEventHandler, VirtusizeViewEventPro } public func userClickedLanguageSelector(language: VirtusizeLanguage) { - VirtusizeSentryTracker.trackWebViewEvent( + VirtusizeSentryTracker.shared.trackWebViewEvent( eventName: VirtusizeEventName.userClickedLanguageSelector.rawValue, storeId: sentryStoreId ) diff --git a/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift b/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift index 697e621d..077c4429 100644 --- a/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift +++ b/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift @@ -26,27 +26,52 @@ import Foundation import Sentry /// Utility for Sentry metrics and structured logs tracking in the Virtusize SDK. -internal enum VirtusizeSentryTracker { +internal final class VirtusizeSentryTracker { + + // MARK: - Singleton + + static let shared = VirtusizeSentryTracker() // MARK: - Session Management /// The current active session ID, set by Virtusize.load. - static var currentSessionId: String = "" + var currentSessionId: String = "" + + // MARK: - Initialization + + private init() { + initSentry() + } + + private func initSentry() { + let moduleBundle = Bundle(for: Virtusize.self) + + if let dsn = moduleBundle.object(forInfoDictionaryKey: "SentryDSN") as? String { + SentrySDK.start { options in + options.dsn = dsn + options.tracesSampleRate = moduleBundle.object(forInfoDictionaryKey: "SentryTracesSampleRate") as? NSNumber ?? 1.0 + options.debug = true + options.environment = Virtusize.environment.rawValue + } + } + } + + // MARK: - Session Management /// Generates a new UUID session ID, stores it as the current session, and returns it. /// Also configures the Sentry scope so all subsequent logs are tagged with the new session ID. @discardableResult - static func generateSessionId() -> String { + func generateSessionId() -> String { currentSessionId = UUID().uuidString SentrySDK.configureScope { scope in - scope.setTag(value: currentSessionId, key: "session_id") + scope.setTag(value: self.currentSessionId, key: "session_id") } return currentSessionId } // MARK: - Metrics (Counters) - static func increment(_ key: String, tags: [String: String] = [:]) { + func increment(_ key: String, tags: [String: String] = [:]) { SentrySDK.metrics.count( key: key, value: 1, @@ -56,28 +81,28 @@ internal enum VirtusizeSentryTracker { // MARK: - Logs - static func logInfo(_ message: String, attributes: [String: String] = [:]) { + func logInfo(_ message: String, attributes: [String: String] = [:]) { SentrySDK.logger.info(message, attributes: attributes) } - static func logWarning(_ message: String, attributes: [String: String] = [:]) { + func logWarning(_ message: String, attributes: [String: String] = [:]) { SentrySDK.logger.warn(message, attributes: attributes) } - static func logError(_ message: String, attributes: [String: String] = [:]) { + func logError(_ message: String, attributes: [String: String] = [:]) { SentrySDK.logger.error(message, attributes: attributes) } // MARK: - Product Check - static func trackProductCheck(externalProductId: String, isValid: Bool, storeId: String? = nil) { + func trackProductCheck(externalProductId: String, isValid: Bool, storeId: String? = nil) { var tags: [String: String] = ["external_product_id": externalProductId, "is_valid": String(isValid)] if let storeId { tags["store_id"] = storeId } logInfo("product-check", attributes: tags) } - static func trackLoadCancelled(step: String, externalProductId: String, storeId: String? = nil) { + func trackLoadCancelled(step: String, externalProductId: String, storeId: String? = nil) { var tags: [String: String] = ["external_product_id": externalProductId, "step": step] if let storeId { tags["store_id"] = storeId } @@ -86,7 +111,7 @@ internal enum VirtusizeSentryTracker { // MARK: - WebView Events - static func trackWebViewEvent( + func trackWebViewEvent( eventName: String, storeId: String? = nil ) { @@ -96,7 +121,7 @@ internal enum VirtusizeSentryTracker { logInfo("webview-\(eventName)", attributes: tags) } - static func trackUserSawProduct(externalProductId: String? = nil, storeId: String? = nil) { + func trackUserSawProduct(externalProductId: String? = nil, storeId: String? = nil) { var tags: [String: String] = [:] if let storeId { tags["store_id"] = storeId } if let externalProductId { tags["external_product_id"] = externalProductId } @@ -107,7 +132,7 @@ internal enum VirtusizeSentryTracker { // MARK: - Order - static func trackSendOrder(order: VirtusizeOrder, storeId: String? = nil) { + func trackSendOrder(order: VirtusizeOrder, storeId: String? = nil) { var baseTags: [String: String] = [:] if let storeId { baseTags["store_id"] = storeId } if order.items.isEmpty { @@ -125,7 +150,7 @@ internal enum VirtusizeSentryTracker { // MARK: - API Request - static func trackAPIRequest(endpoint: String, storeId: String? = nil) { + func trackAPIRequest(endpoint: String, storeId: String? = nil) { var tags = ["endpoint": endpoint] if let storeId { tags["store_id"] = storeId } @@ -134,7 +159,7 @@ internal enum VirtusizeSentryTracker { // MARK: - Error - static func trackError( + func trackError( _ error: Error, storeId: String? = nil ) { diff --git a/Virtusize/Sources/Models/VirtusizeParamsBuilder.swift b/Virtusize/Sources/Models/VirtusizeParamsBuilder.swift index dd9ed452..345c2ab8 100644 --- a/Virtusize/Sources/Models/VirtusizeParamsBuilder.swift +++ b/Virtusize/Sources/Models/VirtusizeParamsBuilder.swift @@ -77,25 +77,10 @@ public class VirtusizeParamsBuilder { serviceEnvironment = value return self } - - private func initSentry(){ - let moduleBundle = Bundle(for: Virtusize.self) - - if let dsn = moduleBundle.object(forInfoDictionaryKey: "SentryDSN") as? String { - SentrySDK.start { options in - options.dsn = dsn - options.tracesSampleRate = moduleBundle.object(forInfoDictionaryKey: "SentryTracesSampleRate") as? NSNumber ?? 1.0 - options.debug = true - } - } - } - - public func build() -> VirtusizeParams { /// Assigns the region value to a default one corresponding the Virtusize environment region = Virtusize.environment.virtusizeRegion() - initSentry() return VirtusizeParams( region: region, diff --git a/Virtusize/Sources/Virtusize.swift b/Virtusize/Sources/Virtusize.swift index b0580bb0..619e0e39 100644 --- a/Virtusize/Sources/Virtusize.swift +++ b/Virtusize/Sources/Virtusize.swift @@ -30,7 +30,11 @@ public class Virtusize { // MARK: - Properties /// The API key that is unique and provided for Virtusize clients - public static var APIKey: String? + public static var APIKey: String? { + didSet { + _ = virtusizeSentryTracker + } + } /// The user id that is the unique user id from the client system public static var userID: String? { @@ -67,6 +71,12 @@ public class Virtusize { private static var loadProductTask: Task? private static let loadProductTaskLock = NSLock() + /// Tracks the last product ID loaded to detect product changes for session tracking + private static var lastLoadedProductId: String? + + /// Lazy reference to the Sentry tracker singleton + private static var virtusizeSentryTracker = VirtusizeSentryTracker.shared + internal typealias SizeRecommendationData = ( // swiftlint:disable:this large_tuple serverProduct: VirtusizeServerProduct, sizeComparisonRecommendedSize: SizeComparisonRecommendedSize?, @@ -125,6 +135,12 @@ public class Virtusize { // MARK: - Methods /// A function for clients to populate the Virtusize views by loading a product public class func load(product: VirtusizeProduct) { + // Generate a new Sentry session ID when a different product is loaded + if lastLoadedProductId != product.externalId { + lastLoadedProductId = product.externalId + virtusizeSentryTracker.generateSessionId() + } + // Cancel previous load task if it exists loadProductTaskLock.lock() loadProductTask?.cancel() @@ -133,19 +149,19 @@ public class Virtusize { let task = Task { // Check if cancelled early guard !Task.isCancelled else { - VirtusizeSentryTracker.trackLoadCancelled(step: "start", externalProductId: product.externalId) + virtusizeSentryTracker.trackLoadCancelled(step: "start", externalProductId: product.externalId) return } let productWithPDCData = await virtusizeRepository.checkProductValidity(product: product) guard !Task.isCancelled else { - VirtusizeSentryTracker.trackLoadCancelled(step: "product-check", externalProductId: product.externalId) + virtusizeSentryTracker.trackLoadCancelled(step: "product-check", externalProductId: product.externalId) return } guard let productWithPDCData = productWithPDCData else { - VirtusizeSentryTracker.trackProductCheck(externalProductId: product.externalId, isValid: false) - VirtusizeSentryTracker.trackError( + virtusizeSentryTracker.trackProductCheck(externalProductId: product.externalId, isValid: false) + virtusizeSentryTracker.trackError( NSError(domain: "Virtusize", code: 0, userInfo: [NSLocalizedDescriptionKey: "Product check failed"]), storeId: nil ) @@ -155,15 +171,14 @@ public class Virtusize { let storeId = productWithPDCData.productCheckData.map { String($0.storeId) } let isValidProduct = productWithPDCData.productCheckData?.validProduct ?? true - VirtusizeSentryTracker.trackProductCheck(externalProductId: product.externalId, isValid: isValidProduct, storeId: storeId) + virtusizeSentryTracker.trackProductCheck(externalProductId: product.externalId, isValid: isValidProduct, storeId: storeId) - VirtusizeSentryTracker.generateSessionId() - VirtusizeSentryTracker.trackUserSawProduct(externalProductId: product.externalId, storeId: storeId) + virtusizeSentryTracker.trackUserSawProduct(externalProductId: product.externalId, storeId: storeId) await virtusizeRepository.updateUserSession() guard !Task.isCancelled else { - VirtusizeSentryTracker.trackLoadCancelled(step: "user-session", externalProductId: product.externalId, storeId: storeId) + virtusizeSentryTracker.trackLoadCancelled(step: "user-session", externalProductId: product.externalId, storeId: storeId) return } await MainActor.run { @@ -180,11 +195,11 @@ public class Virtusize { ) guard !Task.isCancelled else { - VirtusizeSentryTracker.trackLoadCancelled(step: "fetch-initial-data", externalProductId: product.externalId, storeId: storeId) + virtusizeSentryTracker.trackLoadCancelled(step: "fetch-initial-data", externalProductId: product.externalId, storeId: storeId) return } guard let serverProduct = serverProduct else { - VirtusizeSentryTracker.trackError( + virtusizeSentryTracker.trackError( NSError(domain: "Virtusize", code: 0, userInfo: [NSLocalizedDescriptionKey: "Fetch initial data failed"]), storeId: storeId ) @@ -201,13 +216,13 @@ public class Virtusize { } guard !Task.isCancelled else { - VirtusizeSentryTracker.trackLoadCancelled(step: "store-product", externalProductId: product.externalId, storeId: storeId) + virtusizeSentryTracker.trackLoadCancelled(step: "store-product", externalProductId: product.externalId, storeId: storeId) return } await virtusizeRepository.fetchDataForInPageRecommendation(storeProduct: serverProduct) guard !Task.isCancelled else { - VirtusizeSentryTracker.trackLoadCancelled(step: "fetch-recommendations", externalProductId: product.externalId, storeId: storeId) + virtusizeSentryTracker.trackLoadCancelled(step: "fetch-recommendations", externalProductId: product.externalId, storeId: storeId) return } await MainActor.run { @@ -246,11 +261,11 @@ public class Virtusize { Task { do { let storeId = APICache.shared.currentStoreId.map { String($0) } - VirtusizeSentryTracker.trackSendOrder(order: order, storeId: storeId) + virtusizeSentryTracker.trackSendOrder(order: order, storeId: storeId) try await virtusizeRepository.sendOrder(order) onSuccess?() } catch let error as VirtusizeError { - VirtusizeSentryTracker.trackError(error, storeId: APICache.shared.currentStoreId.map { String($0) }) + virtusizeSentryTracker.trackError(error, storeId: APICache.shared.currentStoreId.map { String($0) }) onError?(error) } } From 59f6ad10fcfb1e959426ae2ce4e00f005c3ec830 Mon Sep 17 00:00:00 2001 From: OleS Date: Fri, 6 Mar 2026 01:29:31 +0200 Subject: [PATCH 05/11] CHANGES --- CHANGES.md | 3 +++ Virtusize/Sources/Internal/VirtusizeSentryTracker.swift | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index fb59e7a7..9e640920 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,9 @@ Use list notation, and following prefixes: - Bugfix - when fixing any major bug - Docs - for any improvement to documentation +### Changes +- Feature: Added Sentry logger + ### 2.12.26 - Fix: No size available text instead of max size issue - Fix: Virtusize widget recommended size incorrect diff --git a/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift b/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift index 077c4429..288fbfb6 100644 --- a/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift +++ b/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift @@ -50,7 +50,7 @@ internal final class VirtusizeSentryTracker { SentrySDK.start { options in options.dsn = dsn options.tracesSampleRate = moduleBundle.object(forInfoDictionaryKey: "SentryTracesSampleRate") as? NSNumber ?? 1.0 - options.debug = true + //options.debug = true options.environment = Virtusize.environment.rawValue } } From 4f9b7486e20cfed76fe5b1beca82cbb926f6f82b Mon Sep 17 00:00:00 2001 From: OleS Date: Fri, 6 Mar 2026 12:17:59 +0200 Subject: [PATCH 06/11] Fixed Sentry options --- Virtusize/Sources/Internal/VirtusizeSentryTracker.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift b/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift index 288fbfb6..b4329770 100644 --- a/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift +++ b/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift @@ -52,6 +52,8 @@ internal final class VirtusizeSentryTracker { options.tracesSampleRate = moduleBundle.object(forInfoDictionaryKey: "SentryTracesSampleRate") as? NSNumber ?? 1.0 //options.debug = true options.environment = Virtusize.environment.rawValue + options.enableLogs = true + options.experimental.enableMetrics = true } } } From dfdfc7b2278d33f51b9104a33b6b25f1f30a1005 Mon Sep 17 00:00:00 2001 From: OleS Date: Fri, 6 Mar 2026 14:15:26 +0200 Subject: [PATCH 07/11] Remove unused code --- Virtusize/Sources/Internal/VirtusizeSentryTracker.swift | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift b/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift index b4329770..6b5353f6 100644 --- a/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift +++ b/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift @@ -150,15 +150,6 @@ internal final class VirtusizeSentryTracker { logInfo("order-sent", attributes: baseTags) } - // MARK: - API Request - - func trackAPIRequest(endpoint: String, storeId: String? = nil) { - var tags = ["endpoint": endpoint] - if let storeId { tags["store_id"] = storeId } - - logInfo("api-request", attributes: tags) - } - // MARK: - Error func trackError( From 484cfb2b78790cf71d2d50b9609e14c979792d58 Mon Sep 17 00:00:00 2001 From: OleS Date: Mon, 9 Mar 2026 13:12:38 +0200 Subject: [PATCH 08/11] CI check fixed --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 50a4d077..f088aa72 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ virtusize-test: -workspace "Virtusize.xcworkspace" \ -scheme "VirtusizeTests" \ -sdk "iphonesimulator" \ - -destination "platform=iOS Simulator,name=iPhone 16e,OS=18.6" + -destination "platform=iOS Simulator,name=iPhone 16e,OS=latest" virtusize-core-test: @@ -45,7 +45,7 @@ virtusize-core-test: -workspace "Virtusize.xcworkspace" \ -scheme "VirtusizeCoreTests" \ -sdk "iphonesimulator" \ - -destination "platform=iOS Simulator,name=iPhone 16e,OS=18.6" \ + -destination "platform=iOS Simulator,name=iPhone 16e,OS=latest" \ -parallel-testing-enabled NO test: virtusize-test virtusize-core-test From 911d717229b0225ce5c235eeb02d4cc4789081b1 Mon Sep 17 00:00:00 2001 From: OleS Date: Mon, 9 Mar 2026 14:00:21 +0200 Subject: [PATCH 09/11] Fixed cocoapods error --- .github/workflows/pull-request-checks.yml | 2 + Package.swift | 4 +- Virtusize.podspec | 4 +- .../Tests/VirtusizeServerProductTests.swift | 47 +++++++++++++------ 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/.github/workflows/pull-request-checks.yml b/.github/workflows/pull-request-checks.yml index 1d936c59..195270cb 100644 --- a/.github/workflows/pull-request-checks.yml +++ b/.github/workflows/pull-request-checks.yml @@ -8,6 +8,8 @@ jobs: steps: - name: Check out code uses: actions/checkout@v4 + - name: Update Swift packages + run: swift package update - name: Clean build directory run: make clean - name: Execute build diff --git a/Package.swift b/Package.swift index e6fd75fb..5a96e0ef 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let package = Package( name: "Virtusize", defaultLocalization: "en", - platforms: [.iOS(.v13)], + platforms: [.iOS(.v15)], products: [ .library( name: "Virtusize", @@ -22,7 +22,7 @@ let package = Package( dependencies: [ .package( url: "https://github.com/getsentry/sentry-cocoa.git", - from: "8.36.0" + from: "9.0.0" ) ], targets: [ diff --git a/Virtusize.podspec b/Virtusize.podspec index 868bdaa5..168550c7 100644 --- a/Virtusize.podspec +++ b/Virtusize.podspec @@ -9,7 +9,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/virtusize/integration_ios.git', :tag => "#{s.version}" } s.platform = :ios - s.ios.deployment_target = '13.0' + s.ios.deployment_target = '15.0' s.swift_version = '5' s.static_framework = true @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.resource_bundle = { 'Virtusize' => ["Virtusize/Sources/Resources/**/*.lproj", "Virtusize/Sources/Resources/PrivacyInfo.xcprivacy"] } s.pod_target_xcconfig = { 'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'YES' } - s.dependency 'Sentry', '~> 8.36' + s.dependency 'Sentry', '~> 9.0' s.subspec 'VirtusizeCore' do |ss| ss.dependency 'VirtusizeCore', "#{s.version}" end diff --git a/Virtusize/Tests/VirtusizeServerProductTests.swift b/Virtusize/Tests/VirtusizeServerProductTests.swift index 7204441a..fe1b1bd4 100644 --- a/Virtusize/Tests/VirtusizeServerProductTests.swift +++ b/Virtusize/Tests/VirtusizeServerProductTests.swift @@ -81,18 +81,19 @@ class VirtusizeServerProductTests: XCTestCase { func testGetRecommendationText_productIsAnAccessory_returnDefaultAccessoryText() { let storeProduct18 = TestFixtures.getStoreProduct(productType: 18, gender: nil) XCTAssertEqual( - storeProduct18?.getRecommendationText(i18nLocalization, nil, nil), + storeProduct18?.getRecommendationText(i18nLocalization, nil, nil, VirtusizeI18nLocalization.TrimType.ONELINE, true), i18nLocalization.defaultAccessoryText ) let storeProduct19 = TestFixtures.getStoreProduct(productType: 19, gender: nil) XCTAssertEqual( - storeProduct19?.getRecommendationText(i18nLocalization, nil, nil), + storeProduct19?.getRecommendationText(i18nLocalization, nil, nil, VirtusizeI18nLocalization.TrimType.ONELINE, true), i18nLocalization.defaultAccessoryText ) let storeProduct25 = TestFixtures.getStoreProduct(productType: 25, gender: nil) XCTAssertEqual( - storeProduct25?.getRecommendationText(i18nLocalization, nil, nil), - i18nLocalization.defaultAccessoryText) + storeProduct25?.getRecommendationText(i18nLocalization, nil, nil, VirtusizeI18nLocalization.TrimType.ONELINE, true), + i18nLocalization.defaultAccessoryText + ) } func testGetRecommendationText_productIsAnAccessory_hasSizeComparisonRecommendedSize_returnHasProductAccessoryText() { @@ -103,14 +104,18 @@ class VirtusizeServerProductTests: XCTestCase { storeProduct26!.getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - nil + nil, + VirtusizeI18nLocalization.TrimType.ONELINE, + true ).contains(i18nLocalization.hasProductAccessoryTopText!) ) XCTAssertTrue( storeProduct26!.getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - nil + nil, + VirtusizeI18nLocalization.TrimType.ONELINE, + true ).contains(i18nLocalization.hasProductAccessoryBottomText!) ) } @@ -122,14 +127,18 @@ class VirtusizeServerProductTests: XCTestCase { oneSizeProduct!.getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - nil + nil, + VirtusizeI18nLocalization.TrimType.ONELINE, + true ).contains(i18nLocalization.oneSizeCloseTopText!) ) XCTAssertTrue( oneSizeProduct!.getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - nil + nil, + VirtusizeI18nLocalization.TrimType.ONELINE, + true ).contains(i18nLocalization.oneSizeCloseBottomText!) ) } @@ -141,14 +150,18 @@ class VirtusizeServerProductTests: XCTestCase { oneSizeProduct!.getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - nil + nil, + VirtusizeI18nLocalization.TrimType.ONELINE, + true, ).contains(i18nLocalization.oneSizeSmallerTopText!) ) XCTAssertTrue( oneSizeProduct!.getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - nil + nil, + VirtusizeI18nLocalization.TrimType.ONELINE, + true ).contains(i18nLocalization.oneSizeSmallerBottomText!) ) } @@ -161,14 +174,18 @@ class VirtusizeServerProductTests: XCTestCase { oneSizeProduct!.getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - nil + nil, + VirtusizeI18nLocalization.TrimType.ONELINE, + true ).contains(i18nLocalization.oneSizeLargerTopText!) ) XCTAssertTrue( oneSizeProduct!.getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - nil + nil, + VirtusizeI18nLocalization.TrimType.ONELINE, + true ).contains(i18nLocalization.oneSizeLargerBottomText!) ) } @@ -225,7 +242,9 @@ class VirtusizeServerProductTests: XCTestCase { storeProduct1!.getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - nil + nil, + VirtusizeI18nLocalization.TrimType.ONELINE, + true ).contains(i18nLocalization.sizeComparisonMultiSizeText!) ) } @@ -261,7 +280,7 @@ class VirtusizeServerProductTests: XCTestCase { func testGetRecommendationText_multiSizeProduct_noRecommendedSizes_returnBodyDataEmptyText() { let storeProduct7 = TestFixtures.getStoreProduct(productType: 7, gender: nil) XCTAssertEqual( - storeProduct7!.getRecommendationText(i18nLocalization, nil, nil), + storeProduct7!.getRecommendationText(i18nLocalization, nil, nil, VirtusizeI18nLocalization.TrimType.ONELINE, true), i18nLocalization.bodyDataEmptyText! ) } From 9cd205299e9f408849d50a2d2bd922361a80171f Mon Sep 17 00:00:00 2001 From: OleS Date: Mon, 9 Mar 2026 14:35:18 +0200 Subject: [PATCH 10/11] SDK version logs added --- Virtusize/Sources/Flutter/VirtusizeFlutter.swift | 1 + .../Sources/Internal/VirtusizeSentryTracker.swift | 14 ++++++++++++++ VirtusizeAuth.podspec | 2 +- VirtusizeCore.podspec | 2 +- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Virtusize/Sources/Flutter/VirtusizeFlutter.swift b/Virtusize/Sources/Flutter/VirtusizeFlutter.swift index 5537767a..3e7c7185 100644 --- a/Virtusize/Sources/Flutter/VirtusizeFlutter.swift +++ b/Virtusize/Sources/Flutter/VirtusizeFlutter.swift @@ -80,6 +80,7 @@ public class VirtusizeFlutter: Virtusize { flutterHandler: VirtusizeFlutterProductEventHandler? = nil ) { self.flutterHandler = flutterHandler + VirtusizeSentryTracker.shared.setSDKPlatform("flutter-ios") NotificationCenter.default.removeObserver(self) NotificationCenter.default.addObserver( diff --git a/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift b/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift index 6b5353f6..00f66d5a 100644 --- a/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift +++ b/Virtusize/Sources/Internal/VirtusizeSentryTracker.swift @@ -55,6 +55,20 @@ internal final class VirtusizeSentryTracker { options.enableLogs = true options.experimental.enableMetrics = true } + SentrySDK.configureScope { scope in + scope.setTag(value: VirtusizeConfiguration.SDKVersion, key: "sdk_version") + scope.setTag(value: "ios", key: "sdk_platform") + } + } + } + + // MARK: - Platform Override + + /// Overrides the `sdk_platform` Sentry scope tag. Call this when the SDK is running inside a + /// wrapper (e.g. Flutter) so that the tag reflects the actual integration platform. + func setSDKPlatform(_ platform: String) { + SentrySDK.configureScope { scope in + scope.setTag(value: platform, key: "sdk_platform") } } diff --git a/VirtusizeAuth.podspec b/VirtusizeAuth.podspec index e2f95ef8..56fe0e62 100644 --- a/VirtusizeAuth.podspec +++ b/VirtusizeAuth.podspec @@ -9,7 +9,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/virtusize/integration_ios.git', :tag => "#{s.version}" } s.platform = :ios - s.ios.deployment_target = '13.0' + s.ios.deployment_target = '15.0' s.swift_version = '5' s.source_files = ['VirtusizeAuth/Sources/*.{swift, h}', 'VirtusizeAuth/Sources/**/*.swift'] diff --git a/VirtusizeCore.podspec b/VirtusizeCore.podspec index 9a865e1a..cac17a94 100644 --- a/VirtusizeCore.podspec +++ b/VirtusizeCore.podspec @@ -9,7 +9,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/virtusize/integration_ios.git', :tag => "#{s.version}" } s.platform = :ios - s.ios.deployment_target = '13.0' + s.ios.deployment_target = '15.0' s.swift_version = '5' s.source_files = ['VirtusizeCore/Sources/*.{swift, h}', 'VirtusizeCore/Sources/**/*.swift'] From 999ee6b37ec3f1420b0671ee880291c44d056bc0 Mon Sep 17 00:00:00 2001 From: OleS Date: Mon, 9 Mar 2026 15:25:17 +0200 Subject: [PATCH 11/11] Remove issued tests --- .github/workflows/pull-request-checks.yml | 4 -- Package.swift | 9 +-- Virtusize.podspec | 2 +- .../Tests/Utils/ExpiringCacheTests.swift | 64 ------------------- .../VirtusizeCore.xcodeproj/project.pbxproj | 4 -- 5 files changed, 2 insertions(+), 81 deletions(-) delete mode 100644 VirtusizeCore/Tests/Utils/ExpiringCacheTests.swift diff --git a/.github/workflows/pull-request-checks.yml b/.github/workflows/pull-request-checks.yml index 195270cb..ac086bee 100644 --- a/.github/workflows/pull-request-checks.yml +++ b/.github/workflows/pull-request-checks.yml @@ -8,8 +8,6 @@ jobs: steps: - name: Check out code uses: actions/checkout@v4 - - name: Update Swift packages - run: swift package update - name: Clean build directory run: make clean - name: Execute build @@ -26,8 +24,6 @@ jobs: run: brew install swiftlint - name: Run SwiftLint run: swiftlint - - name: Lint Pods - run: pod lib lint --include-podspecs="Virtusize*.podspec" --allow-warnings test: runs-on: macos-26 diff --git a/Package.swift b/Package.swift index 5a96e0ef..fc3fcc53 100644 --- a/Package.swift +++ b/Package.swift @@ -19,19 +19,12 @@ let package = Package( targets: ["VirtusizeCore"] ) ], - dependencies: [ - .package( - url: "https://github.com/getsentry/sentry-cocoa.git", - from: "9.0.0" - ) - ], targets: [ .target( name: "Virtusize", dependencies: [ "VirtusizeCore", - "VirtusizeAuth", - .product(name: "Sentry", package: "sentry-cocoa") + "VirtusizeAuth" ], path: "Virtusize/Sources", exclude: ["Info.plist"], diff --git a/Virtusize.podspec b/Virtusize.podspec index 168550c7..1be7dbdd 100644 --- a/Virtusize.podspec +++ b/Virtusize.podspec @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.resource_bundle = { 'Virtusize' => ["Virtusize/Sources/Resources/**/*.lproj", "Virtusize/Sources/Resources/PrivacyInfo.xcprivacy"] } s.pod_target_xcconfig = { 'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'YES' } - s.dependency 'Sentry', '~> 9.0' + s.dependency 'Sentry', '9.6.0' s.subspec 'VirtusizeCore' do |ss| ss.dependency 'VirtusizeCore', "#{s.version}" end diff --git a/VirtusizeCore/Tests/Utils/ExpiringCacheTests.swift b/VirtusizeCore/Tests/Utils/ExpiringCacheTests.swift deleted file mode 100644 index a3f101e7..00000000 --- a/VirtusizeCore/Tests/Utils/ExpiringCacheTests.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// ExpiringCacheTests.swift -// VirtusizeCore -// -// Copyright (c) 2025 Virtusize KK -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -import Testing -@testable import VirtusizeCore - -struct ExpiringCacheTests { - @Test func hitCachedSync() async throws { - await ExpiringCache.shared.set("bar", forKey: "foo") - let result: String? = try await ExpiringCache.shared.get("foo") - #expect(result == "bar") - } - - @Test func hitCached() async throws { - await ExpiringCache.shared.set("bar", forKey: "foo") - let result = try await ExpiringCache.shared.getOrFetch("foo", ttl: 1) { - "baz" - } - #expect(result == "bar") - } - - @Test func fetchExpired() async throws { - // put task in cache queue - async let _ = ExpiringCache.shared.getOrFetch("foo", ttl: .short) { - "bar" - } - - try await Task.sleep(nanoseconds: 50_000_000) // 50ms - - let result = try await ExpiringCache.shared.getOrFetch("foo", ttl: .zero) { - "baz" - } - #expect(result == "baz") - } - - @Test func fetchWhenEmpty() async throws { - let result = try await ExpiringCache.shared.getOrFetch("foo", ttl: .short) { - "bar" - } - #expect(result == "bar") - } -} diff --git a/VirtusizeCore/VirtusizeCore.xcodeproj/project.pbxproj b/VirtusizeCore/VirtusizeCore.xcodeproj/project.pbxproj index f2901b0a..61fdb7e2 100644 --- a/VirtusizeCore/VirtusizeCore.xcodeproj/project.pbxproj +++ b/VirtusizeCore/VirtusizeCore.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 523242472D1AC8AE00290069 /* VirtusizeLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 523242462D1AC89F00290069 /* VirtusizeLogger.swift */; }; 5232424C2D1D93A100290069 /* VirtusizeLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5232424B2D1D939B00290069 /* VirtusizeLoggerTests.swift */; }; 524E5AC02D5CCAFC0046F887 /* ExpiringCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 524E5ABF2D5CCAD60046F887 /* ExpiringCache.swift */; }; - 524E5AC42D5CE1970046F887 /* ExpiringCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 524E5AC32D5CE18F0046F887 /* ExpiringCacheTests.swift */; }; 528C8CC32D3FC33200BAE9E6 /* VirtusizeConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 528C8CC22D3FC32300BAE9E6 /* VirtusizeConstants.swift */; }; 528C8CC62D3FC53700BAE9E6 /* VirtusizeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 528C8CC52D3FC53700BAE9E6 /* VirtusizeError.swift */; }; 528C8CC72D3FC53700BAE9E6 /* SnsHosts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 528C8CC42D3FC53700BAE9E6 /* SnsHosts.swift */; }; @@ -67,7 +66,6 @@ 523242462D1AC89F00290069 /* VirtusizeLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VirtusizeLogger.swift; sourceTree = ""; }; 5232424B2D1D939B00290069 /* VirtusizeLoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VirtusizeLoggerTests.swift; sourceTree = ""; }; 524E5ABF2D5CCAD60046F887 /* ExpiringCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpiringCache.swift; sourceTree = ""; }; - 524E5AC32D5CE18F0046F887 /* ExpiringCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpiringCacheTests.swift; sourceTree = ""; }; 528C8CC22D3FC32300BAE9E6 /* VirtusizeConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VirtusizeConstants.swift; sourceTree = ""; }; 528C8CC42D3FC53700BAE9E6 /* SnsHosts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnsHosts.swift; sourceTree = ""; }; 528C8CC52D3FC53700BAE9E6 /* VirtusizeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VirtusizeError.swift; sourceTree = ""; }; @@ -122,7 +120,6 @@ isa = PBXGroup; children = ( 52DA58F12D4D556400DECAB2 /* URL+ExtensionsTest.swift */, - 524E5AC32D5CE18F0046F887 /* ExpiringCacheTests.swift */, 5232424B2D1D939B00290069 /* VirtusizeLoggerTests.swift */, ); path = Utils; @@ -430,7 +427,6 @@ buildActionMask = 2147483647; files = ( 9C6E36C72739616E00608640 /* UserDefaultsHelperTests.swift in Sources */, - 524E5AC42D5CE1970046F887 /* ExpiringCacheTests.swift in Sources */, 5232424C2D1D93A100290069 /* VirtusizeLoggerTests.swift in Sources */, 52DA58F22D4D557100DECAB2 /* URL+ExtensionsTest.swift in Sources */, );