diff --git a/CHANGELOG.md b/CHANGELOG.md index ffc0bc6..9637fbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog All notable changes to this package will be documented in this file. +## [Unreleased] + +### Fixes +* `SuperwallEventInfo.EventType` was never populated: iOS serialized `String(describing: event)`, which on SuperwallKit versions without a `CustomStringConvertible` conformance dumps the case together with its associated values (`transactionComplete(transaction: ..., product: ...)`), and Android sends the snake_case `rawName` (`transaction_complete`). Neither matched the `EventType` enum, so every delegate event arrived as the default `FirstSeen` and any `switch` on it was dead. iOS now emits only the case name, and the C# parser accepts both camelCase and snake_case, logging a warning instead of failing silently when a name is still unknown. `EventType` gains the members both native SDKs already emit but C# lacked (`PaywallPageView`, `TestModeModalOpen`/`Close`, `CelExpressionResult`, `ErrorThrown`, `ReviewGranted`/`Denied`, `AttributionMatch`, `StripeCheckoutStart`/`Submit`/`Complete`/`Fail`), and the two placement names whose member is spelled differently (`deepLink_open` → `DeepLink`, `paywallWebviewLoad_processTerminated` → `PaywallWebviewProcessTerminated`) are mapped explicitly. + ## [0.2.6] ### Dependencies diff --git a/Plugins/iOS/SuperwallUnityBridge.swift b/Plugins/iOS/SuperwallUnityBridge.swift index 03b892a..4ccb8ae 100644 --- a/Plugins/iOS/SuperwallUnityBridge.swift +++ b/Plugins/iOS/SuperwallUnityBridge.swift @@ -292,8 +292,18 @@ private func serializeEventInfo(_ info: SuperwallEventInfo) -> [String: Any] { var dict: [String: Any] = [ "params": info.params ] - // Use the string representation of the event - dict["eventType"] = String(describing: info.event) + // `String(describing:)` is not a stable name for this enum. Recent SuperwallKit releases make + // `SuperwallEvent` CustomStringConvertible and describe it as its snake_case placement name + // ("transaction_complete"), but older 4.x releases fall back to Swift's default description, + // which for a case with associated values is the whole payload: + // + // transactionComplete(transaction: Optional(SuperwallKit.StoreTransaction), product: ..., ...) + // + // That never matched the C# `EventType` enum, so every delegate event arrived typed as the + // default (`FirstSeen`). Keep only the leading identifier: both shapes then parse on the C# side, + // which also normalises Android's snake_case `rawName`. + let described = String(describing: info.event) + dict["eventType"] = String(described.prefix(while: { $0 != "(" })) return dict } diff --git a/Runtime/Internal/BridgeCallbackHandler.cs b/Runtime/Internal/BridgeCallbackHandler.cs index 33d1a0c..ed30db4 100644 --- a/Runtime/Internal/BridgeCallbackHandler.cs +++ b/Runtime/Internal/BridgeCallbackHandler.cs @@ -254,6 +254,29 @@ private static long GetLong(Dictionary data, string key, long de return null; } + /// + /// The two native bridges do not agree on the shape of an event name: iOS sends the Swift case + /// name (camelCase) or, on newer SuperwallKit, the snake_case placement name; Android sends + /// SuperwallEvent.rawName, which is snake_case (transaction_complete, + /// freeTrial_start). Stripping underscores makes all of them match the PascalCase + /// members under a case-insensitive parse. Two placement names differ from + /// the member that was chosen for them and are mapped explicitly. + /// + internal static EventType? ParseEventType(string value) + { + if (string.IsNullOrEmpty(value)) return null; + + var parsed = ParseEnum(value) ?? ParseEnum(value.Replace("_", string.Empty)); + if (parsed.HasValue) return parsed; + + switch (value.Replace("_", string.Empty).ToLowerInvariant()) + { + case "deeplinkopen": return EventType.DeepLink; + case "paywallwebviewloadprocessterminated": return EventType.PaywallWebviewProcessTerminated; + default: return null; + } + } + private static SubscriptionStatus DeserializeSubscriptionStatus(Dictionary data) { if (data == null) return SubscriptionStatus.CreateUnknown(); @@ -540,10 +563,11 @@ private static SuperwallEventInfo DeserializeSuperwallEventInfo(Dictionary(eventTypeStr); + var parsed = ParseEventType(eventTypeStr); if (parsed.HasValue) info.EventType = parsed.Value; + else + Debug.LogWarning($"[Superwall] Unrecognised eventType '{eventTypeStr}'; leaving EventType at its default. Handlers switching on it will not run for this event."); } var paramsDict = GetDict(data, "params"); diff --git a/Runtime/Models/Enums.cs b/Runtime/Models/Enums.cs index 0d6a5d6..3d165a8 100644 --- a/Runtime/Models/Enums.cs +++ b/Runtime/Models/Enums.cs @@ -277,7 +277,22 @@ public enum EventType ReviewRequested, PermissionRequested, PermissionGranted, - PermissionDenied + PermissionDenied, + // Names both native SDKs emit (SuperwallKit 4.16 / superwall-android 2.7) that had no member here, + // so they were reported with a warning and left at the default. Appended, never reordered: the + // enum is serialized by name, not by value, but consumers may still persist the ordinal. + PaywallPageView, + TestModeModalOpen, + TestModeModalClose, + CelExpressionResult, + ErrorThrown, + ReviewGranted, + ReviewDenied, + AttributionMatch, + StripeCheckoutStart, + StripeCheckoutSubmit, + StripeCheckoutComplete, + StripeCheckoutFail } [Serializable]