Skip to content

Commit

Permalink
Correctly tag preload requests (#205)
Browse files Browse the repository at this point in the history
* Correctly tag preload requests

* Add tests

* Patch version + changelong entry
  • Loading branch information
markmur authored Aug 6, 2024
1 parent 4dfe8a8 commit 38d444d
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 10 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
Expand Down
2 changes: 1 addition & 1 deletion ShopifyCheckoutSheetKit.podspec
Original file line number Diff line number Diff line change
@@ -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."
Expand Down
9 changes: 7 additions & 2 deletions Sources/ShopifyCheckoutSheetKit/CheckoutBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down Expand Up @@ -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));
Expand Down
19 changes: 13 additions & 6 deletions Sources/ShopifyCheckoutSheetKit/CheckoutWebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand All @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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")
}

Expand Down Expand Up @@ -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) {

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
71 changes: 71 additions & 0 deletions Tests/ShopifyCheckoutSheetKitTests/CheckoutWebViewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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() {
Expand All @@ -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

0 comments on commit 38d444d

Please sign in to comment.