From a5621f8bf93f4dc923951807ea59f9f83d686c92 Mon Sep 17 00:00:00 2001 From: Leonardo Bilck Date: Sun, 19 Jul 2026 23:00:47 -0300 Subject: [PATCH] Fix iOS crash: sanitize forwarded event params before JSON serialization serializeEventInfo copies the native SuperwallEventInfo.params verbatim into the payload sendToUnity feeds to JSONSerialization.data(withJSONObject:). When params contain a value that is not a JSON-native type (a Swift enum/struct/URL bridged as __SwiftValue), JSONSerialization raises an Objective-C NSInvalidArgumentException ("Invalid type in JSON write (__SwiftValue)"). That is an NSException, not a Swift Error, so the `try?` in toJsonString cannot catch it and the app hard-crashes. Reproduces reliably on iOS at launch: after Superwall.Identify the SDK runs getEnrichment and tracks an event whose params carry such a value; the delegate forwards it via handleSuperwallEvent -> sendToUnity and the app terminates. Route toJsonString through a recursive sanitizer that coerces any non-JSON leaf to its String(describing:) form, and guard with isValidJSONObject so residual edge cases (e.g. non-finite Doubles) drop the message instead of crashing. This protects the entire native->Unity path, not just event forwarding. --- Plugins/iOS/SuperwallUnityBridge.swift | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Plugins/iOS/SuperwallUnityBridge.swift b/Plugins/iOS/SuperwallUnityBridge.swift index 20cb861..03b892a 100644 --- a/Plugins/iOS/SuperwallUnityBridge.swift +++ b/Plugins/iOS/SuperwallUnityBridge.swift @@ -17,8 +17,32 @@ private func toCString(_ str: String?) -> UnsafePointer? { return UnsafePointer(strdup(str)) } +// Coerce any value JSONSerialization would reject (Swift enums/structs, URL, +// __SwiftValue, etc.) into a JSON-legal form. JSONSerialization raises an +// Objective-C NSException on invalid input, which `try?` cannot catch, so the +// input must be made valid BEFORE serialization. Forwarded event params (e.g. +// enrichment) can carry such non-JSON leaves and otherwise crash the app. +private func sanitizeForJson(_ value: Any) -> Any { + switch value { + case let dict as [String: Any]: + var out = [String: Any](minimumCapacity: dict.count) + for (key, element) in dict { + out[key] = sanitizeForJson(element) + } + return out + case let array as [Any]: + return array.map { sanitizeForJson($0) } + case is NSNull, is NSNumber, is String: + return value + default: + return String(describing: value) + } +} + private func toJsonString(_ obj: Any) -> String? { - guard let data = try? JSONSerialization.data(withJSONObject: obj), + let sanitized = sanitizeForJson(obj) + guard JSONSerialization.isValidJSONObject(sanitized), + let data = try? JSONSerialization.data(withJSONObject: sanitized), let str = String(data: data, encoding: .utf8) else { return nil } return str }