Conversation
The checkout API rejects `context.identity.email` unless it is a valid email address or null. Apps that set a placeholder like `"none"` when the user has no email silently break the Stripe checkout flow because the server returns a validation error and no checkout session is created. Introduce an `Email` domain primitive with a failable initializer that validates against the same regex the API enforces. When merging user attributes, the SDK now parses the `email` value through `Email` and drops it (sends null) when invalid, with a warning log. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Configures storage with apiKey inside DependencyContainer.init rather than via a separate Superwall.makeDependencyContainer factory. Prevents code paths from reading storage.apiKey between init and configure. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fix redeem auth race during startup
Adds an optional `onCustomCallback` parameter to `getPaywall(...)` so paywalls embedded by the developer can handle custom webview callbacks. Previously this was only wired up through `register()`, which presents in its own UIWindow. The handler is stored on `PaywallViewControllerDelegateAdapter` and registered with `CustomCallbackRegistry` from `PaywallViewController` on init / when the delegate is reassigned, and unregistered on deinit. Bumps version to 4.15.1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Only unregister the custom callback handler when this PVC was the one that registered it. Prevents a getPaywall-flow VC from clearing a register-flow handler that happens to share the same paywall identifier. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire custom callbacks through getPaywall
Generalises SuperwallOptions.localResources from `[String: URL]` to `[String: AssetResource]`. URL conforms to AssetResource so existing call sites are unaffected. New `CatalogAsset(name:bundle:)` registers a Data Set entry from an .xcassets, resolved at load time via NSDataAsset — the iOS equivalent of Android's R.raw.* resource IDs. ObjC keeps a URL-only shim under the same `localResources` name. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`UTType.preferredMIMEType` is iOS 14+. On iOS 13, fall back to `UTTypeCopyPreferredTagWithClass` from MobileCoreServices so catalog assets are served with a real MIME type (an `<img>` or `<video>` is refused by WKWebView when the MIME is `application/octet-stream`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- SWLocalResourcesViewController: when a catalog asset exists but isn't image-decodable, show its UTI and byte count instead of a blank cell. - AssetResource: remove the unused UIKit import. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Satisfies the `trailing_closure` SwiftLint rule — the only remaining project-level lint violation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- LocalFileSchemeHandler: remove unreferenced UIKit import. - SuperwallOptions: ObjC setter now replaces only the URL subset of localResources, so CatalogAsset entries registered from Swift survive a subsequent ObjC assignment in mixed Swift/ObjC codebases. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
localResources is documented as set once before configure(), so the "mixed Swift/ObjC post-configure mutation" scenario the merge was guarding against isn't part of the intended usage pattern. The setter goes back to replacing the full dict. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NSDataAsset only resolves Data Sets, so a typical Image Set logo would fail with "File not found". Try NSDataAsset first (lossless, any file type), then fall back to UIImage(named:in:compatibleWith:)?.pngData() so existing Image Sets work without restructuring the asset catalog. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Calling NSDataAsset on an Image Set triggers a CoreUI log about a wrong-typed lookup. Flip the order so UIImage(named:) runs first — Image Sets resolve cleanly, and NSDataAsset is only reached when there's no Image Set to mistype. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lets customers register an in-memory image directly: "logo": UIImage(named: "Logo")! Served to the webview as image/png via pngData(). Complements CatalogAsset for cases where eager decoding is acceptable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Debug view was loading catalog assets via NSDataAsset only, so an Image Set CatalogAsset previewed as "Asset not found" even though the scheme handler resolved it successfully. Try UIImage(named:) first, fall through to NSDataAsset for Data Sets. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Avoids re-encoding on every swlocal:// request, which runs on the main thread. Also updates the ObjC bridge doc to reflect that UIImage is accepted there too. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…alog Accept asset catalog entries in localResources
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d-duration Expose abandoned transaction product params in filters
ICU treats $ as matching before a final \n, so user@example.com\n was slipping through. Switch to \z and add a regression case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…attribute Sanitize email user attribute before sending to checkout API
Removes SanitizeAttributeTests since the helper is no longer reachable; EmailTests still covers the regex including the trailing-newline case. Renames test funcs from backticked display names to plain identifiers since newer Swift Testing rejects having both. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Avoids leaking PII from caller-supplied strings into the SDK log stream. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops the period guard so consumables and other non-subscription products surface price, locale, and currency in audience filters, matching PaywallInfo.placementParams behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
4.15.1
Enhancements
onCustomCallbackparameter togetPaywall.SuperwallOptions.localResourcesnow accepts UIImage's from xcasset files, e.g.UIImage(named: "my-image").Fixes
Greptile Summary
This PR bumps the SDK to 4.15.1 with four changes: adds an
onCustomCallbackparameter togetPaywall(wired through aCustomCallbackRegistryandPaywallViewControllerDelegateAdapter); extendsSuperwallOptions.localResourcesto acceptUIImagevalues from asset catalogs via the newAssetResourceprotocol; exposes abandoned-transaction product attributes in audience filters; and sanitises the"email"user attribute against a regex-validatedEmailtype before sending it to the server.Confidence Score: 5/5
Safe to merge — no P0 or P1 issues found; all four feature areas are well-tested and correctly implemented.
The PR is clean with no new critical or warning-level defects. The case-sensitive email key match (
switch keyvsswitch key.lowercased()) was already flagged in a prior review cycle and remains; all other logic is sound. The custom callback registration lifecycle (init → didSet → deinit) is correct,localResourcesis properly excluded fromEncodable, and theNSCache<UIImage, NSData>usage is thread-safe.Sources/SuperwallKit/Identity/UserAttributes.swift — case-sensitive email key match noted in previous review remains unresolved.
Important Files Changed
Emailvalue type with regex-based validation; uses\zanchor correctly to reject trailing newlines.AssetResourceprotocol;URLandUIImageconformances are correctly guarded with#if canImport(UIKit).localResourcesto[String: AssetResource]with a proper ObjC bridge;localResourcesis excluded fromEncodablevia a customCodingKeysenum.UIImage-to-PNG path with a staticNSCache<UIImage, NSData>for repeated-request efficiency; thread-safe sinceNSCacheis inherently thread-safe.customCallbackRegistrydependency andsyncCustomCallbackRegistration()called from init (where delegate is already set) anddelegate.didSet; cleanup indeinitis correct.camelCaseToSnakeCase();"identifier"key correctly excluded to avoid collision withabandoned_product_id.storage.configure(apiKey:)intoDependencyContainer.initso it runs synchronously; theStorageTeststest validates this timing.onCustomCallbackparameter to both the callback-based and asyncgetPaywalloverloads; defaultnilpreserves backwards compatibility.Sequence Diagram
sequenceDiagram participant App participant Superwall participant PaywallViewController participant CustomCallbackRegistry participant WebView App->>Superwall: getPaywall(forPlacement:, delegate:, onCustomCallback:) Superwall->>Superwall: internallyGetPaywall(delegate: adapter with onCustomCallback) Superwall->>PaywallViewController: init(customCallbackRegistry:) PaywallViewController->>PaywallViewController: syncCustomCallbackRegistration() PaywallViewController->>CustomCallbackRegistry: register(paywallIdentifier:, handler:) WebView-->>CustomCallbackRegistry: custom callback triggered CustomCallbackRegistry-->>App: invoke onCustomCallback closure App-->>WebView: CustomCallbackResult (.success / .failure) PaywallViewController->>PaywallViewController: deinit PaywallViewController->>CustomCallbackRegistry: unregister(paywallIdentifier:)Comments Outside Diff (2)
Sources/SuperwallKit/Identity/UserAttributes.swift, line 376-391 (link)The
switch keycomparison is case-sensitive, so attribute keys like"Email"or"EMAIL"will bypass sanitization entirely and be forwarded to the server as-is. This is inconsistent with documented API examples that show lowercase"email"and could silently let invalid emails through for callers using different casing conventions (e.g. Objective-C or legacy integrations).Prompt To Fix With AI
Sources/SuperwallKit/Identity/UserAttributes.swift, line 378-383 (link)The warning log interpolates the caller-supplied
stringValuedirectly into the message. For a genuine (but malformed) email like"user@example"or"john.doe@company", this emits the user's actual email address to the SDK's debug log stream. Depending on the host app's logging pipeline (crash reporters, analytics SDKs), this could constitute an unintended PII leak. Consider redacting or truncating the value, e.g. printing only its length or a masked form.Prompt To Fix With AI
Reviews (3): Last reviewed commit: "Always emit abandoned product attributes" | Re-trigger Greptile