Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v3.4.0 #174

Merged
merged 54 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
62a378f
Initial structs/enums for tracking source of no rule match
yusuftor Aug 22, 2023
b35402e
Initial refactor to add own purchase controller
yusuftor Aug 29, 2023
02bbc6f
More purchase controller refactoring
yusuftor Aug 30, 2023
04c9caa
Updated GitHub PR template and changelog
yusuftor Aug 30, 2023
c936603
Update StoreKitManager.swift
yusuftor Aug 30, 2023
0449121
Updated some wording
yusuftor Aug 31, 2023
7144a9b
Moved all restore logic into internal purchase controller
yusuftor Aug 31, 2023
6ff90dd
Fix to restore flow from Superwall class
yusuftor Aug 31, 2023
244aca0
Name change and spelling fix
yusuftor Aug 31, 2023
8119196
Merge pull request #166 from superwall-me/feature/add-internal-purcha…
yusuftor Aug 31, 2023
bc1cd8d
Merge branch 'develop' into feature/add-context-to-no-rule-match
yusuftor Aug 31, 2023
9b329ec
Propagated the unmatched rules and their source to TriggerFire
yusuftor Aug 31, 2023
335c438
Fixed tests
yusuftor Aug 31, 2023
83fc8a7
Merge pull request #167 from superwall-me/feature/add-context-to-no-r…
yusuftor Aug 31, 2023
6efd3f5
Adds sdk_version/sdk_version_padded/app_build_number to device
yusuftor Aug 31, 2023
0044419
Add `app_build_string_number` to device and swiftlint fix
yusuftor Sep 1, 2023
b4f8f37
Update CHANGELOG.md
yusuftor Sep 1, 2023
922689a
Merge pull request #168 from superwall-me/feature/expose-sdk-version
yusuftor Sep 1, 2023
9c6a579
Moved the tracking of paywall_decline until after a potential survey …
yusuftor Sep 1, 2023
e6b27ec
Update CHANGELOG.md
yusuftor Sep 1, 2023
06a93a9
Merge pull request #169 from superwall-me/feature/fix-paywall-decline…
yusuftor Sep 1, 2023
ccdcb21
Adds TouchesBegan event and swizzles on UIWindow
yusuftor Sep 1, 2023
2079a51
Added semaphore but need to consider blocking main thread
yusuftor Sep 1, 2023
74f490f
Only swizzles sendEvent if config contains the touchesBegan trigger
yusuftor Sep 4, 2023
8fbdec2
Update CHANGELOG.md
yusuftor Sep 4, 2023
234adf4
Bugfix for checking for began touch events
yusuftor Sep 5, 2023
1f2b2b7
Merge pull request #170 from superwall-me/feature/touches-began-trigger
yusuftor Sep 5, 2023
c143e02
Adds close button option to survey
yusuftor Sep 6, 2023
b093d6d
Fix tests, update changelog
yusuftor Sep 6, 2023
4983bf0
Changed duration of free trial notification to be minutes instead of …
yusuftor Sep 6, 2023
f69040e
Adds surveyShowCondition to display a survey on purchase
yusuftor Sep 7, 2023
e95797f
Update PULL_REQUEST_TEMPLATE.md
yusuftor Sep 7, 2023
aae80d7
Fixes issue where the retrieved `StoreTransaction` associated with th…
yusuftor Sep 7, 2023
e15a04c
Move storing of transaction above updating of purchase completion block
yusuftor Sep 7, 2023
f675be8
Updated to support multiple surveys attached to a paywall
yusuftor Sep 12, 2023
0df5858
Fixed tests and swiftlint
yusuftor Sep 12, 2023
282a65a
Merge pull request #172 from superwall-me/feature/survey-on-transacti…
yusuftor Sep 12, 2023
d77fa76
Merge branch 'develop' into feature/survey-close
yusuftor Sep 12, 2023
2e62964
Fixed tests
yusuftor Sep 12, 2023
285aea7
Merge pull request #171 from superwall-me/feature/survey-close
yusuftor Sep 12, 2023
656a126
Updated survey presentation schema and moved appSessionManager init
yusuftor Sep 13, 2023
c256bfb
Tracks a presentationRequest for implicit triggers when there's no in…
yusuftor Sep 13, 2023
9342dac
Merge branch 'develop' into feature/fallback-to-sk1-product
yusuftor Sep 14, 2023
2032b98
Adds in a fallback and a retry when getting transactions
yusuftor Sep 14, 2023
afd6b0a
Bugfix for surveys - it was sometimes accidentally disabling swipe to…
yusuftor Sep 15, 2023
51b20f6
Required passing of transaction to completePurchase
yusuftor Sep 15, 2023
9cf4d2e
Merge pull request #173 from superwall-me/feature/fallback-to-sk1-pro…
yusuftor Sep 15, 2023
7654893
Added unit tests for sdkVersionPadded and the clearing of assignments
yusuftor Sep 15, 2023
4918b39
Update CHANGELOG.md
yusuftor Sep 15, 2023
011a9fc
Added unit tests for scheduling notifications
yusuftor Sep 15, 2023
6fdb864
Added check for transaction purchase date when verifying
yusuftor Sep 18, 2023
3b99fcf
Documentation updates
yusuftor Sep 18, 2023
972f275
Moved Transaction extension to its own file
yusuftor Sep 18, 2023
1c32301
Reduce code duplication for transaction date comparison and adds tests
yusuftor Sep 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
## Changes in this pull request

Issue fixed: #
-

### Checklist

- [ ] All unit and UI tests pass. Demo project builds and runs.
- [ ] I added tests, an experiment, or detailed why my change isn't tested.
- [ ] All unit tests pass.
- [ ] All UI tests pass.
- [ ] Demo project builds and runs.
- [ ] I added/updated tests or detailed why my change isn't tested.
- [ ] I added an entry to the `CHANGELOG.md` for any breaking changes, enhancements, or bug fixes.
- [ ] I have run `swiftlint` in the main directory and fixed any issues.
- [ ] I have updated the SDK documentation as well as the online docs.
Expand Down
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

The changelog for `SuperwallKit`. Also see the [releases](https://github.com/superwall-me/Superwall-iOS/releases) on GitHub.

## 3.4.0

### Enhancements

- Adds `sdk_version`, `sdk_version_padded`, `app_build_string`, and `app_build_string_number` to the device object for use in rules. `sdk_version` is the version of the sdk, e.g. `3.4.0`. `sdk_version_padded` is the sdk version padded with zeros for use with string comparison. For example `003.004.000`. `app_build_string` is the build of your app and `app_build_string_number` is the build of your app casted as an Int.
- When you experience `no_rule_match`, the `TriggerFire` event params will specify which part of the rules didn't match in the format `"unmatched_rule_<id>": "<outcome>"`. Where `outcome` will either be `OCCURRENCE`, referring to the limit applied to a rule, or `EXPRESSION`. The `id` is the experiment id.
- Adds a `touches_began` implicit trigger. By adding the `touches_began` event to a campaign, you can show a paywall the first time a user touches anywhere in your app.
- Adds the ability to include a close button on a survey.
- If running in sandbox, the duration of a free trial notification added to a paywall will be converted from days to minutes for testing purposes.
- Adds the ability to show a survey after purchasing a product.

### Fixes

- Fixes issue where a survey attached to a paywall wouldn't show if you were also using the `paywall_decline` trigger.
- Fixes issue where verification was happening after the finishing of transactions when not using a `PurchaseController`.
- Fixes issue where the retrieved `StoreTransaction` associated with the purchased product may be `nil`.
- Fixes issue where a `presentationRequest` wasn't being tracked for implicit triggers like `session_start` when there was no internet.

## 3.3.2

### Fixes
Expand All @@ -27,7 +45,7 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/sup
### Enhancements

- Adds the ability to add a paywall exit survey. Surveys are configured via the dashboard and added to paywalls. When added to a paywall, it will attempt to display when the user taps the close button. If the paywall has the `modalPresentationStyle` of `pageSheet`, `formSheet`, or `popover`, the survey will also attempt to display when the user tries to drag to dismiss the paywall. The probability of the survey showing is determined by the survey's configuration in the dashboard. A user will only ever see the survey once unless you reset responses via the dashboard. The survey will always show on exit of the paywall in the debugger.
- Adds the ability to add `survey_close` as a trigger and use the selected option title in rules.
- Adds the ability to add `survey_response` as a trigger and use the selected option title in rules.
- Adds new `PaywallCloseReason` `.manualClose`.

### Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,28 @@ enum InternalSuperwallEvent {
struct AppInstall: TrackableSuperwallEvent {
let superwallEvent: SuperwallEvent = .appInstall
let appInstalledAtString: String
let hasPurchaseController: Bool
let hasExternalPurchaseController: Bool
var customParameters: [String: Any] = [:]
func getSuperwallParameters() async -> [String: Any] {
return [
"application_installed_at": appInstalledAtString,
"using_purchase_controller": hasPurchaseController
"using_purchase_controller": hasExternalPurchaseController
]
}
}

struct TouchesBegan: TrackableSuperwallEvent {
let superwallEvent: SuperwallEvent = .touchesBegan
var customParameters: [String: Any] = [:]
func getSuperwallParameters() async -> [String: Any] { [:] }
}

struct SurveyClose: TrackableSuperwallEvent {
let superwallEvent: SuperwallEvent = .surveyClose
var customParameters: [String: Any] = [:]
func getSuperwallParameters() async -> [String: Any] { [:] }
}

struct SurveyResponse: TrackableSuperwallEvent {
var superwallEvent: SuperwallEvent {
return .surveyResponse(
Expand Down Expand Up @@ -233,11 +245,11 @@ enum InternalSuperwallEvent {
}

struct TriggerFire: TrackableSuperwallEvent {
let triggerResult: TriggerResult
let triggerResult: InternalTriggerResult
var superwallEvent: SuperwallEvent {
return .triggerFire(
eventName: triggerName,
result: triggerResult
result: triggerResult.toPublicType()
)
}
let triggerName: String
Expand All @@ -254,10 +266,14 @@ enum InternalSuperwallEvent {
}

switch triggerResult {
case .noRuleMatch:
return params + [
case .noRuleMatch(let unmatchedRules):
params += [
"result": "no_rule_match"
]
for unmatchedRule in unmatchedRules {
params["unmatched_rule_\(unmatchedRule.experimentId)"] = unmatchedRule.source.rawValue
}
return params
case .holdout(let experiment):
return params + [
"variant_id": experiment.variant.id as Any,
Expand Down Expand Up @@ -348,7 +364,7 @@ enum InternalSuperwallEvent {

func getSuperwallParameters() async -> [String: Any] {
var params: [String: Any] = [
"survey_attached": paywallInfo.survey == nil ? false : true
"survey_attached": paywallInfo.surveys.isEmpty ? false : true
]

if surveyPresentationResult != .noShow {
Expand Down
63 changes: 21 additions & 42 deletions Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//

import Foundation
import StoreKit
import Combine

extension Superwall {
/// Tracks an analytical event by sending it to the server and, for internal Superwall events, the delegate.
Expand Down Expand Up @@ -75,66 +75,45 @@ extension Superwall {
forEvent event: Trackable,
withData eventData: EventData
) async {
let presentationInfo: PresentationInfo = .implicitTrigger(eventData)

var request = dependencyContainer.makePresentationRequest(
presentationInfo,
isPaywallPresented: isPaywallPresented,
type: .presentation
)

do {
try await dependencyContainer.configManager.configState
.compactMap { $0.getConfig() }
.throwableAsync()
try await waitForSubsStatusAndConfig(request, paywallStatePublisher: nil)
} catch {
return
return logErrors(from: request, error)
}

let presentationInfo: PresentationInfo = .implicitTrigger(eventData)

let outcome = TrackingLogic.canTriggerPaywall(
event,
triggers: Set(dependencyContainer.configManager.triggersByEventName.keys),
paywallViewController: paywallViewController
)

var statePublisher = PassthroughSubject<PaywallState, Never>()

switch outcome {
case .deepLinkTrigger:
if isPaywallPresented {
await dismiss()
}
let presentationRequest = dependencyContainer.makePresentationRequest(
presentationInfo,
isPaywallPresented: isPaywallPresented,
type: .presentation
)
_ = try? await internallyPresent(presentationRequest).throwableAsync()
await dismiss()
case .closePaywallThenTriggerPaywall:
guard let lastPresentationItems = presentationItems.last else {
return
}
if isPaywallPresented {
await dismissForNextPaywall()
}
let presentationRequest = dependencyContainer.makePresentationRequest(
presentationInfo,
isPaywallPresented: isPaywallPresented,
type: .presentation
)
_ = try? await internallyPresent(
presentationRequest,
lastPresentationItems.statePublisher
).throwableAsync()
await dismissForNextPaywall()
statePublisher = lastPresentationItems.statePublisher
case .triggerPaywall:
let presentationRequest = dependencyContainer.makePresentationRequest(
presentationInfo,
isPaywallPresented: isPaywallPresented,
type: .presentation
)
_ = try? await internallyPresent(presentationRequest).throwableAsync()
case .disallowedEventAsTrigger:
Logger.debug(
logLevel: .warn,
scope: .superwallCore,
message: "Event Used as Trigger",
info: ["message": "You can't use events as triggers"],
error: nil
)
break
case .dontTriggerPaywall:
return
}

request.flags.isPaywallPresented = isPaywallPresented

internallyPresent(request, statePublisher)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ enum TrackingLogic {
enum ImplicitTriggerOutcome {
case triggerPaywall
case deepLinkTrigger
case disallowedEventAsTrigger
case dontTriggerPaywall
case closePaywallThenTriggerPaywall
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ public enum SuperwallEvent {
reason: PaywallPresentationRequestStatusReason?
)

/// When the first touch was detected on the UIWindow of the app.
///
/// This is only registered if there's an active `touches_began` trigger on your dashboard.
case touchesBegan

/// When the user chose the close button on a survey instead of responding.
case surveyClose

var canImplicitlyTriggerPaywall: Bool {
switch self {
case .appInstall,
Expand All @@ -147,7 +155,8 @@ public enum SuperwallEvent {
.transactionFail,
.paywallDecline,
.transactionAbandon,
.surveyResponse:
.surveyResponse,
.touchesBegan:
return true
default:
return false
Expand Down Expand Up @@ -247,6 +256,10 @@ extension SuperwallEvent {
return .init(objcEvent: .paywallPresentationRequest)
case .surveyResponse:
return .init(objcEvent: .surveyResponse)
case .touchesBegan:
return .init(objcEvent: .touchesBegan)
case .surveyClose:
return .init(objcEvent: .surveyClose)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,14 @@ public enum SuperwallEventObjc: Int, CaseIterable {
/// When the response to a paywall survey as been recorded.
case surveyResponse

/// When the user touches the app's UIWindow for the first time.
///
/// This is only tracked if there is an active `touches_began` trigger in a campaign.
case touchesBegan

/// When the user taps the close button to skip the survey without recording a response.
case surveyClose

public init(event: SuperwallEvent) {
self = event.backingData.objcEvent
}
Expand Down Expand Up @@ -201,6 +209,10 @@ public enum SuperwallEventObjc: Int, CaseIterable {
return "paywallPresentationRequest"
case .surveyResponse:
return "survey_response"
case .touchesBegan:
return "touches_began"
case .surveyClose:
return "survey_close"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ actor TriggerSessionManager {
for presentationInfo: PresentationInfo,
on presentingViewController: UIViewController? = nil,
paywall: Paywall? = nil,
triggerResult: TriggerResult?,
triggerResult: InternalTriggerResult?,
trackEvent: (Trackable) async -> TrackingResult = Superwall.shared.track
) async {
guard let eventName = presentationInfo.eventName else {
Expand All @@ -152,7 +152,7 @@ actor TriggerSessionManager {
presentationInfo: presentationInfo,
presentingViewController: presentingViewController,
paywall: paywall,
triggerResult: triggerResult
triggerResult: triggerResult?.toPublicType()
) else {
return
}
Expand Down
9 changes: 9 additions & 0 deletions Sources/SuperwallKit/Config/ConfigManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class ConfigManager {

triggersByEventName = ConfigLogic.getTriggersByEventName(from: config.triggers)
choosePaywallVariants(from: config.triggers)
await checkForTouchesBeganTrigger(in: config.triggers)
configState.send(.retrieved(config))

Task { await preloadPaywalls() }
Expand All @@ -101,6 +102,14 @@ class ConfigManager {
Task { await preloadPaywalls() }
}

/// Swizzles the UIWindow's `sendEvent` to intercept the first `began` touch event if
/// config's triggers contain `touches_began`.
private func checkForTouchesBeganTrigger(in triggers: Set<Trigger>) async {
if triggers.contains(where: { $0.eventName == SuperwallEvent.touchesBegan.description }) {
await UIWindow.swizzleSendEvent()
}
}

// MARK: - Assignments

private func choosePaywallVariants(from triggers: Set<Trigger>) {
Expand Down
17 changes: 15 additions & 2 deletions Sources/SuperwallKit/Config/Models/Survey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Foundation

/// A survey attached to a paywall.
@objc(SWKSurvey)
@objcMembers
final public class Survey: NSObject, Decodable {
Expand All @@ -27,13 +28,19 @@ final public class Survey: NSObject, Decodable {
/// The options to display in the alert controller.
public let options: [SurveyOption]

/// An enum whose cases indicate when the survey should show.
public internal(set) var presentationCondition: SurveyShowCondition

/// The probability that the survey will present to the user.
public internal(set) var presentationProbability: Double

/// Whether the "Other" option should appear to allow a user to provide a custom
/// response.
public let includeOtherOption: Bool

/// Whether a close button should appear to allow users to skip the survey.
public let includeCloseOption: Bool

/// Rolls dice to see if survey should present or is in holdout.
///
/// - Returns: `true` if user is in holdout, false if survey should present.
Expand Down Expand Up @@ -83,7 +90,9 @@ final public class Survey: NSObject, Decodable {
message: String,
options: [SurveyOption],
presentationProbability: Double,
includeOtherOption: Bool
includeOtherOption: Bool,
includeCloseOption: Bool,
presentationCondition: SurveyShowCondition
) {
self.id = id
self.assignmentKey = assignmentKey
Expand All @@ -92,6 +101,8 @@ final public class Survey: NSObject, Decodable {
self.options = options
self.presentationProbability = presentationProbability
self.includeOtherOption = includeOtherOption
self.includeCloseOption = includeCloseOption
self.presentationCondition = presentationCondition
}
}

Expand All @@ -105,7 +116,9 @@ extension Survey: Stubbable {
message: "test",
options: [.stub()],
presentationProbability: 1,
includeOtherOption: true
includeOtherOption: true,
includeCloseOption: true,
presentationCondition: .onManualClose
)
}
}
1 change: 1 addition & 0 deletions Sources/SuperwallKit/Config/Models/SurveyOption.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Foundation

/// An option to display in a paywall survey.
@objc(SWKSurveyOption)
@objcMembers
final public class SurveyOption: NSObject, Decodable {
Expand Down