@layers/react-native
Version:
React Native SDK for Layers Analytics with SKAN and ATT support
992 lines (982 loc) • 29.2 kB
JavaScript
import { LayersClient } from "@layers/client";
import { NativeModules, Platform } from "react-native";
import { useEffect } from "react";
export * from "@layers/client"
//#region src/native/SKANModule.ts
/**
* Mock SKAN implementation
*/
var MockSKANModule = class {
conversionValue = 0;
isRegistered = false;
attributionData = null;
async isAvailable() {
return Platform.OS === "ios";
}
async getVersion() {
return "4.0";
}
async registerAppForAdNetworkAttribution() {
this.isRegistered = true;
this.attributionData = {
adNetworkId: "mock.network.com",
campaignId: "12345",
sourceAppId: "123456789",
timestamp: (/* @__PURE__ */ new Date()).toISOString()
};
console.log("[Mock SKAN] App registered for attribution");
}
async updateConversionValue(value) {
if (value >= 0 && value <= 63) {
this.conversionValue = Math.max(this.conversionValue, value);
console.log(`[Mock SKAN] Conversion value updated to: ${value}`);
} else console.warn(`[Mock SKAN] Invalid conversion value: ${value}. Must be 0-63.`);
}
async updatePostbackConversionValue(value, coarseValue, lockWindow) {
this.conversionValue = Math.max(this.conversionValue, value);
console.log(`[Mock SKAN] Postback value updated: ${value}, coarse: ${coarseValue}, lock: ${lockWindow}`);
}
async startImpression(signature) {
console.log(`[Mock SKAN] Started impression: ${signature}`);
}
async endImpression(signature) {
console.log(`[Mock SKAN] Ended impression: ${signature}`);
}
async getCurrentConversionValue() {
return this.conversionValue;
}
async getAttributionData() {
return this.attributionData;
}
};
/**
* Get SKAN module instance
*/
function getSKANModule() {
if (Platform.OS === "ios") {
const nativeModule = NativeModules.LayersSKAN;
if (nativeModule) return nativeModule;
}
return new MockSKANModule();
}
/**
* SKAN Manager - High-level wrapper for SKAdNetwork functionality with rule engine
*/
var SKANManager = class {
module;
onConversionUpdate;
currentPreset;
rules = [];
ruleEvaluationHistory = [];
constructor(onConversionUpdate) {
this.module = getSKANModule();
if (onConversionUpdate) this.onConversionUpdate = onConversionUpdate;
}
/**
* Initialize SKAN
*/
async initialize() {
if (await this.module.isAvailable()) await this.module.registerAppForAdNetworkAttribution();
}
/**
* Check if SKAN is supported
*/
async isSupported() {
return this.module.isAvailable();
}
/**
* Get SKAN version
*/
async getVersion() {
return this.module.getVersion();
}
/**
* Set conversion rules preset
*/
setPreset(preset) {
this.rules = this.getPresetConfig(preset).rules.sort((a, b) => (b.priority || 0) - (a.priority || 0));
this.currentPreset = preset;
console.log(`SKAN preset set to: ${preset} (${this.rules.length} rules)`);
}
/**
* Set custom conversion rules
*/
setCustomRules(rules) {
this.rules = rules.sort((a, b) => (b.priority || 0) - (a.priority || 0));
this.currentPreset = "custom";
console.log(`SKAN custom rules set: ${rules.length} rules`);
}
/**
* Process an event against conversion rules
*/
async processEvent(eventName, properties) {
if (!await this.isSupported()) return;
const ruleEvaluations = [];
let matchedRule;
for (const rule of this.rules) {
const evaluation = this.evaluateRule(rule, eventName, properties);
const ruleEvaluation = {
rule: rule.description || `${rule.eventName} (${rule.conversionValue})`,
matched: evaluation.matched
};
if (evaluation.reason) ruleEvaluation.reason = evaluation.reason;
ruleEvaluations.push(ruleEvaluation);
if (evaluation.matched && !matchedRule) {
matchedRule = rule;
break;
}
}
const historyEntry = {
event: eventName,
timestamp: (/* @__PURE__ */ new Date()).toISOString()
};
if (matchedRule?.description || matchedRule?.eventName) historyEntry.rule = matchedRule.description || matchedRule.eventName;
this.ruleEvaluationHistory.push(historyEntry);
if (this.ruleEvaluationHistory.length > 100) this.ruleEvaluationHistory = this.ruleEvaluationHistory.slice(-100);
if (matchedRule) try {
const previousValue = await this.module.getCurrentConversionValue() || 0;
if (matchedRule.conversionValue > previousValue) {
if (await this.getVersion() === "4.0" && matchedRule.coarseValue) await this.module.updatePostbackConversionValue(matchedRule.conversionValue, matchedRule.coarseValue, matchedRule.lockWindow);
else await this.module.updateConversionValue(matchedRule.conversionValue);
if (this.onConversionUpdate) this.onConversionUpdate({
event: eventName,
properties,
matchedRule,
previousValue,
newValue: matchedRule.conversionValue,
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
ruleEvaluations
});
console.log(`SKAN: Event ${eventName} matched rule '${matchedRule.description || matchedRule.eventName}', value: ${previousValue} → ${matchedRule.conversionValue}`);
} else console.log(`SKAN: Event ${eventName} matched rule but value ${matchedRule.conversionValue} not higher than current ${previousValue}`);
} catch (error) {
console.error("SKAN conversion value update failed:", error);
}
}
/**
* Evaluate if a rule matches an event
*/
evaluateRule(rule, eventName, properties) {
if (rule.eventName !== eventName) return {
matched: false,
reason: `Event name '${eventName}' != '${rule.eventName}'`
};
if (rule.conditions) for (const [key, expectedValue] of Object.entries(rule.conditions)) {
const actualValue = properties[key];
if (typeof expectedValue === "object" && expectedValue !== null) {
for (const [operator, comparisonValue] of Object.entries(expectedValue)) if (!this.evaluateCondition(actualValue, operator, comparisonValue).matched) return {
matched: false,
reason: `Condition ${key} ${operator} ${comparisonValue} failed (actual: ${actualValue})`
};
} else if (actualValue !== expectedValue) return {
matched: false,
reason: `Property ${key}: expected ${expectedValue}, got ${actualValue}`
};
}
return { matched: true };
}
/**
* Evaluate a single condition with operator
*/
evaluateCondition(actualValue, operator, expectedValue) {
const numericActual = Number(actualValue);
const numericExpected = Number(expectedValue);
switch (operator) {
case ">": return { matched: numericActual > numericExpected };
case ">=": return { matched: numericActual >= numericExpected };
case "<": return { matched: numericActual < numericExpected };
case "<=": return { matched: numericActual <= numericExpected };
case "==":
case "=": return { matched: actualValue === expectedValue };
case "!=": return { matched: actualValue !== expectedValue };
default:
console.warn(`Unknown condition operator: ${operator}`);
return { matched: false };
}
}
/**
* Get current conversion value
*/
async getCurrentValue() {
return this.module.getCurrentConversionValue();
}
/**
* Get attribution data
*/
async getAttributionData() {
return this.module.getAttributionData();
}
/**
* Get preset configuration
*/
getPresetConfig(preset) {
switch (preset) {
case "subscriptions": return {
name: "subscriptions",
description: "Optimized for subscription apps with trial-to-paid conversion tracking",
maxValue: 63,
rules: [
{
eventName: "app_open",
conversionValue: 1,
priority: 1,
description: "App opened"
},
{
eventName: "screen_view",
conditions: { screen_name: "onboarding_complete" },
conversionValue: 8,
priority: 3,
description: "Onboarding completed"
},
{
eventName: "trial_start",
conversionValue: 20,
priority: 5,
description: "Free trial started"
},
{
eventName: "purchase_success",
conditions: { revenue: { "<": 5 } },
conversionValue: 35,
priority: 7,
description: "Small purchase (<$5)"
},
{
eventName: "subscription_start",
conversionValue: 50,
priority: 10,
description: "Subscription started"
},
{
eventName: "subscription_renew",
conversionValue: 63,
priority: 15,
description: "Subscription renewed"
}
]
};
case "engagement": return {
name: "engagement",
description: "Optimized for engagement-driven apps focusing on user activity",
maxValue: 63,
rules: [
{
eventName: "app_open",
conversionValue: 1,
priority: 1,
description: "App opened"
},
{
eventName: "content_open",
conversionValue: 5,
priority: 2,
description: "Content viewed"
},
{
eventName: "search",
conversionValue: 12,
priority: 4,
description: "Search performed"
},
{
eventName: "bookmark_add",
conversionValue: 25,
priority: 6,
description: "Content bookmarked"
},
{
eventName: "app_open",
conditions: { session_count: { ">": 3 } },
conversionValue: 40,
priority: 8,
description: "Regular user (3+ sessions)"
},
{
eventName: "app_open",
conditions: { session_count: { ">": 10 } },
conversionValue: 63,
priority: 12,
description: "Power user (10+ sessions)"
}
]
};
case "iap": return {
name: "iap",
description: "Optimized for in-app purchase revenue tracking",
maxValue: 63,
rules: [
{
eventName: "app_open",
conversionValue: 1,
priority: 1,
description: "App opened"
},
{
eventName: "paywall_show",
conversionValue: 8,
priority: 2,
description: "Paywall shown"
},
{
eventName: "purchase_attempt",
conversionValue: 15,
priority: 3,
description: "Purchase attempted"
},
{
eventName: "purchase_success",
conditions: { revenue: { "<": 1 } },
conversionValue: 25,
priority: 5,
description: "Small purchase (<$1)"
},
{
eventName: "purchase_success",
conditions: { revenue: {
">=": 1,
"<": 10
} },
conversionValue: 40,
priority: 7,
description: "Medium purchase ($1-$10)"
},
{
eventName: "purchase_success",
conditions: { revenue: { ">=": 10 } },
conversionValue: 63,
priority: 10,
description: "Large purchase ($10+)"
}
]
};
default: throw new Error(`Unknown preset: ${preset}`);
}
}
/**
* Get current preset name
*/
getCurrentPreset() {
return this.currentPreset || null;
}
/**
* Get current rules
*/
getCurrentRules() {
return [...this.rules];
}
/**
* Get rule evaluation history
*/
getEvaluationHistory() {
return [...this.ruleEvaluationHistory];
}
/**
* Check if SKAN 4.0 features are available
*/
async supportsSKAN4() {
return await this.getVersion() === "4.0";
}
/**
* Get SKAN metrics for debugging
*/
async getSKANMetrics() {
const isSupported = await this.isSupported();
const version = await this.getVersion();
const currentValue = await this.getCurrentValue();
const attributionData = await this.getAttributionData();
return {
isSupported,
version,
currentValue,
currentPreset: this.currentPreset || null,
ruleCount: this.rules.length,
evaluationCount: this.ruleEvaluationHistory.length,
attributionData,
lastEvaluations: this.ruleEvaluationHistory.slice(-5)
};
}
};
const SKAN = new SKANManager();
//#endregion
//#region src/storage/identity.ts
const INSTALL_ID_KEY = "layers_install_id";
let asyncStorageInstance = null;
let asyncStorageLoadPromise = null;
let _pendingPromise = null;
async function getAsyncStorage$1() {
if (asyncStorageInstance) return asyncStorageInstance;
if (!asyncStorageLoadPromise) asyncStorageLoadPromise = import("@react-native-async-storage/async-storage").then((mod) => {
asyncStorageInstance = mod.default ?? mod;
return asyncStorageInstance;
}).catch(() => null);
return asyncStorageLoadPromise;
}
function getOrSetInstallId() {
if (_pendingPromise) return _pendingPromise;
_pendingPromise = (async () => {
try {
const AsyncStorage = await getAsyncStorage$1();
if (!AsyncStorage) {
console.warn("[Layers] AsyncStorage not found. Using temporary install_id. Identity will not persist across restarts.");
return generateUUID();
}
const existingId = await AsyncStorage.getItem(INSTALL_ID_KEY);
if (existingId) return existingId;
const newId = generateUUID();
await AsyncStorage.setItem(INSTALL_ID_KEY, newId);
return newId;
} catch (error) {
console.warn("[Layers] Failed to access storage for install_id", error);
return generateUUID();
}
})();
return _pendingPromise;
}
function generateUUID() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
const r = Math.random() * 16 | 0;
return (c === "x" ? r : r & 3 | 8).toString(16);
});
}
//#endregion
//#region src/storage/nativeQueueStorage.ts
const STORAGE_KEY_PREFIX = "layers_queue_";
let _AsyncStorage = void 0;
let _initPromise = null;
function getAsyncStorage() {
if (_AsyncStorage) return Promise.resolve(_AsyncStorage);
if (!_initPromise) _initPromise = import("@react-native-async-storage/async-storage").then((module) => {
_AsyncStorage = module.default || module;
return _AsyncStorage;
}).catch(() => {
return null;
});
return _initPromise;
}
const createMemoryQueueStorage = () => {
let snapshot = null;
return {
async load() {
return snapshot;
},
async save(nextSnapshot) {
snapshot = nextSnapshot;
},
async clear() {
snapshot = null;
}
};
};
function createReactNativeQueueStorage(appId) {
const storageKey = `${STORAGE_KEY_PREFIX}${appId}`;
const memoryFallback = createMemoryQueueStorage();
let useMemory = false;
let hasWarned = false;
const getStorageOrFallback = async () => {
if (useMemory) return null;
const AsyncStorage = await getAsyncStorage();
if (!AsyncStorage) {
useMemory = true;
if (!hasWarned) {
console.warn("[Layers] AsyncStorage not found. Falling back to in-memory storage. Events will not persist across restarts.");
hasWarned = true;
}
return null;
}
return AsyncStorage;
};
return {
async load() {
const AsyncStorage = await getStorageOrFallback();
if (!AsyncStorage) return memoryFallback.load();
try {
const value = await AsyncStorage.getItem(storageKey);
if (!value) return null;
return JSON.parse(value);
} catch (error) {
console.warn("[Layers] Failed to load queue from AsyncStorage", error);
return null;
}
},
async save(snapshot) {
const AsyncStorage = await getStorageOrFallback();
if (!AsyncStorage) return memoryFallback.save(snapshot);
try {
await AsyncStorage.setItem(storageKey, JSON.stringify(snapshot));
} catch (error) {
console.warn("[Layers] Failed to persist queue to AsyncStorage", error);
}
},
async clear() {
const AsyncStorage = await getStorageOrFallback();
if (!AsyncStorage) return memoryFallback.clear();
try {
await AsyncStorage.removeItem(storageKey);
} catch (error) {
console.warn("[Layers] Failed to clear queue storage", error);
}
}
};
}
//#endregion
//#region src/ExpoRouterIntegration.ts
/**
* Hook for tracking screen views with Expo Router.
*
* @param usePathname - The `usePathname` hook from `expo-router`.
* @param useGlobalSearchParams - The `useGlobalSearchParams` hook from `expo-router`.
*/
function useLayersExpoRouterTracking(usePathname, useGlobalSearchParams) {
const pathname = usePathname();
const params = useGlobalSearchParams();
useEffect(() => {
if (pathname) {
const properties = {};
Object.entries(params).forEach(([key, value]) => {
if (Array.isArray(value)) properties[key] = value.join(",");
else properties[key] = value;
});
Layers.screen(pathname, properties).catch((err) => {
console.warn("[Layers] Failed to track screen view:", err);
});
}
}, [pathname, JSON.stringify(params)]);
}
/**
* Component for tracking screen views with Expo Router.
* Render this in your root `_layout.tsx`.
*/
function LayersExpoRouter({ usePathname, useGlobalSearchParams }) {
useLayersExpoRouterTracking(usePathname, useGlobalSearchParams);
return null;
}
//#endregion
//#region src/integrations/Commerce.ts
const Commerce = { trackPurchase: async (transaction) => {
try {
await Layers.track("purchase_success", {
product_id: transaction.products.map((p) => p.productId).join(","),
transaction_id: transaction.transactionId,
store: transaction.store,
revenue: transaction.revenue,
currency: transaction.currency,
quantity: transaction.products.reduce((acc, p) => acc + (p.quantity ?? 1), 0),
items: transaction.products
});
} catch (e) {
console.warn("[Layers] Failed to track purchase:", e);
}
} };
//#endregion
//#region src/integrations/RevenueCat.ts
/**
* Safe wrapper for RevenueCat integration.
* Does not import 'react-native-purchases' to avoid runtime crashes.
*/
const RevenueCat = {
connect: (purchasesInstance) => {
if (_isConnected) return;
try {
if (!purchasesInstance || typeof purchasesInstance.addCustomerInfoUpdateListener !== "function" || typeof purchasesInstance.getCustomerInfo !== "function") {
console.warn("[Layers] RevenueCat.connect() was passed an invalid object. Expected the default export from \"react-native-purchases\".");
return;
}
const purchases = purchasesInstance;
purchases.addCustomerInfoUpdateListener((info) => {
handleCustomerInfoUpdate(info, false);
});
purchases.getCustomerInfo().then((info) => handleCustomerInfoUpdate(info, true)).catch(() => {});
_isConnected = true;
console.log("[Layers] RevenueCat integration connected.");
} catch (e) {
console.warn("[Layers] Failed to connect RevenueCat:", e);
}
},
trackPurchase: async (rcPackage) => {
try {
if (!rcPackage || !rcPackage.product) return;
const { product } = rcPackage;
const store = Platform.OS === "ios" ? "app_store" : Platform.OS === "android" ? "play_store" : "custom";
await Layers.track("purchase_success", {
product_id: product.identifier,
price: product.price,
currency: product.currencyCode,
store,
transaction_id: "",
source: "revenuecat"
});
} catch (e) {}
}
};
let _activeSubscriptionsSet = /* @__PURE__ */ new Set();
let _isInitialized = false;
let _isConnected = false;
function handleCustomerInfoUpdate(info, isInitialLoad) {
try {
const currentSubscriptions = new Set(info.activeSubscriptions || []);
if (isInitialLoad && _isInitialized) return;
if (!isInitialLoad && _isInitialized) {
for (const subId of currentSubscriptions) if (!_activeSubscriptionsSet.has(subId)) Layers.track("subscription_start", {
product_id: subId,
platform: "revenuecat"
}).catch((err) => {
console.warn("[Layers] Failed to track subscription start:", err);
});
}
_activeSubscriptionsSet = currentSubscriptions;
_isInitialized = true;
const userProps = { is_subscriber: currentSubscriptions.size > 0 };
if (info.originalAppUserId) userProps.revenuecat_original_app_user_id = info.originalAppUserId;
Layers.setUserProperties(userProps).catch((err) => {
console.warn("[Layers] Failed to sync user properties:", err);
});
} catch (e) {
console.warn("[Layers] Error processing RevenueCat update:", e);
}
}
//#endregion
//#region src/integrations/Superwall.ts
const Superwall = {
trackPresentation: async (paywallInfo) => {
try {
if (!paywallInfo) return;
await Layers.track("paywall_show", {
paywall_id: paywallInfo.identifier,
placement: paywallInfo.name || "unknown",
ab_test: paywallInfo.experiment ? {
id: paywallInfo.experiment.id,
variant: paywallInfo.experiment.variantId
} : void 0,
url: paywallInfo.url
});
} catch (e) {}
},
trackDismiss: async (paywallInfo) => {
try {
if (!paywallInfo) return;
await Layers.track("paywall_dismiss", {
paywall_id: paywallInfo.identifier,
reason: "user_close"
});
} catch (e) {}
}
};
//#endregion
//#region src/index.ts
const SUPPORTED_NETWORKS = [
"meta",
"tiktok",
"snap",
"google"
];
const DEFAULT_DEEP_LINK_CONFIG = {
urlSchemes: [],
universalLinkDomains: [],
deferredDeepLinkTimeout: 0,
attributionWindowDays: 0,
trackOrganicLinks: false
};
const EMPTY_CONNECTOR_STATUS = {
network: "meta",
enabled: false,
initialized: false,
hasSDK: false,
eventsSent: 0,
errors: 0
};
const EMPTY_CONNECTOR_METRICS = {
network: "meta",
totalEvents: 0,
successfulEvents: 0,
failedEvents: 0
};
const EMPTY_DEEP_LINK_METRICS = {
totalLinks: 0,
uniqueLinks: 0,
attributedInstalls: 0,
attributedSessions: 0,
deferredLinks: 0,
sourceBreakdown: {},
conversionRate: 0
};
const NOT_SUPPORTED_ATT_STATUS = "not_determined";
const NOT_SUPPORTED_SKAN_METRICS = {
isSupported: false,
version: "4.0",
currentValue: null,
currentPreset: null,
ruleCount: 0,
evaluationCount: 0,
attributionData: null,
lastEvaluations: []
};
var LayersRN = class {
client = null;
isInitialized = false;
deepLinkConfig = null;
skanManager = null;
async init(config) {
const installId = await getOrSetInstallId();
this.client = new LayersClient({
...config,
queueStorage: createReactNativeQueueStorage(config.appId),
queueOptions: {
flushIntervalMs: 1e3,
maxQueueSize: 50,
maxItemAgeMs: 6e4,
requestTimeoutMs: 1e4,
maxRetries: 1,
baseRetryDelayMs: 500,
maxRetryDelayMs: 1e3,
...config.queueOptions ?? {}
}
});
this.client.setDeviceInfo({
platform: Platform.OS === "ios" ? "ios" : Platform.OS === "android" ? "android" : "react-native",
os_version: typeof Platform.Version === "string" ? Platform.Version : String(Platform.Version),
install_id: installId
});
await this.client.init();
if (Platform.OS === "ios") await this.initializeSKAN();
this.isInitialized = true;
if (config.enableDebug) console.log("Layers initialized with SKAN support.");
}
/**
* Initialize SKAN and apply config from server
*/
async initializeSKAN() {
try {
this.skanManager = new SKANManager((data) => {
if (this.client?.getConfig().enableDebug) console.log("[Layers SKAN] Conversion updated:", {
event: data.event,
previousValue: data.previousValue,
newValue: data.newValue,
rule: data.matchedRule?.description
});
});
await this.skanManager.initialize();
const remoteConfig = await this.getRemoteConfig();
if (remoteConfig?.skan) await this.applySKANConfig(remoteConfig.skan);
} catch (error) {
console.warn("[Layers] SKAN initialization failed:", error);
}
}
/**
* Get remote config from client
*/
async getRemoteConfig() {
if (!this.client) return null;
try {
return this.client.remoteConfig ?? null;
} catch {
return null;
}
}
/**
* Apply SKAN configuration from server
*/
async applySKANConfig(skanConfig) {
if (!this.skanManager || !skanConfig.enabled) return;
try {
if (skanConfig.preset && [
"subscriptions",
"engagement",
"iap"
].includes(skanConfig.preset)) {
this.skanManager.setPreset(skanConfig.preset);
console.log(`[Layers SKAN] Preset applied: ${skanConfig.preset}`);
} else if (Array.isArray(skanConfig.customRules) && skanConfig.customRules.length > 0) {
this.skanManager.setCustomRules(skanConfig.customRules);
console.log(`[Layers SKAN] Custom rules applied: ${skanConfig.customRules.length} rules`);
}
} catch (error) {
console.warn("[Layers] Failed to apply SKAN config:", error);
}
}
getClient() {
return this.client;
}
isReady() {
return this.isInitialized;
}
async destroy() {
if (this.client) {
await this.client.flush();
this.client = null;
}
this.isInitialized = false;
}
async track(eventName, properties = {}) {
if (!this.client) throw new Error("Layers must be initialized before tracking events");
const eventProperties = {
platform: Platform.OS,
...properties
};
await this.client.track(eventName, eventProperties);
if (Platform.OS === "ios" && this.skanManager) this.skanManager.processEvent(eventName, eventProperties).catch((error) => {
if (this.client?.getConfig().enableDebug) console.warn("[Layers SKAN] Event processing failed:", error);
});
}
async screen(screenName, properties = {}) {
await this.track("screen_view", {
screen_name: screenName,
...properties
});
}
async setUserProperties(properties) {
if (!this.client) throw new Error("Layers must be initialized before setting user properties");
await this.client.setUserProperties(properties);
}
setAppUserId(appUserId) {
if (!this.client) throw new Error("Layers must be initialized before setting app user ID");
this.client.setAppUserId(appUserId);
}
clearAppUserId() {
if (!this.client) throw new Error("Layers must be initialized before clearing app user ID");
this.client.setAppUserId(void 0);
}
async setConsent(consent) {
if (!this.client) throw new Error("Layers must be initialized before setting consent");
await this.client.setConsent(consent);
}
async requestTracking(_options) {
console.warn("Layers ATT support is disabled in simplified mode.");
}
debug = {
showOverlay: (_options) => {
console.warn("Debug overlay is not available in simplified mode.");
},
hideOverlay: () => {},
isOverlayVisible: () => false,
getATTStatus: async () => NOT_SUPPORTED_ATT_STATUS,
getSKANMetrics: async () => {
if (Platform.OS !== "ios" || !this.skanManager) return NOT_SUPPORTED_SKAN_METRICS;
return this.skanManager.getSKANMetrics();
},
getDeepLinkMetrics: async () => EMPTY_DEEP_LINK_METRICS,
getConnectorStatuses: async () => this.createConnectorStatusMap(),
testConnectorConnection: async (_network) => false,
flushAllEvents: async () => {
if (this.client) await this.client.flush();
return this.createZeroCountMap();
}
};
connectors = {
configure: async (_network, _config) => false,
enable: async (_network) => false,
disable: async (_network) => false,
getStatus: async (network) => ({
...EMPTY_CONNECTOR_STATUS,
network
}),
getAllStatuses: async () => this.createConnectorStatusMap(),
getMetrics: async (network) => ({
...EMPTY_CONNECTOR_METRICS,
network
}),
testConnection: async (_network) => false,
flush: async () => this.createZeroCountMap()
};
deepLinks = {
configure: async (config) => {
this.deepLinkConfig = {
...DEFAULT_DEEP_LINK_CONFIG,
...this.deepLinkConfig ?? {},
...config
};
},
addListener: (_listener) => {
console.warn("Deep link listeners are not available in simplified mode.");
return () => void 0;
},
getMetrics: async () => EMPTY_DEEP_LINK_METRICS,
parseUrl: (url) => {
try {
const parsed = new URL(url);
const params = {};
parsed.searchParams.forEach((value, key) => {
params[key] = value;
});
const data = {
url,
scheme: parsed.protocol.replace(":", ""),
host: parsed.hostname,
path: parsed.pathname,
queryParams: params,
timestamp: Date.now()
};
if (parsed.hash) data.fragment = parsed.hash.slice(1);
return data;
} catch {
throw new Error(`Invalid deep link URL: ${url}`);
}
},
clearAttribution: async () => {}
};
att = {
request: async (_strategy) => {
console.warn("ATT request is not supported in simplified mode.");
return NOT_SUPPORTED_ATT_STATUS;
},
getStatus: async () => NOT_SUPPORTED_ATT_STATUS,
isSupported: async () => false,
setCustomPrompt: (_title, _message) => {
console.warn("ATT custom prompts are not supported in simplified mode.");
}
};
skan = {
setPreset: async (preset) => {
if (Platform.OS !== "ios") {
console.warn("[Layers SKAN] Only supported on iOS");
return;
}
if (!this.skanManager) {
console.warn("[Layers SKAN] Not initialized");
return;
}
this.skanManager.setPreset(preset);
},
setRules: async (rules) => {
if (Platform.OS !== "ios") {
console.warn("[Layers SKAN] Only supported on iOS");
return;
}
if (!this.skanManager) {
console.warn("[Layers SKAN] Not initialized");
return;
}
this.skanManager.setCustomRules(rules);
},
getCurrentConversionValue: async () => {
if (Platform.OS !== "ios" || !this.skanManager) return null;
return this.skanManager.getCurrentValue();
},
getMetrics: async () => {
if (Platform.OS !== "ios" || !this.skanManager) return NOT_SUPPORTED_SKAN_METRICS;
return this.skanManager.getSKANMetrics();
}
};
createConnectorStatusMap() {
const statuses = {};
for (const network of SUPPORTED_NETWORKS) statuses[network] = {
...EMPTY_CONNECTOR_STATUS,
network
};
return statuses;
}
createZeroCountMap() {
return {
meta: 0,
tiktok: 0,
snap: 0,
google: 0
};
}
};
const Layers = new LayersRN();
var src_default = Layers;
const DebugOverlay = (_props) => null;
//#endregion
export { Commerce, DebugOverlay, Layers, LayersExpoRouter, RevenueCat, Superwall, src_default as default, useLayersExpoRouterTracking };
//# sourceMappingURL=index.js.map