Three Swift dispatcher cases answer only when their argument casts, and have no else:
case "openURL":
if let urlString = body["url"] as? String, let url = URL(string: urlString) {
UIApplication.shared.open(url)
resolveCallback(callbackId, result: true)
}
case "setBadge":
if let count = body["count"] as? Int {
setBadgeCount(count, callbackId: callbackId)
}
case "setFlashlight":
if let enabled = body["enabled"] as? Bool {
setFlashlight(enabled: enabled, callbackId: callbackId)
}All three page methods post a callbackId and return a promise. So craft.openURL(undefined), craft.openURL('not a url'), craft.setBadge('3') and craft.setFlashlight('on') never settle on the Swift path.
Where it bites
Zig serves all three first (bridge_mobile_system.zig, bridge_mobile_display.zig, bridge_mobile_misc.zig), so this path runs:
- in apps built without the Zig runtime;
- in a Zig build when the payload cannot be serialized (
CraftZigRuntime.offerreturns false for an invalid JSON object and Swift answers instead).
Fix
Reject with INVALID_ARGUMENT in each else, using the same code share uses for nothing to share.
ios_conformance_test.zig could also catch the class. Among the checks added for #207 is one that a case the page waits on can resolve. The next step is a check that no if let inside such a case ends without an else that answers. The scan is harder to make exact, so start with a recorded list of the three cases above that may only shrink.
Found while fixing #207.