Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion QonversionCapacitorPlugin.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ Pod::Spec.new do |s|
s.ios.deployment_target = '14.0'
s.dependency 'Capacitor'
s.swift_version = '5.1'
s.dependency "QonversionSandwich", "7.7.0"
s.dependency "QonversionSandwich", "7.9.0"
end
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ dependencies {
implementation project(':capacitor-android')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation 'androidx.core:core-ktx:1.13.1'
implementation "io.qonversion:sandwich:7.7.0"
implementation "io.qonversion:sandwich:7.9.0"
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
Expand Down
11 changes: 10 additions & 1 deletion android/src/main/java/io/qonversion/capacitor/NoCodesPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,16 @@ class NoCodesPlugin : Plugin() {
val contextKey = call.getString("contextKey")
?: return call.noNecessaryDataError("contextKey")

noCodesSandwich.showScreen(contextKey)
val customVariablesJson = call.getObject("customVariables")
val customVariables: Map<String, String>? = customVariablesJson?.let { json ->
buildMap {
json.keys().forEach { key ->
(json.opt(key) as? String)?.let { value -> put(key, value) }
}
}
}

noCodesSandwich.showScreen(contextKey, customVariables)
call.resolve()
}

Expand Down
12 changes: 12 additions & 0 deletions android/src/main/java/io/qonversion/capacitor/QonversionPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import io.qonversion.sandwich.QonversionSandwich
import io.qonversion.sandwich.BridgeData

private const val EntitlementsUpdatedEvent = "entitlementsUpdatedEvent"
private const val DeferredPurchaseCompletedEvent = "deferredPurchaseCompletedEvent"

@CapacitorPlugin(name = "Qonversion")
class QonversionPlugin : Plugin() {
Expand All @@ -33,6 +34,10 @@ class QonversionPlugin : Plugin() {
override fun onEntitlementsUpdated(entitlements: BridgeData) {
notifyListeners(EntitlementsUpdatedEvent, entitlements.toJSObject())
}

override fun onDeferredPurchaseCompleted(transaction: BridgeData) {
notifyListeners(DeferredPurchaseCompletedEvent, transaction.toJSObject())
}
}

@PluginMethod
Expand Down Expand Up @@ -106,6 +111,13 @@ class QonversionPlugin : Plugin() {
qonversionSandwich.userProperties(call.toResultListener())
}

@PluginMethod
fun forceSendProperties(call: PluginCall) {
qonversionSandwich.forceSendProperties {
call.resolve()
}
}

@PluginMethod
fun isFallbackFileAccessible(call: PluginCall) {
qonversionSandwich.isFallbackFileAccessible(call.toResultListener())
Expand Down
12 changes: 11 additions & 1 deletion ios/Sources/QonversionPlugin/NoCodesPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,18 @@ public class NoCodesPlugin: CAPPlugin, CAPBridgedPlugin {
return call.noNecessaryDataError()
}

let customVariables: [String: String]? = call.getObject("customVariables").map { rawObject in
var result: [String: String] = [:]
for (key, value) in rawObject {
if let stringValue = value as? String {
result[key] = stringValue
}
}
return result
}

DispatchQueue.main.async { [weak self] in
self?.noCodesSandwich?.showScreen(contextKey)
self?.noCodesSandwich?.showScreen(contextKey, customVariables: customVariables)
}
call.resolve()
}
Expand Down
11 changes: 11 additions & 0 deletions ios/Sources/QonversionPlugin/QonversionPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class QonversionPlugin: CAPPlugin, CAPBridgedPlugin {
CAPPluginMethod(name: "remoteConfigList", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "userInfo", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "userProperties", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "forceSendProperties", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "restore", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "offerings", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "setDefinedUserProperty", returnType: CAPPluginReturnPromise),
Expand Down Expand Up @@ -138,6 +139,12 @@ public class QonversionPlugin: CAPPlugin, CAPBridgedPlugin {
qonversionSandwich?.userProperties(getDefaultCompletion(call))
}

@objc func forceSendProperties(_ call: CAPPluginCall) {
qonversionSandwich?.forceSendProperties {
call.resolve()
}
}

@objc func restore(_ call: CAPPluginCall) {
qonversionSandwich?.restore(getDefaultCompletion(call))
}
Expand Down Expand Up @@ -276,6 +283,10 @@ extension QonversionPlugin: QonversionEventListener {
self.notifyListeners("entitlementsUpdatedEvent", data: entitlements)
}

public func qonversionDidCompleteDeferredPurchase(_ transaction: [String : Any]) {
self.notifyListeners("deferredPurchaseCompletedEvent", data: transaction)
}

public func shouldPurchasePromoProduct(with productId: String) {
self.notifyListeners("shouldPurchasePromoProductEvent", data: ["productId": productId])
}
Expand Down
7 changes: 7 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @type {import('jest').Config} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['<rootDir>/src/**/__tests__/**/*.test.ts'],
moduleFileExtensions: ['ts', 'js'],
};
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"build": "npm run clean && tsc && rollup -c rollup.config.js",
"clean": "./node_modules/rimraf/bin.js ./dist",
"watch": "tsc --watch",
"test": "jest",
"prepublishOnly": "npm run build",
"deploy": "npm publish"
},
Expand All @@ -66,12 +67,15 @@
"@ionic/eslint-config": "^0.4.0",
"@ionic/prettier-config": "^1.0.1",
"@ionic/swiftlint-config": "^1.1.2",
"@types/jest": "^30.0.0",
"eslint": "^8.57.0",
"jest": "^30.3.0",
"prettier": "~2.3.0",
"prettier-plugin-java": "~1.0.2",
"rimraf": "^3.0.2",
"rollup": "^2.32.0",
"swiftlint": "^1.0.1",
"ts-jest": "^29.4.9",
"typescript": "^5.0.0",
"yarn": "^1.22.22"
},
Expand Down
7 changes: 5 additions & 2 deletions src/NoCodesApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ export interface NoCodesApi {

/**
* Show the screen using its context key.
* @param contextKey the context key of the screen which must be shown
* @param contextKey the context key of the screen which must be shown.
* @param customVariables optional map of custom variables that will be injected
* into the screen's JavaScript context. Variables are scoped
* to the provided contextKey and only applied to that screen.
*/
showScreen(contextKey: string): void;
showScreen(contextKey: string, customVariables?: Record<string, string>): void;

/**
* Close the current opened No-Code screen.
Expand Down
2 changes: 1 addition & 1 deletion src/NoCodesNativePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface NoCodesNativePlugin {
contextKey?: string;
}): Promise<void>;

showScreen(params: { contextKey: string }): Promise<void>;
showScreen(params: { contextKey: string; customVariables?: Record<string, string> }): Promise<void>;

close(): Promise<void>;

Expand Down
20 changes: 20 additions & 0 deletions src/QonversionApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {RemoteConfigList} from './dto/RemoteConfigList';
import {AttributionProvider, UserPropertyKey} from './dto/enums';
import {UserProperties} from './dto/UserProperties';
import {EntitlementsUpdateListener} from './dto/EntitlementsUpdateListener';
import {DeferredPurchasesListener} from './dto/DeferredPurchasesListener';
import {PromoPurchasesListener} from './dto/PromoPurchasesListener';
import {SKProductDiscount} from './dto/storeProducts/SKProductDiscount';
import {PromotionalOffer} from './dto/PromotionalOffer';
Expand Down Expand Up @@ -245,6 +246,13 @@ export interface QonversionApi {
*/
userProperties(): Promise<UserProperties>;

/**
* Force-flushes any pending user property updates to the server immediately.
* Use this when you need to ensure all previously set properties have been sent
* before performing an operation that depends on them.
*/
forceSendProperties(): Promise<void>;

/**
* Provide a listener to be notified about asynchronous user entitlements updates.
*
Expand All @@ -257,9 +265,21 @@ export interface QonversionApi {
* with {@link Qonversion.initialize}.
*
* @param listener listener to be called when entitlements update
* @deprecated Use {@link QonversionApi.setDeferredPurchasesListener} instead, which provides detailed purchase result information for deferred purchase completions.
*/
setEntitlementsUpdateListener(listener: EntitlementsUpdateListener): void;

/**
* Provide a listener to be notified about deferred purchases (e.g., SCA, Ask to Buy)
* once they are completed.
*
* You may set this listener *after* Qonversion SDK initialization using this method,
* or *during* Qonversion initialization via {@link QonversionConfigBuilder.setDeferredPurchasesListener}.
*
* @param listener listener to be called when a deferred purchase completes.
*/
setDeferredPurchasesListener(listener: DeferredPurchasesListener): void;

/**
* iOS only. Does nothing if called on Android.
*
Expand Down
7 changes: 6 additions & 1 deletion src/QonversionConfig.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import {EntitlementsCacheLifetime, Environment, LaunchMode} from './dto/enums';
import {EntitlementsUpdateListener} from './dto/EntitlementsUpdateListener';
import {DeferredPurchasesListener} from './dto/DeferredPurchasesListener';

export class QonversionConfig {
readonly projectKey: string;
readonly launchMode: LaunchMode;
readonly environment: Environment;
readonly entitlementsCacheLifetime: EntitlementsCacheLifetime;
/** @deprecated Use {@link deferredPurchasesListener} instead. */
readonly entitlementsUpdateListener: EntitlementsUpdateListener | undefined;
readonly deferredPurchasesListener: DeferredPurchasesListener | undefined;
readonly proxyUrl: string | undefined;
readonly kidsMode: boolean;

Expand All @@ -17,13 +20,15 @@ export class QonversionConfig {
entitlementsCacheLifetime: EntitlementsCacheLifetime = EntitlementsCacheLifetime.MONTH,
entitlementsUpdateListener: EntitlementsUpdateListener | undefined = undefined,
proxyUrl: string | undefined,
kidsMode: boolean = false
kidsMode: boolean = false,
deferredPurchasesListener: DeferredPurchasesListener | undefined = undefined,
) {
this.projectKey = projectKey;
this.launchMode = launchMode;
this.environment = environment;
this.entitlementsCacheLifetime = entitlementsCacheLifetime;
this.entitlementsUpdateListener = entitlementsUpdateListener;
this.deferredPurchasesListener = deferredPurchasesListener;
this.proxyUrl = proxyUrl;
this.kidsMode = kidsMode;
}
Expand Down
18 changes: 17 additions & 1 deletion src/QonversionConfigBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {EntitlementsCacheLifetime, Environment, LaunchMode} from './dto/enums';
import {EntitlementsUpdateListener} from './dto/EntitlementsUpdateListener';
import {DeferredPurchasesListener} from './dto/DeferredPurchasesListener';
import {QonversionConfig} from './QonversionConfig';

export class QonversionConfigBuilder {
Expand All @@ -14,6 +15,7 @@ export class QonversionConfigBuilder {
private environment: Environment = Environment.PRODUCTION;
private entitlementsCacheLifetime: EntitlementsCacheLifetime = EntitlementsCacheLifetime.MONTH;
private entitlementsUpdateListener: EntitlementsUpdateListener | undefined = undefined;
private deferredPurchasesListener: DeferredPurchasesListener | undefined = undefined;
private proxyUrl: string | undefined = undefined;
private kidsMode: boolean = false;

Expand Down Expand Up @@ -51,12 +53,25 @@ export class QonversionConfigBuilder {
*
* @param entitlementsUpdateListener listener to be called when entitlements update.
* @return builder instance for chain calls.
* @deprecated Use {@link QonversionConfigBuilder.setDeferredPurchasesListener} instead, which provides detailed purchase result information for deferred purchase completions.
*/
setEntitlementsUpdateListener(entitlementsUpdateListener: EntitlementsUpdateListener): QonversionConfigBuilder {
this.entitlementsUpdateListener = entitlementsUpdateListener;
return this;
}

/**
* Provide a listener to be notified about deferred purchases (e.g., SCA, Ask to Buy)
* once they are completed.
*
* @param deferredPurchasesListener listener to be called when a deferred purchase completes.
* @return builder instance for chain calls.
*/
setDeferredPurchasesListener(deferredPurchasesListener: DeferredPurchasesListener): QonversionConfigBuilder {
this.deferredPurchasesListener = deferredPurchasesListener;
return this;
}

/**
* Provide a URL to your proxy server which will redirect all the requests from the app
* to our API. Please, contact us before using this feature.
Expand Down Expand Up @@ -94,7 +109,8 @@ export class QonversionConfigBuilder {
this.entitlementsCacheLifetime,
this.entitlementsUpdateListener,
this.proxyUrl,
this.kidsMode
this.kidsMode,
this.deferredPurchasesListener,
)
}
}
4 changes: 4 additions & 0 deletions src/QonversionNativePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ export interface QonversionNativePlugin {

userProperties(): Promise<QUserProperties>;

forceSendProperties(): Promise<void>;

collectAdvertisingId(): void;

collectAppleSearchAdsAttribution(): void;
Expand All @@ -90,5 +92,7 @@ export interface QonversionNativePlugin {

addListener(event: 'entitlementsUpdatedEvent', listener: (payload: (Record<string, QEntitlement> | null | undefined)) => void): void;

addListener(event: 'deferredPurchaseCompletedEvent', listener: (payload: QPurchaseResult | null | undefined) => void): void;

addListener(event: 'shouldPurchasePromoProductEvent', listener: (payload: {productId: string}) => void): void;
}
12 changes: 12 additions & 0 deletions src/dto/DeferredPurchasesListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {PurchaseResult} from './PurchaseResult';

export interface DeferredPurchasesListener {

/**
* Called when a deferred purchase completes.
* For example, when pending purchases like SCA, Ask to buy, etc., are approved and finalized.
* Provides the full purchase result with entitlements and store transaction details.
* @param purchaseResult the result of the completed deferred purchase.
*/
onDeferredPurchaseCompleted(purchaseResult: PurchaseResult): void;
}
3 changes: 3 additions & 0 deletions src/dto/EntitlementsUpdateListener.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import {Entitlement} from './Entitlement';

/**
* @deprecated Use {@link DeferredPurchasesListener} instead.
*/
export interface EntitlementsUpdateListener {

/**
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export * from './dto/PurchaseOptionsBuilder';
export * from './dto/PurchaseResult';
export * from './dto/StoreTransaction';
export * from './dto/EntitlementsUpdateListener';
export * from './dto/DeferredPurchasesListener';
export * from './dto/PromoPurchasesListener';
export * from './dto/storeProducts/SKPaymentDiscount';
export * from './dto/storeProducts/SKProduct';
Expand Down
4 changes: 2 additions & 2 deletions src/internal/NoCodesInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export class NoCodesInternal implements NoCodesApi {
await NoCodesPlugin.setScreenPresentationConfig({ configData: data, contextKey });
}

async showScreen(contextKey: string) {
await NoCodesPlugin.showScreen({ contextKey });
async showScreen(contextKey: string, customVariables?: Record<string, string>) {
await NoCodesPlugin.showScreen({ contextKey, customVariables });
}

async close() {
Expand Down
Loading
Loading