diff --git a/CHANGELOG.md b/CHANGELOG.md index b0405cea..027c77cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 3.0.3 - August 6, 2024 + +- Fixes internal instrumentation + ## 3.0.2 - July 24, 2024 - Sets `allowsInlineMediaPlayback` to true on the Webview to prevent the iOS camera opening as a live broadcast. diff --git a/Samples/MobileBuyIntegration/MobileBuyIntegration/CartViewController.swift b/Samples/MobileBuyIntegration/MobileBuyIntegration/CartViewController.swift index ed6d27a9..0c93ce59 100644 --- a/Samples/MobileBuyIntegration/MobileBuyIntegration/CartViewController.swift +++ b/Samples/MobileBuyIntegration/MobileBuyIntegration/CartViewController.swift @@ -212,10 +212,12 @@ extension CartViewController: CheckoutDelegate { func checkoutDidEmitWebPixelEvent(event: ShopifyCheckoutSheetKit.PixelEvent) { switch event { case .customEvent(let customEvent): + print("[PIXEL - Custom]", customEvent.name!) if let genericEvent = mapToGenericEvent(customEvent: customEvent) { recordAnalyticsEvent(genericEvent) } case .standardEvent(let standardEvent): + print("[PIXEL - Standard]", standardEvent.name!) recordAnalyticsEvent(mapToGenericEvent(standardEvent: standardEvent)) } } diff --git a/ShopifyCheckoutSheetKit.podspec b/ShopifyCheckoutSheetKit.podspec index 83b08a19..40ffa211 100644 --- a/ShopifyCheckoutSheetKit.podspec +++ b/ShopifyCheckoutSheetKit.podspec @@ -1,5 +1,5 @@ Pod::Spec.new do |s| - s.version = "3.0.2" + s.version = "3.0.3" s.name = "ShopifyCheckoutSheetKit" s.summary = "Enables Swift apps to embed the Shopify's highest converting, customizable, one-page checkout." diff --git a/Sources/ShopifyCheckoutSheetKit/CheckoutBridge.swift b/Sources/ShopifyCheckoutSheetKit/CheckoutBridge.swift index f170e4a9..0286db85 100644 --- a/Sources/ShopifyCheckoutSheetKit/CheckoutBridge.swift +++ b/Sources/ShopifyCheckoutSheetKit/CheckoutBridge.swift @@ -28,7 +28,12 @@ enum BridgeError: Swift.Error { case unencodableInstrumentation(Swift.Error? = nil) } -enum CheckoutBridge { +protocol CheckoutBridgeProtocol { + static func instrument(_ webView: WKWebView, _ instrumentation: InstrumentationPayload) + static func sendMessage(_ webView: WKWebView, messageName: String, messageBody: String?) +} + +enum CheckoutBridge: CheckoutBridgeProtocol { static let schemaVersion = "8.1" static let messageHandler = "mobileCheckoutSdk" internal static let userAgent = "ShopifyCheckoutSDK/\(ShopifyCheckoutSheetKit.version)" @@ -85,7 +90,7 @@ enum CheckoutBridge { } } - static func dispatchMessageTemplate(body: String) -> String { + static internal func dispatchMessageTemplate(body: String) -> String { return """ if (window.MobileCheckoutSdk && window.MobileCheckoutSdk.dispatchMessage) { window.MobileCheckoutSdk.dispatchMessage(\(body)); diff --git a/Sources/ShopifyCheckoutSheetKit/CheckoutWebView.swift b/Sources/ShopifyCheckoutSheetKit/CheckoutWebView.swift index 139d1c9f..58804d09 100644 --- a/Sources/ShopifyCheckoutSheetKit/CheckoutWebView.swift +++ b/Sources/ShopifyCheckoutSheetKit/CheckoutWebView.swift @@ -39,9 +39,12 @@ private let checkoutLiquidNotSupportedReason = "checkout_liquid_not_supported" class CheckoutWebView: WKWebView { private static var cache: CacheEntry? + internal var timer: Date? static var preloadingActivatedByClient: Bool = false + var checkoutBridge: CheckoutBridgeProtocol.Type = CheckoutBridge.self + /// A reference to the view is needed when preload is deactivated in order to detatch the bridge static weak var uncacheableViewRef: CheckoutWebView? @@ -56,7 +59,7 @@ class CheckoutWebView: WKWebView { } var isPreloadingAvailable: Bool { - return !isRecovery + return !isRecovery && ShopifyCheckoutSheetKit.configuration.preloading.enabled } static func `for`(checkout url: URL, recovery: Bool = false) -> CheckoutWebView { @@ -111,6 +114,7 @@ class CheckoutWebView: WKWebView { dispatchPresentedMessage(checkoutDidLoad, checkoutDidPresent) } } + var isPreloadRequest: Bool = false // MARK: Initializers init(frame: CGRect = .zero, configuration: WKWebViewConfiguration = WKWebViewConfiguration(), recovery: Bool = false) { @@ -196,12 +200,17 @@ class CheckoutWebView: WKWebView { } } + internal func instrument(_ payload: InstrumentationPayload) { + checkoutBridge.instrument(self, payload) + } + // MARK: - func load(checkout url: URL, isPreload: Bool = false) { var request = URLRequest(url: url) if isPreload && isPreloadingAvailable { + isPreloadRequest = true request.setValue("prefetch", forHTTPHeaderField: "Sec-Purpose") } @@ -259,8 +268,6 @@ extension CheckoutWebView: WKScriptMessageHandler { } } -private var timer: Date? - extension CheckoutWebView: WKNavigationDelegate { func webView(_ webView: WKWebView, decidePolicyFor action: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { @@ -354,18 +361,18 @@ extension CheckoutWebView: WKNavigationDelegate { if let startTime = timer { let endTime = Date() let diff = endTime.timeIntervalSince(startTime) - let preloading = String(ShopifyCheckoutSheetKit.Configuration().preloading.enabled) let message = "Loaded checkout in \(String(format: "%.2f", diff))s" + let preload = String(isPreloadRequest) ShopifyCheckoutSheetKit.configuration.logger.log(message) if isBridgeAttached { - CheckoutBridge.instrument(self, + self.instrument( InstrumentationPayload( name: "checkout_finished_loading", value: Int(diff * 1000), type: .histogram, - tags: ["preloading": preloading])) + tags: ["preloading": preload])) } } checkoutDidLoad = true diff --git a/Sources/ShopifyCheckoutSheetKit/ShopifyCheckoutSheetKit.swift b/Sources/ShopifyCheckoutSheetKit/ShopifyCheckoutSheetKit.swift index 86655cba..df4f3bc9 100644 --- a/Sources/ShopifyCheckoutSheetKit/ShopifyCheckoutSheetKit.swift +++ b/Sources/ShopifyCheckoutSheetKit/ShopifyCheckoutSheetKit.swift @@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SO import UIKit /// The version of the `ShopifyCheckoutSheetKit` library. -public let version = "3.0.2" +public let version = "3.0.3" internal var invalidateOnConfigurationChange = true diff --git a/Tests/ShopifyCheckoutSheetKitTests/CheckoutWebViewTests.swift b/Tests/ShopifyCheckoutSheetKitTests/CheckoutWebViewTests.swift index c3fffacf..9f690e5d 100644 --- a/Tests/ShopifyCheckoutSheetKitTests/CheckoutWebViewTests.swift +++ b/Tests/ShopifyCheckoutSheetKitTests/CheckoutWebViewTests.swift @@ -33,9 +33,11 @@ class CheckoutWebViewTests: XCTestCase { private var url = URL(string: "http://shopify1.shopify.com/checkouts/cn/123")! override func setUp() { + ShopifyCheckoutSheetKit.configuration.preloading.enabled = true view = CheckoutWebView.for(checkout: url) mockDelegate = MockCheckoutWebViewDelegate() view.viewDelegate = mockDelegate + view.checkoutBridge = MockCheckoutBridge.self } private func createRecoveryAgent() -> CheckoutWebView { @@ -354,6 +356,57 @@ class CheckoutWebViewTests: XCTestCase { let secPurposeHeader = webView.lastLoadedURLRequest?.value(forHTTPHeaderField: "Sec-Purpose") XCTAssertEqual(secPurposeHeader, nil) + XCTAssertFalse(webView.isPreloadRequest) + } + + func testInstrumentRequestWithPreloadingTag() { + let webView = LoadedRequestObservableWebView() + + webView.load( + checkout: URL(string: "https://checkout-sdk.myshopify.io")!, + isPreload: true + ) + + webView.timer = Date() + webView.webView(webView, didFinish: nil) + + XCTAssertTrue(webView.isPreloadRequest) + XCTAssertEqual(webView.lastInstrumentationPayload?.name, "checkout_finished_loading") + XCTAssertEqual(webView.lastInstrumentationPayload?.type, .histogram) + XCTAssertEqual(webView.lastInstrumentationPayload?.tags, ["preloading": "true"]) + } + + func testDoesNotInstrumentRequestWithPreloadingTag() { + let webView = LoadedRequestObservableWebView() + + webView.load( + checkout: URL(string: "https://checkout-sdk.myshopify.io")!, + isPreload: false + ) + + webView.timer = Date() + webView.webView(webView, didFinish: nil) + + XCTAssertFalse(webView.isPreloadRequest) + XCTAssertEqual(webView.lastInstrumentationPayload?.name, "checkout_finished_loading") + XCTAssertEqual(webView.lastInstrumentationPayload?.type, .histogram) + XCTAssertEqual(webView.lastInstrumentationPayload?.tags, ["preloading": "false"]) + } + + func testDoesNotInstrumentPreloadingTagIfDisabled() { + let webView = LoadedRequestObservableWebView() + ShopifyCheckoutSheetKit.configuration.preloading.enabled = false + + webView.load( + checkout: URL(string: "https://checkout-sdk.myshopify.io")!, + /// This is not respected if preloading is disabled at a config level + isPreload: true + ) + + webView.timer = Date() + webView.webView(webView, didFinish: nil) + + XCTAssertEqual(webView.lastInstrumentationPayload?.tags, ["preloading": "false"]) } func testDetachBridgeCalledOnInit() { @@ -369,10 +422,28 @@ class CheckoutWebViewTests: XCTestCase { class LoadedRequestObservableWebView: CheckoutWebView { var lastLoadedURLRequest: URLRequest? + var lastInstrumentationPayload: InstrumentationPayload? override func load(_ request: URLRequest) -> WKNavigation? { self.lastLoadedURLRequest = request return nil } + + override internal func instrument(_ payload: InstrumentationPayload) { + self.lastInstrumentationPayload = payload + } +} + +class MockCheckoutBridge: CheckoutBridgeProtocol { + static var instrumentCalled = false + static var sendMessageCalled = false + + static func instrument(_ webView: WKWebView, _ instrumentation: InstrumentationPayload) { + instrumentCalled = true + } + + static func sendMessage(_ webView: WKWebView, messageName: String, messageBody: String?) { + sendMessageCalled = true + } } // swiftlint:enable type_body_length