@adapty/capacitor
Version:
Official Adapty SDK for Capacitor
1,214 lines • 53.5 kB
JavaScript
// PluginListenerHandle is no longer referenced directly in this file
import { Capacitor } from '@capacitor/core';
import { AdaptyEmitter } from './adapty-emitter';
import { AdaptyCapacitorPlugin } from './bridge/plugin';
import { getCoder } from './coder-registry';
import { defaultAdaptyOptions } from './default-configs';
import { AdaptyConfigurationCoder } from './shared/coders/adapty-configuration';
import { AdaptyIdentifyParamsCoder } from './shared/coders/adapty-identify-params';
import { AdaptyPaywallCoder } from './shared/coders/adapty-paywall';
import { AdaptyPaywallProductCoder } from './shared/coders/adapty-paywall-product';
import { AdaptyProfileParametersCoder } from './shared/coders/adapty-profile-parameters';
import { AdaptyPurchaseParamsCoder } from './shared/coders/adapty-purchase-params';
import { Log, LogContext } from './shared/logger';
import { isErrorResponse, isSuccessResponse, } from './shared/types/method-types';
import { filterUndefined } from './shared/utils/compact-object';
import { mergeOptions } from './shared/utils/merge-options';
import { withErrorContext } from './shared/utils/with-error-context';
/**
* Entry point for the Adapty SDK.
*
* @remarks
* This is the main Adapty class, excluding UI rendering functionality.
*
* @public
*/
export class Adapty {
constructor() {
this.activating = null;
this.resolveHeldActivation = null;
this.emitter = new AdaptyEmitter();
this.nonWaitingMethods = [
'activate',
'is_activated',
'get_paywall_for_default_audience',
'get_onboarding_for_default_audience',
'set_log_level',
'set_fallback',
];
this.options = defaultAdaptyOptions;
/**
* Adds an event listener for SDK events.
*
* @remarks
* You can listen to various events from the Adapty SDK such as profile updates.
* The listener will be called whenever the corresponding event occurs.
*
* @param eventName - The name of the event to listen to.
* @param listenerFunc - The function to call when the event occurs.
* @returns A listener handle that can be used to remove the listener.
*
* @example
* Listen to profile updates
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* const listener = adapty.addListener('onLatestProfileLoad', (profile) => {
* console.log('Profile updated:', profile);
* const isSubscribed = profile.accessLevels['YOUR_ACCESS_LEVEL']?.isActive;
* if (isSubscribed) {
* console.log('User has premium access');
* }
* });
*
* // Later, remove the listener
* listener.remove();
* ```
*/
this.addListener = (eventName, listenerFunc) => {
const wrappedListener = withErrorContext(listenerFunc, eventName, 'Adapty');
return this.emitter.addListener(eventName, wrappedListener);
};
}
/**
* Helper method for logging encode operations
*/
encodeWithLogging(coder, data, methodName, parentCtx) {
if (!parentCtx) {
return coder.encode(data);
}
const encodeLog = parentCtx.encode({ methodName: `encode/${methodName}` });
encodeLog.start(() => ({ data }));
try {
const result = coder.encode(data);
encodeLog.success(() => ({ result }));
return result;
}
catch (error) {
encodeLog.failed(() => ({ error }));
throw error;
}
}
/**
* Handle method calls through crossplatform bridge with type safety
* @internal
*/
async handleMethodCall(methodName, args, ctx, log) {
// Hold on deferred activation first
if (this.resolveHeldActivation && !this.nonWaitingMethods.includes(methodName)) {
log.wait(() => ({}));
await this.resolveHeldActivation();
this.resolveHeldActivation = null;
log.waitComplete(() => ({}));
}
// Then wait for ongoing activation if required
if (this.activating && (!this.nonWaitingMethods.includes(methodName) || methodName === 'is_activated')) {
log.wait(() => ({}));
await this.activating;
log.waitComplete(() => ({}));
}
const bridgeLog = ctx.bridge({ methodName: `fetch/${methodName}` });
bridgeLog.start(() => ({ method: methodName, args }));
try {
const result = await AdaptyCapacitorPlugin.handleMethodCall({
methodName,
args,
});
bridgeLog.success(() => ({ crossPlatformJson: result.crossPlatformJson }));
// Parse JSON response with type safety
const parsedResponse = JSON.parse(result.crossPlatformJson);
// Check for native errors
if (isErrorResponse(parsedResponse)) {
const error = parsedResponse.error;
const errorMessage = `Native error: ${error.message} (code: ${error.adaptyCode})`;
const nativeError = new Error(errorMessage);
log.failed(() => ({ error: nativeError }));
throw nativeError;
}
// Extract success data with type safety
if (isSuccessResponse(parsedResponse)) {
const successData = parsedResponse.success;
// Apply decoder if available for this method
const coder = getCoder(methodName);
let result;
if (coder) {
// Create decode scope for logging decode operations
const decodeLog = ctx.decode({ methodName: `decode/${methodName}` });
decodeLog.start(() => ({ successData }));
try {
result = coder.decode(successData);
decodeLog.success(() => ({ result }));
}
catch (error) {
decodeLog.failed(() => ({ error }));
throw error;
}
}
else {
result = successData;
}
log.success(() => ({ result }));
return result;
}
const formatError = new Error('Invalid response format: missing success or error field');
log.failed(() => ({ error: formatError }));
throw formatError;
}
catch (error) {
bridgeLog.failed(() => ({ error }));
// If it's our custom error and log wasn't called yet, log it
if (error instanceof Error && !error.message.startsWith('{')) {
if (!error.message.startsWith('Native error:')) {
log.failed(() => ({ error }));
}
throw error;
}
// If JSON parsing fails, wrap the error
const parseError = new Error(`Failed to parse native response: ${error}`);
log.failed(() => ({ error: parseError }));
throw parseError;
}
}
isPaywallProduct(obj) {
return 'vendorProductId' in obj;
}
/**
* Initializes the Adapty SDK.
*
* @remarks
* This method must be called in order for the SDK to work.
* It is preferred to call it as early as possible in the app lifecycle,
* so background activities can be performed and cache can be updated.
*
* @example
*
* @example
* Usage with your user identifier from your system
* ```typescript
* await adapty.activate({
* apiKey: 'YOUR_PUBLIC_SDK_KEY',
* params: {
* customerUserId: 'YOUR_USER_ID'
* }
* });
* ```
*
* @param options - The activation options
* @param options.apiKey - You can find it in your app settings in Adapty Dashboard App settings > General.
* @param options.params - Optional activation parameters of type {@link ActivateParamsInput}.
* @returns A promise that resolves when the SDK is activated.
* @throws Error if the SDK is already activated or if the API key is invalid.
*/
async activate(options) {
const { apiKey, params = {} } = options;
// Validate API key
if (!apiKey || typeof apiKey !== 'string' || apiKey.trim().length === 0) {
throw new Error('API key is required and must be a non-empty string');
}
// Prevent multiple activations
if (this.activating) {
await this.activating;
return;
}
// Check if already activated (if __ignoreActivationOnFastRefresh is enabled)
if (params.__ignoreActivationOnFastRefresh) {
try {
const isActivated = await this.isActivated();
if (isActivated) {
return;
}
}
catch (error) {
// Continue with activation if we can't check activation status
}
}
// Defer activation if requested (for debugging) — start activation lazily on first blocked call
if (params.__debugDeferActivation) {
return new Promise((resolve) => {
// Do not start activation immediately. It will be started when a blocked method comes in.
this.resolveHeldActivation = async () => {
this.activating = this.performActivation(apiKey, params);
try {
await this.activating;
}
finally {
this.activating = null;
}
resolve();
};
});
}
// Perform activation
this.activating = this.performActivation(apiKey, params);
await this.activating;
this.activating = null;
}
async performActivation(apiKey, params) {
const method = 'activate';
// Set log level before creating LogContext
const logLevel = params.logLevel;
Log.logLevel = logLevel || null;
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({ apiKey, params }));
const configurationCoder = new AdaptyConfigurationCoder();
const configuration = configurationCoder.encode(apiKey, params);
const activateRequestWithUndefined = {
method,
configuration,
};
const activateRequest = filterUndefined(activateRequestWithUndefined);
await this.handleMethodCall(method, JSON.stringify(activateRequest), ctx, log);
}
/**
* Checks if the Adapty SDK is activated.
*
* @returns A promise that resolves to `true` if the SDK is activated, `false` otherwise.
* @throws Error if an error occurs while checking activation status.
*/
async isActivated() {
const method = 'is_activated';
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({}));
const argsWithUndefined = {
method,
};
const args = filterUndefined(argsWithUndefined);
return await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Gets the current installation status.
*
* @remarks
* Installation status provides information about when the app was installed,
* how many times it has been launched, and other installation-related details.
* The status can be "not_available", "not_determined", or "determined" with details.
*
* @returns A promise that resolves with the installation status.
* @throws Error if an error occurs while retrieving the installation status.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* const status = await adapty.getCurrentInstallationStatus();
* if (status.status === 'determined') {
* console.log('Install time:', status.details.installTime);
* console.log('Launch count:', status.details.appLaunchCount);
* }
* } catch (error) {
* console.error('Failed to get installation status:', error);
* }
* ```
*/
async getCurrentInstallationStatus() {
const method = 'get_current_installation_status';
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({}));
const argsWithUndefined = {
method,
};
const args = filterUndefined(argsWithUndefined);
return await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Fetches the paywall by the specified placement.
*
* @remarks
* With Adapty, you can remotely configure the products and offers in your app
* by simply adding them to paywalls – no need for hardcoding them.
* The only thing you hardcode is the placement ID.
* This flexibility allows you to easily update paywalls, products, and offers,
* or run A/B tests, all without the need for a new app release.
*
* @param options - The options for fetching the paywall
* @param options.placementId - The identifier of the desired placement. This is the value you specified when creating a placement in the Adapty Dashboard.
* @param options.locale - Optional. The identifier of the paywall localization. Default: `'en'`. See {@link https://docs.adapty.io/docs/localizations-and-locale-codes | Localizations and locale codes} for more information.
* @param options.params - Optional. Additional parameters for fetching the paywall, including fetch policy and load timeout.
* @returns A promise that resolves with the requested {@link AdaptyPaywall}.
* @throws Error if the paywall with the specified ID is not found or if your bundle ID does not match with your Adapty Dashboard setup.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* const paywall = await adapty.getPaywall({
* placementId: 'YOUR_PLACEMENT_ID',
* locale: 'en',
* });
* console.log('Paywall fetched successfully');
* } catch (error) {
* console.error('Failed to fetch paywall:', error);
* }
* ```
*/
async getPaywall(options) {
const method = 'get_paywall';
const optionsWithDefault = mergeOptions(options, this.options[method]);
const params = optionsWithDefault.params;
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({ optionsWithDefault }));
const argsWithUndefined = {
method,
placement_id: optionsWithDefault.placementId,
load_timeout: params.loadTimeoutMs / 1000,
locale: optionsWithDefault.locale,
fetch_policy: params.fetchPolicy === 'return_cache_data_if_not_expired_else_load'
? { type: params.fetchPolicy, max_age: params.maxAgeSeconds }
: { type: params.fetchPolicy },
};
const args = filterUndefined(argsWithUndefined);
return await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Fetches the paywall of the specified placement for the **All Users** audience.
*
* @remarks
* With Adapty, you can remotely configure the products and offers in your app
* by simply adding them to paywalls – no need for hardcoding them.
* The only thing you hardcode is the placement ID.
*
* However, it's crucial to understand that the recommended approach is to fetch the paywall
* through the placement ID by the {@link getPaywall} method.
* The `getPaywallForDefaultAudience` method should be a last resort due to its significant drawbacks:
* - Potential backward compatibility issues
* - Loss of targeting (all users see the same paywall)
*
* See {@link https://docs.adapty.io/docs/capacitor-get-pb-paywalls#get-a-paywall-for-a-default-audience-to-fetch-it-faster | documentation} for more details.
*
* @param options - The options for fetching the paywall
* @param options.placementId - The identifier of the desired placement.
* @param options.locale - Optional. The identifier of the paywall localization. Default: `'en'`.
* @param options.params - Optional. Additional parameters for fetching the paywall.
* @returns A promise that resolves with the requested {@link AdaptyPaywall}.
* @throws Error if the paywall with the specified ID is not found.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* const paywall = await adapty.getPaywallForDefaultAudience({
* placementId: 'YOUR_PLACEMENT_ID',
* locale: 'en',
* });
* } catch (error) {
* console.error('Failed to fetch paywall:', error);
* }
* ```
*/
async getPaywallForDefaultAudience(options) {
var _a;
const method = 'get_paywall_for_default_audience';
const optionsWithDefault = mergeOptions(options, this.options[method]);
const params = optionsWithDefault.params;
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({ optionsWithDefault }));
const argsWithUndefined = {
method,
placement_id: optionsWithDefault.placementId,
locale: optionsWithDefault.locale,
fetch_policy: params.fetchPolicy === 'return_cache_data_if_not_expired_else_load'
? { type: params.fetchPolicy, max_age: params.maxAgeSeconds }
: { type: (_a = params.fetchPolicy) !== null && _a !== void 0 ? _a : 'reload_revalidating_cache_data' },
};
const args = filterUndefined(argsWithUndefined);
return await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Fetches a list of products associated with a provided paywall.
*
* @param options - The options object
* @param options.paywall - A paywall to fetch products for. You can get it using {@link getPaywall} method.
* @returns A promise that resolves with a list of {@link AdaptyPaywallProduct} associated with a provided paywall.
* @throws Error if an error occurs while fetching products.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID' });
* const products = await adapty.getPaywallProducts({ paywall });
* console.log('Products:', products);
* } catch (error) {
* console.error('Failed to fetch products:', error);
* }
* ```
*/
async getPaywallProducts(options) {
const method = 'get_paywall_products';
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({ options }));
const paywallCoder = new AdaptyPaywallCoder();
const argsWithUndefined = {
method,
paywall: this.encodeWithLogging(paywallCoder, options.paywall, 'AdaptyPaywall', ctx),
};
const args = filterUndefined(argsWithUndefined);
return await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Fetches the onboarding by the specified placement.
*
* @remarks
* When you create an onboarding with the no-code builder, it's stored as a container with configuration
* that your app needs to fetch and display. This container manages the entire experience - what content appears,
* how it's presented, and how user interactions are processed.
*
* @param options - The options for fetching the onboarding
* @param options.placementId - The identifier of the desired placement.
* @param options.locale - Optional. The identifier of the onboarding localization. Default: `'en'`.
* @param options.params - Optional. Additional parameters for fetching the onboarding.
* @returns A promise that resolves with the requested {@link AdaptyOnboarding}.
* @throws Error if the onboarding with the specified ID is not found.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* const onboarding = await adapty.getOnboarding({
* placementId: 'YOUR_PLACEMENT_ID',
* locale: 'en',
* params: {
* fetchPolicy: 'reload_revalidating_cache_data',
* loadTimeoutMs: 5000
* }
* });
* console.log('Onboarding fetched successfully');
* } catch (error) {
* console.error('Failed to fetch onboarding:', error);
* }
* ```
*/
async getOnboarding(options) {
const method = 'get_onboarding';
const optionsWithDefault = mergeOptions(options, this.options[method]);
const params = optionsWithDefault.params;
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({ optionsWithDefault }));
const argsWithUndefined = {
method,
placement_id: optionsWithDefault.placementId,
locale: optionsWithDefault.locale,
load_timeout: params.loadTimeoutMs / 1000,
fetch_policy: params.fetchPolicy === 'return_cache_data_if_not_expired_else_load'
? { type: params.fetchPolicy, max_age: params.maxAgeSeconds }
: { type: params.fetchPolicy },
};
const args = filterUndefined(argsWithUndefined);
return await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Fetches the onboarding of the specified placement for the **All Users** audience.
*
* @remarks
* It's crucial to understand that the recommended approach is to fetch the onboarding
* by the {@link getOnboarding} method. The `getOnboardingForDefaultAudience` method
* should be used only if faster fetching outweighs the drawbacks:
* - Potential backward compatibility issues
* - Loss of personalization (no targeting based on country, attribution, or custom attributes)
*
* @param options - The options for fetching the onboarding
* @param options.placementId - The identifier of the desired placement.
* @param options.locale - Optional. The identifier of the onboarding localization. Default: `'en'`.
* @param options.params - Optional. Additional parameters for fetching the onboarding.
* @returns A promise that resolves with the requested {@link AdaptyOnboarding}.
* @throws Error if the onboarding with the specified ID is not found.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* const onboarding = await adapty.getOnboardingForDefaultAudience({
* placementId: 'YOUR_PLACEMENT_ID',
* locale: 'en',
* });
* } catch (error) {
* console.error('Failed to fetch onboarding:', error);
* }
* ```
*/
async getOnboardingForDefaultAudience(options) {
var _a;
const method = 'get_onboarding_for_default_audience';
const optionsWithDefault = mergeOptions(options, this.options[method]);
const params = optionsWithDefault.params;
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({ optionsWithDefault }));
const argsWithUndefined = {
method,
placement_id: optionsWithDefault.placementId,
locale: optionsWithDefault.locale,
fetch_policy: params.fetchPolicy === 'return_cache_data_if_not_expired_else_load'
? { type: params.fetchPolicy, max_age: params.maxAgeSeconds }
: { type: (_a = params.fetchPolicy) !== null && _a !== void 0 ? _a : 'reload_revalidating_cache_data' },
};
const args = filterUndefined(argsWithUndefined);
return await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Fetches a user profile.
*
* @remarks
* The getProfile method provides the most up-to-date result
* as it always tries to query the API.
* If for some reason (e.g. no internet connection),
* the Adapty SDK fails to retrieve information from the server,
* the data from cache will be returned.
* It is also important to note
* that the Adapty SDK updates {@link AdaptyProfile} cache
* on a regular basis, in order
* to keep this information as up-to-date as possible.
*
* @returns A promise that resolves with the user's {@link AdaptyProfile}.
* @throws Error if an error occurs while fetching the profile.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* const profile = await adapty.getProfile();
* const isSubscribed = profile.accessLevels['YOUR_ACCESS_LEVEL']?.isActive;
* if (isSubscribed) {
* console.log('User has access to premium features');
* }
* } catch (error) {
* console.error('Failed to get profile:', error);
* }
* ```
*/
async getProfile() {
const method = 'get_profile';
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({}));
const argsWithUndefined = {
method,
};
const args = filterUndefined(argsWithUndefined);
return await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Logs in a user with a provided customerUserId.
*
* @remarks
* If you don't have a user id on SDK initialization,
* you can set it later at any time with this method.
* The most common cases are after registration/authorization
* when the user switches from being an anonymous user to an authenticated user.
*
* @param options - The identification options
* @param options.customerUserId - A unique user identifier.
* @param options.params - Optional. Additional parameters for identification, including platform-specific settings.
* @returns A promise that resolves when identification is complete.
* @throws Error if an error occurs during identification.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* await adapty.identify({ customerUserId: 'YOUR_USER_ID' });
* console.log('User identified successfully');
* } catch (error) {
* console.error('Failed to identify user:', error);
* }
* ```
*/
async identify(options) {
const method = 'identify';
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({ options }));
const identifyParamsCoder = new AdaptyIdentifyParamsCoder();
const parameters = identifyParamsCoder.encode(options.params);
const argsWithUndefined = {
method,
customer_user_id: options.customerUserId,
parameters,
};
const args = filterUndefined(argsWithUndefined);
await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Logs a paywall view event.
*
* @remarks
* Adapty helps you to measure the performance of the paywalls.
* We automatically collect all the metrics related to purchases except for custom paywall views.
* This is because only you know when the paywall was shown to a customer.
*
* Whenever you show a paywall to your user,
* call this function to log the event,
* and it will be accumulated in the paywall metrics.
*
* @param options - The options object
* @param options.paywall - The paywall object that was shown to the user.
* @returns A promise that resolves when the event is logged.
* @throws Error if an error occurs while logging the event.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID' });
* // ...after opening the paywall
* await adapty.logShowPaywall({ paywall });
* ```
*/
async logShowPaywall(options) {
const method = 'log_show_paywall';
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({ options }));
const paywallCoder = new AdaptyPaywallCoder();
const argsWithUndefined = {
method,
paywall: this.encodeWithLogging(paywallCoder, options.paywall, 'AdaptyPaywall', ctx),
};
const args = filterUndefined(argsWithUndefined);
await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Opens a web paywall in the default browser.
*
* @param options - The options object
* @param options.paywallOrProduct - The paywall or product to open as a web paywall.
* @returns A promise that resolves when the web paywall is opened.
* @throws Error if an error occurs while opening the web paywall.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID' });
* await adapty.openWebPaywall({ paywallOrProduct: paywall });
* } catch (error) {
* console.error('Failed to open web paywall:', error);
* }
* ```
*/
async openWebPaywall(options) {
const method = 'open_web_paywall';
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({ options }));
const paywallCoder = new AdaptyPaywallCoder();
const productCoder = new AdaptyPaywallProductCoder();
const argsWithUndefined = Object.assign({ method }, (this.isPaywallProduct(options.paywallOrProduct)
? { product: this.encodeWithLogging(productCoder, options.paywallOrProduct, 'AdaptyPaywallProduct', ctx) }
: { paywall: this.encodeWithLogging(paywallCoder, options.paywallOrProduct, 'AdaptyPaywall', ctx) }));
const args = filterUndefined(argsWithUndefined);
await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Creates a URL for a web paywall.
*
* @remarks
* This method generates a URL that can be used to open a web version of the paywall.
* You can use this URL in a custom web view or a browser.
*
* @param options - The options object
* @param options.paywallOrProduct - The paywall or product to create a URL for.
* @returns A promise that resolves with the web paywall URL.
* @throws Error if an error occurs while creating the URL.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID' });
* const url = await adapty.createWebPaywallUrl({ paywallOrProduct: paywall });
* console.log('Web paywall URL:', url);
* } catch (error) {
* console.error('Failed to create web paywall URL:', error);
* }
* ```
*/
async createWebPaywallUrl(options) {
const method = 'create_web_paywall_url';
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({ options }));
const paywallCoder = new AdaptyPaywallCoder();
const productCoder = new AdaptyPaywallProductCoder();
const argsWithUndefined = Object.assign({ method }, (this.isPaywallProduct(options.paywallOrProduct)
? { product: this.encodeWithLogging(productCoder, options.paywallOrProduct, 'AdaptyPaywallProduct', ctx) }
: { paywall: this.encodeWithLogging(paywallCoder, options.paywallOrProduct, 'AdaptyPaywall', ctx) }));
const args = filterUndefined(argsWithUndefined);
return await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Logs out the current user.
*
* @remarks
* You can then login the user using {@link identify} method.
*
* @returns A promise that resolves when the user is logged out.
* @throws Error if an error occurs during logout.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* await adapty.logout();
* console.log('User logged out successfully');
* } catch (error) {
* console.error('Failed to logout user:', error);
* }
* ```
*/
async logout() {
const method = 'logout';
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({}));
const argsWithUndefined = {
method,
};
const args = filterUndefined(argsWithUndefined);
await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Performs a purchase of the specified product.
*
* @remarks
* In paywalls built with Paywall Builder, purchases are processed automatically with no additional code.
*
* @param options - The purchase options
* @param options.product - The product to be purchased. You can get it using {@link getPaywallProducts} method.
* @param options.params - Optional. Additional parameters for the purchase, including Android subscription update params.
* @returns A promise that resolves with the {@link AdaptyPurchaseResult} object containing details about the purchase.
* If the result is `'success'`, it also includes the updated user's profile.
* @throws Error if an error occurs during the purchase process.
*
* @example
* Basic purchase
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* const result = await adapty.makePurchase({ product });
*
* if (result.type === 'success') {
* const isSubscribed = result.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive;
* if (isSubscribed) {
* console.log('User is now subscribed!');
* }
* }
* } catch (error) {
* console.error('Purchase failed:', error);
* }
* ```
*/
async makePurchase(options) {
var _a;
const method = 'make_purchase';
const params = (_a = options.params) !== null && _a !== void 0 ? _a : {};
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({ options }));
const productCoder = new AdaptyPaywallProductCoder();
const purchaseParamsCoder = new AdaptyPurchaseParamsCoder();
const encodedProduct = this.encodeWithLogging(productCoder, options.product, 'AdaptyPaywallProduct', ctx);
const productInput = productCoder.getInput(encodedProduct);
const purchaseParams = this.encodeWithLogging(purchaseParamsCoder, params, 'AdaptyPurchaseParams', ctx);
const argsWithUndefined = {
method,
product: productInput,
parameters: purchaseParams,
};
const args = filterUndefined(argsWithUndefined);
return await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Opens a native modal screen to redeem Apple Offer Codes.
*
* @remarks
* iOS 14+ only. Since iOS 14.0, your users can redeem Offer Codes.
* To enable users to redeem offer codes, you can display the offer code redemption sheet.
*
* @returns A promise that resolves when the redemption sheet is presented.
* @throws Error if an error occurs or if called on Android.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* await adapty.presentCodeRedemptionSheet();
* } catch (error) {
* console.error('Failed to present code redemption sheet:', error);
* }
* ```
*/
async presentCodeRedemptionSheet() {
const method = 'present_code_redemption_sheet';
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({}));
const argsWithUndefined = {
method,
};
const args = filterUndefined(argsWithUndefined);
await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Sets the variation ID of the purchase.
*
* @remarks
* In Observer mode, Adapty SDK doesn't know where the purchase was made from.
* you can manually assign variation to the purchase.
* After doing this, you'll be able to see metrics in Adapty Dashboard.
*
* @param options - The options object
* @param options.transactionId - The transaction ID of the purchase.
* @param options.variationId - Optional. The variation ID from the {@link AdaptyPaywall}.
* @returns A promise that resolves when the transaction is reported.
* @throws Error if an error occurs while reporting the transaction.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* await adapty.reportTransaction({
* transactionId: 'transaction_123',
* variationId: 'variation_456'
* });
* } catch (error) {
* console.error('Failed to report transaction:', error);
* }
* ```
*/
async reportTransaction(options) {
const method = 'report_transaction';
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({ options }));
const argsWithUndefined = {
method,
transaction_id: options.transactionId,
variation_id: options.variationId,
};
const args = filterUndefined(argsWithUndefined);
await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Restores user purchases and updates the profile.
*
* @remarks
* Restoring purchases allows users to regain access to previously purchased content,
* such as subscriptions or in-app purchases, without being charged again.
* This feature is especially useful for users who may have uninstalled and reinstalled the app
* or switched to a new device.
*
* In paywalls built with Paywall Builder, purchases are restored automatically without additional code from you.
*
* @returns A promise that resolves with the updated user's {@link AdaptyProfile}.
* @throws Error if an error occurs during the restore process.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* const profile = await adapty.restorePurchases();
* const isSubscribed = profile.accessLevels['YOUR_ACCESS_LEVEL']?.isActive;
*
* if (isSubscribed) {
* console.log('Access restored successfully!');
* } else {
* console.log('No active subscriptions found');
* }
* } catch (error) {
* console.error('Failed to restore purchases:', error);
* }
* ```
*/
async restorePurchases() {
const method = 'restore_purchases';
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({}));
const argsWithUndefined = {
method,
};
const args = filterUndefined(argsWithUndefined);
return await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Sets the fallback paywalls.
*
* @remarks
* Fallback file will be used if the SDK fails
* to fetch the paywalls from the dashboard.
* It is not designed to be used for the offline flow,
* as products are not cached in Adapty.
*
* @param options - The options object
* @param options.fileLocation - The location of the fallback file for each platform.
* @returns A promise that resolves when fallback placements are saved.
* @throws Error if an error occurs while setting the fallback.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* await adapty.setFallback({
* fileLocation: {
* ios: { fileName: 'fallback_paywalls.json' },
* android: { relativeAssetPath: 'fallback_paywalls.json' }
* }
* });
* } catch (error) {
* console.error('Failed to set fallback:', error);
* }
* ```
*/
async setFallback(options) {
const method = 'set_fallback';
const platform = Capacitor.getPlatform();
let fileLocationString;
if (platform === 'ios') {
fileLocationString = options.fileLocation.ios.fileName;
}
else if (platform === 'android') {
// Add suffixes to distinguish resource types on Android
if ('relativeAssetPath' in options.fileLocation.android) {
fileLocationString = `${options.fileLocation.android.relativeAssetPath}a`;
}
else {
fileLocationString = `${options.fileLocation.android.rawResName}r`;
}
}
else {
fileLocationString = '';
}
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({ platform, fileLocationString }));
const argsWithUndefined = {
method,
asset_id: fileLocationString,
};
const args = filterUndefined(argsWithUndefined);
await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Sets an integration identifier for the current user.
*
* @remarks
* Integration identifiers can be used to link Adapty profiles to external systems
* and track users across different platforms.
*
* @param options - The options object
* @param options.key - The key of the integration identifier.
* @param options.value - The value of the integration identifier.
* @returns A promise that resolves when the integration identifier is set.
* @throws Error if an error occurs while setting the identifier.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* await adapty.setIntegrationIdentifier({
* key: 'firebase_app_instance_id',
* value: 'YOUR_FIREBASE_ID'
* });
* } catch (error) {
* console.error('Failed to set integration identifier:', error);
* }
* ```
*/
async setIntegrationIdentifier(options) {
const method = 'set_integration_identifiers';
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({ options }));
const argsWithUndefined = {
method,
key_values: { [options.key]: options.value },
};
const args = filterUndefined(argsWithUndefined);
await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Sets the preferred log level and/or custom logger configuration.
*
* @remarks
* By default, the log level is set to `error`.
*
* There are four levels available:
* - `error`: only errors will be logged
* - `warn`: messages from the SDK that do not cause critical errors, but are worth paying attention to
* - `info`: various information messages, such as those that log the lifecycle of various modules
* - `verbose`: any additional information that may be useful during debugging, such as function calls, API queries, etc.
*
* @param options - The options object
* @param options.logLevel - Optional. The new preferred log level.
* @param options.logger - Optional. Custom logger configuration.
* @returns A promise that resolves when the log level is set.
* @throws Error if the log level is invalid.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* // Set log level
* await adapty.setLogLevel({ logLevel: 'verbose' });
*
* // Or set custom logger
* await adapty.setLogLevel({
* logger: {
* handler: (level, scope, message, data) => {
* sendLogToYourServer(`[${level}] ${message}`, data);
* }
* }
* });
* ```
*/
async setLogLevel(options) {
const method = 'set_log_level';
if (options.logger) {
Log.configure(options.logger);
}
if (options.logLevel !== undefined) {
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({ options: { logLevel: options.logLevel } }));
// Update log level immediately
Log.logLevel = options.logLevel;
const argsWithUndefined = {
method,
value: options.logLevel,
};
const args = filterUndefined(argsWithUndefined);
await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
}
/**
* Updates attribution data for the current user.
*
* @remarks
* Attribution data can be used to track marketing campaigns and user acquisition sources.
*
* @param options - The options object
* @param options.attribution - An object containing attribution data.
* @param options.source - The source of the attribution data (e.g., 'adjust', 'appsflyer').
* @returns A promise that resolves when the attribution data is updated.
* @throws Error if parameters are invalid or not provided.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* const attribution = {
* 'Adjust Adid': 'adjust_adid',
* 'Adjust Network': 'adjust_network',
* 'Adjust Campaign': 'adjust_campaign',
* 'Adjust Adgroup': 'adjust_adgroup',
* };
*
* await adapty.updateAttribution({ attribution, source: 'adjust' });
* } catch (error) {
* console.error('Failed to update attribution:', error);
* }
* ```
*/
async updateAttribution(options) {
const method = 'update_attribution_data';
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({ options }));
const argsWithUndefined = {
method,
attribution: JSON.stringify(options.attribution),
source: options.source,
};
const args = filterUndefined(argsWithUndefined);
await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Updates the user's consent for collecting refund data.
*
* @remarks
* iOS only. This method has no effect on Android.
* Use this method to update whether the SDK should collect refund data for the user.
*
* @param options - The options object
* @param options.consent - Whether to collect refund data.
* @returns A promise that resolves when the consent is updated (or immediately on Android).
* @throws Error if an error occurs on iOS.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* await adapty.updateCollectingRefundDataConsent({ consent: true });
* } catch (error) {
* console.error('Failed to update consent:', error);
* }
* ```
*/
async updateCollectingRefundDataConsent(options) {
const platform = Capacitor.getPlatform();
if (platform === 'android') {
return Promise.resolve();
}
const method = 'update_collecting_refund_data_consent';
const ctx = new LogContext();
const log = ctx.call({ methodName: method });
log.start(() => ({ options }));
const argsWithUndefined = {
method,
consent: options.consent,
};
const args = filterUndefined(argsWithUndefined);
await this.handleMethodCall(method, JSON.stringify(args), ctx, log);
}
/**
* Updates the user's refund preference.
*
* @remarks
* iOS only. This method has no effect on Android.
* Use this method to set the user's preference for handling refunds.
*
* @param options - The options object
* @param options.refundPreference - The refund preference setting.
* @returns A promise that resolves when the preference is updated (or immediately on Android).
* @throws Error if an error occurs on iOS.
*
* @example
* ```typescript
* import { adapty } from '@adapty/capacitor';
*
* try {
* await adapty.updateRefundPreference({ refundPreference: 'ask_to_cancel' });
* } catch (error) {
* console.error('Failed to update refund preference:', error);
* }
* ```
*/
async updateRefundPreference(options) {
const platform = Capacitor.getPlatform();