@virex-tech/paywallo-sdk 2.2.4 → 2.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +5 -3
- package/android/src/main/java/com/paywallo/sdk/PaywalloEnvModule.kt +31 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloFirebaseMessagingService.kt +33 -5
- package/android/src/main/java/com/paywallo/sdk/PaywalloNotificationsModule.kt +47 -30
- package/android/src/main/java/com/paywallo/sdk/PaywalloSdkPackage.kt +2 -1
- package/android/src/main/java/com/paywallo/sdk/PaywalloStoreKitModule.kt +7 -3
- package/android/src/main/java/com/paywallo/sdk/PaywalloWebViewEvent.kt +4 -2
- package/dist/index.d.mts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +628 -266
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +618 -257
- package/dist/index.mjs.map +1 -1
- package/ios/PaywalloEnv.m +8 -0
- package/ios/PaywalloEnv.swift +28 -0
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -814,9 +814,9 @@ var init_CampaignGateService = __esm({
|
|
|
814
814
|
}
|
|
815
815
|
}
|
|
816
816
|
getPreloadedCampaign(placement) {
|
|
817
|
-
const
|
|
818
|
-
if (!
|
|
819
|
-
const age = Date.now() -
|
|
817
|
+
const cached2 = this.preloadedCampaigns.get(placement);
|
|
818
|
+
if (!cached2) return null;
|
|
819
|
+
const age = Date.now() - cached2.preloadedAt;
|
|
820
820
|
if (age >= PRELOAD_CACHE_TTL_MS) {
|
|
821
821
|
this.preloadedCampaigns.delete(placement);
|
|
822
822
|
return null;
|
|
@@ -825,7 +825,7 @@ var init_CampaignGateService = __esm({
|
|
|
825
825
|
void this.preloadCampaign(this.apiClientRef, placement).catch(() => {
|
|
826
826
|
});
|
|
827
827
|
}
|
|
828
|
-
return
|
|
828
|
+
return cached2.campaign;
|
|
829
829
|
}
|
|
830
830
|
async preloadAllActive(apiClient, debug) {
|
|
831
831
|
this.apiClientRef = apiClient;
|
|
@@ -2008,13 +2008,21 @@ var init_NativePushBridge = __esm({
|
|
|
2008
2008
|
if (!raw || typeof raw !== "object") return null;
|
|
2009
2009
|
const r = raw;
|
|
2010
2010
|
const notification = r["notification"];
|
|
2011
|
+
const asString = (v) => typeof v === "string" ? v : void 0;
|
|
2011
2012
|
return {
|
|
2012
|
-
messageId:
|
|
2013
|
+
messageId: asString(r["messageId"]),
|
|
2013
2014
|
data: typeof r["data"] === "object" && r["data"] !== null ? r["data"] : void 0,
|
|
2014
2015
|
notification: notification && typeof notification === "object" ? {
|
|
2015
|
-
title:
|
|
2016
|
-
body:
|
|
2016
|
+
title: asString(notification["title"]),
|
|
2017
|
+
body: asString(notification["body"]),
|
|
2018
|
+
imageUrl: asString(notification["imageUrl"])
|
|
2017
2019
|
} : void 0,
|
|
2020
|
+
// Pass through top-level title/body/imageUrl emitted by the native modules
|
|
2021
|
+
// (both iOS and Android currently emit these at the top level rather than
|
|
2022
|
+
// nested under `notification`).
|
|
2023
|
+
title: asString(r["title"]),
|
|
2024
|
+
body: asString(r["body"]),
|
|
2025
|
+
imageUrl: asString(r["imageUrl"]),
|
|
2018
2026
|
sentTime: typeof r["sentTime"] === "number" ? r["sentTime"] : void 0
|
|
2019
2027
|
};
|
|
2020
2028
|
} catch {
|
|
@@ -2149,12 +2157,13 @@ function extractPayload(data) {
|
|
|
2149
2157
|
}
|
|
2150
2158
|
function setupForegroundHandler(deps, onReceive) {
|
|
2151
2159
|
const { bridge, tracker, debug } = deps;
|
|
2152
|
-
const
|
|
2160
|
+
const unsubscribe2 = bridge.onMessage(async (message) => {
|
|
2153
2161
|
const data = message.data ?? {};
|
|
2154
2162
|
const payload = extractPayload({
|
|
2155
2163
|
...data,
|
|
2156
|
-
title: data["title"] ?? message.notification?.title ?? "",
|
|
2157
|
-
body: data["body"] ?? message.notification?.body ?? ""
|
|
2164
|
+
title: data["title"] ?? message.notification?.title ?? message.title ?? "",
|
|
2165
|
+
body: data["body"] ?? message.notification?.body ?? message.body ?? "",
|
|
2166
|
+
imageUrl: data["imageUrl"] ?? message.notification?.imageUrl ?? message.imageUrl
|
|
2158
2167
|
});
|
|
2159
2168
|
if (debug) console.log("[Paywallo PUSH] foreground message received:", payload.notificationId);
|
|
2160
2169
|
onReceive(payload);
|
|
@@ -2166,7 +2175,7 @@ function setupForegroundHandler(deps, onReceive) {
|
|
|
2166
2175
|
messageId
|
|
2167
2176
|
});
|
|
2168
2177
|
});
|
|
2169
|
-
return
|
|
2178
|
+
return unsubscribe2;
|
|
2170
2179
|
}
|
|
2171
2180
|
var init_ForegroundHandler = __esm({
|
|
2172
2181
|
"src/domains/notifications/handlers/ForegroundHandler.ts"() {
|
|
@@ -2345,8 +2354,8 @@ async function getInitialNotificationPayload(bridge) {
|
|
|
2345
2354
|
notificationId: String(data["notificationId"] ?? data["notification_id"] ?? ""),
|
|
2346
2355
|
campaignId: String(data["campaignId"] ?? data["campaign_id"] ?? ""),
|
|
2347
2356
|
variantKey: data["variantKey"] != null ? String(data["variantKey"]) : void 0,
|
|
2348
|
-
title: String(data["title"] ?? message.notification?.title ?? ""),
|
|
2349
|
-
body: String(data["body"] ?? message.notification?.body ?? ""),
|
|
2357
|
+
title: String(data["title"] ?? message.notification?.title ?? message.title ?? ""),
|
|
2358
|
+
body: String(data["body"] ?? message.notification?.body ?? message.body ?? ""),
|
|
2350
2359
|
data
|
|
2351
2360
|
};
|
|
2352
2361
|
}
|
|
@@ -2361,8 +2370,8 @@ var init_NotificationHandlerSetup = __esm({
|
|
|
2361
2370
|
// src/core/version.ts
|
|
2362
2371
|
function resolveVersion() {
|
|
2363
2372
|
try {
|
|
2364
|
-
if ("2.2.
|
|
2365
|
-
return "2.2.
|
|
2373
|
+
if ("2.2.6") {
|
|
2374
|
+
return "2.2.6";
|
|
2366
2375
|
}
|
|
2367
2376
|
} catch {
|
|
2368
2377
|
}
|
|
@@ -2435,9 +2444,6 @@ function mapPermissionStatus(status) {
|
|
|
2435
2444
|
function detectPlatform2() {
|
|
2436
2445
|
return Platform6.OS === "ios" ? "ios" : "android";
|
|
2437
2446
|
}
|
|
2438
|
-
function detectEnvironment() {
|
|
2439
|
-
return typeof __DEV__ !== "undefined" && __DEV__ ? "sandbox" : "production";
|
|
2440
|
-
}
|
|
2441
2447
|
async function getAppVersion() {
|
|
2442
2448
|
try {
|
|
2443
2449
|
const { collectDeviceInfo: collectDeviceInfo2 } = await Promise.resolve().then(() => (init_deviceInfo(), deviceInfo_exports));
|
|
@@ -2477,7 +2483,7 @@ async function clearPersistedToken(secureStorage2) {
|
|
|
2477
2483
|
} catch {
|
|
2478
2484
|
}
|
|
2479
2485
|
}
|
|
2480
|
-
async function buildRegistration(token, getDeviceId, getDistinctId) {
|
|
2486
|
+
async function buildRegistration(token, getDeviceId, getDistinctId, environment) {
|
|
2481
2487
|
const appVersion = await getAppVersion();
|
|
2482
2488
|
return {
|
|
2483
2489
|
token,
|
|
@@ -2488,7 +2494,7 @@ async function buildRegistration(token, getDeviceId, getDistinctId) {
|
|
|
2488
2494
|
deviceId: getDeviceId?.() ?? void 0,
|
|
2489
2495
|
locale: await getLocale(),
|
|
2490
2496
|
timezone: getTimezone2(),
|
|
2491
|
-
environment
|
|
2497
|
+
environment
|
|
2492
2498
|
};
|
|
2493
2499
|
}
|
|
2494
2500
|
async function registerToken(deps, token) {
|
|
@@ -2497,7 +2503,8 @@ async function registerToken(deps, token) {
|
|
|
2497
2503
|
if (debug) console.log("[Paywallo PUSH] httpClient not ready, skipping token registration");
|
|
2498
2504
|
return;
|
|
2499
2505
|
}
|
|
2500
|
-
const
|
|
2506
|
+
const { environment } = deps;
|
|
2507
|
+
const registration = await buildRegistration(token, getDeviceId, getDistinctId, environment);
|
|
2501
2508
|
try {
|
|
2502
2509
|
await httpClient.post(
|
|
2503
2510
|
TOKEN_ENDPOINT,
|
|
@@ -2547,7 +2554,8 @@ async function registerAndPersistToken(deps, token) {
|
|
|
2547
2554
|
appKey: deps.appKey,
|
|
2548
2555
|
getDeviceId: deps.getDeviceId,
|
|
2549
2556
|
getDistinctId: deps.getDistinctId,
|
|
2550
|
-
debug: deps.debug
|
|
2557
|
+
debug: deps.debug,
|
|
2558
|
+
environment: deps.environment
|
|
2551
2559
|
},
|
|
2552
2560
|
token
|
|
2553
2561
|
);
|
|
@@ -2564,7 +2572,8 @@ function subscribeTokenRefresh(bridge, deps, onToken) {
|
|
|
2564
2572
|
appKey: deps.appKey,
|
|
2565
2573
|
getDeviceId: deps.getDeviceId,
|
|
2566
2574
|
getDistinctId: deps.getDistinctId,
|
|
2567
|
-
debug: deps.debug
|
|
2575
|
+
debug: deps.debug,
|
|
2576
|
+
environment: deps.environment
|
|
2568
2577
|
},
|
|
2569
2578
|
newToken
|
|
2570
2579
|
).catch(() => void 0);
|
|
@@ -2772,6 +2781,7 @@ var init_NotificationsManager = __esm({
|
|
|
2772
2781
|
this.getDistinctId = null;
|
|
2773
2782
|
this.appKey = null;
|
|
2774
2783
|
this.apiBaseUrl = null;
|
|
2784
|
+
this.environment = "production";
|
|
2775
2785
|
this.tokenRefreshCleanup = null;
|
|
2776
2786
|
this.foregroundCleanup = null;
|
|
2777
2787
|
this.pendingHandlersConfig = null;
|
|
@@ -2791,6 +2801,7 @@ var init_NotificationsManager = __esm({
|
|
|
2791
2801
|
this.secureStorage = deps.secureStorage;
|
|
2792
2802
|
this.getDeviceId = deps.getDeviceId;
|
|
2793
2803
|
if (deps.getDistinctId) this.getDistinctId = deps.getDistinctId;
|
|
2804
|
+
if (deps.environment) this.environment = deps.environment;
|
|
2794
2805
|
}
|
|
2795
2806
|
async initialize(config) {
|
|
2796
2807
|
this.applyConfig(config);
|
|
@@ -2833,7 +2844,8 @@ var init_NotificationsManager = __esm({
|
|
|
2833
2844
|
appKey: this.appKey,
|
|
2834
2845
|
getDeviceId: this.getDeviceId,
|
|
2835
2846
|
getDistinctId: this.getDistinctId,
|
|
2836
|
-
debug: this.debug
|
|
2847
|
+
debug: this.debug,
|
|
2848
|
+
environment: this.environment
|
|
2837
2849
|
};
|
|
2838
2850
|
}
|
|
2839
2851
|
async doRegisterToken(token) {
|
|
@@ -3905,6 +3917,32 @@ var init_IAPTransactionEmitter = __esm({
|
|
|
3905
3917
|
if (this.getDebug()) console.log("[Paywallo PURCHASE] transaction event emission failed", err);
|
|
3906
3918
|
}
|
|
3907
3919
|
}
|
|
3920
|
+
/**
|
|
3921
|
+
* Emits `checkout_started` quando o usuário inicia a compra (toca em comprar),
|
|
3922
|
+
* antes do StoreKit/Billing abrir — conta mesmo que a sheet nativa seja cancelada.
|
|
3923
|
+
* Vira InitiateCheckout no Meta/TikTok. Fire-and-forget — uma falha de tracking
|
|
3924
|
+
* nunca pode travar a compra.
|
|
3925
|
+
*/
|
|
3926
|
+
async emitCheckoutStarted(productId) {
|
|
3927
|
+
try {
|
|
3928
|
+
const apiClient = this.getApiClientFn();
|
|
3929
|
+
if (!apiClient) return;
|
|
3930
|
+
const distinctId = PaywalloClient.getDistinctId();
|
|
3931
|
+
if (!distinctId) return;
|
|
3932
|
+
const product = this.getProductFromCache(productId);
|
|
3933
|
+
const properties = {
|
|
3934
|
+
product_id: productId,
|
|
3935
|
+
amount: product?.priceValue ?? 0,
|
|
3936
|
+
currency: product?.currency ?? "USD"
|
|
3937
|
+
};
|
|
3938
|
+
await apiClient.trackEvent("checkout_started", distinctId, properties, void 0, {
|
|
3939
|
+
priority: "critical"
|
|
3940
|
+
});
|
|
3941
|
+
if (this.getDebug()) console.log("[Paywallo PURCHASE] emitted checkout_started", productId);
|
|
3942
|
+
} catch (err) {
|
|
3943
|
+
if (this.getDebug()) console.log("[Paywallo PURCHASE] checkout_started emission failed", err);
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3908
3946
|
/**
|
|
3909
3947
|
* Forwards a native transaction update to the EventBatcher.
|
|
3910
3948
|
* Public so tests can exercise the wiring without a fake NativeEventEmitter.
|
|
@@ -3927,9 +3965,9 @@ var init_IAPTransactionEmitter = __esm({
|
|
|
3927
3965
|
product_id: update.productId
|
|
3928
3966
|
};
|
|
3929
3967
|
if (update.type !== "canceled") {
|
|
3930
|
-
const
|
|
3931
|
-
const amount = update.amount ??
|
|
3932
|
-
const currency = update.currency ??
|
|
3968
|
+
const cached2 = this.getProductFromCache(update.productId);
|
|
3969
|
+
const amount = update.amount ?? cached2?.priceValue;
|
|
3970
|
+
const currency = update.currency ?? cached2?.currency;
|
|
3933
3971
|
if (typeof amount === "number" && amount > 0) properties.amount = amount;
|
|
3934
3972
|
if (currency) properties.currency = currency;
|
|
3935
3973
|
}
|
|
@@ -4108,10 +4146,10 @@ var init_OfflineQueueStorage = __esm({
|
|
|
4108
4146
|
init_NativeStorage();
|
|
4109
4147
|
init_QueueJournal();
|
|
4110
4148
|
OfflineQueueStorage = class {
|
|
4111
|
-
constructor(config, emit,
|
|
4149
|
+
constructor(config, emit, log2) {
|
|
4112
4150
|
this.config = config;
|
|
4113
4151
|
this.emit = emit;
|
|
4114
|
-
this.log =
|
|
4152
|
+
this.log = log2;
|
|
4115
4153
|
}
|
|
4116
4154
|
async loadFromStorage(queue) {
|
|
4117
4155
|
const { merged, recovered, hadJournal } = await mergeJournalIntoMain(
|
|
@@ -4718,12 +4756,25 @@ var init_QueueProcessor = __esm({
|
|
|
4718
4756
|
const freshHeaders = this.config.getFreshHeaders?.() ?? {};
|
|
4719
4757
|
const headers = { ...persistedHeaders, ...freshHeaders };
|
|
4720
4758
|
this.log("Sending event batch", { size: batch.length, batchIndex: Math.floor(i / BATCH_SIZE) });
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4759
|
+
let response;
|
|
4760
|
+
try {
|
|
4761
|
+
response = await this.httpClient.request("/sdk/ingest/batch", {
|
|
4762
|
+
method: "POST",
|
|
4763
|
+
body: { events: bodies },
|
|
4764
|
+
headers,
|
|
4765
|
+
skipRetry: false
|
|
4766
|
+
});
|
|
4767
|
+
} catch (error) {
|
|
4768
|
+
for (const item of batch) {
|
|
4769
|
+
retryIds.add(item.id);
|
|
4770
|
+
}
|
|
4771
|
+
console.error("[Paywallo:QUEUE] event batch network error \u2014 items will retry", { size: batch.length, error: error instanceof Error ? error.message : String(error) });
|
|
4772
|
+
this.log("Event batch threw network error - will retry", {
|
|
4773
|
+
size: batch.length,
|
|
4774
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4775
|
+
});
|
|
4776
|
+
continue;
|
|
4777
|
+
}
|
|
4727
4778
|
if (response.ok) {
|
|
4728
4779
|
for (const item of batch) {
|
|
4729
4780
|
await offlineQueue.removeItem(item.id);
|
|
@@ -4735,14 +4786,25 @@ var init_QueueProcessor = __esm({
|
|
|
4735
4786
|
await offlineQueue.removeItem(item.id);
|
|
4736
4787
|
failed++;
|
|
4737
4788
|
}
|
|
4789
|
+
const responseError = typeof response.data === "object" && response.data !== null && "error" in response.data ? response.data.error : void 0;
|
|
4790
|
+
const isDuplicate = typeof responseError === "string" && responseError.toUpperCase().includes("DUPLICATE");
|
|
4791
|
+
if (isDuplicate) {
|
|
4792
|
+
if (this.config.debug) {
|
|
4793
|
+
console.log("[Paywallo:QUEUE] event batch deduplicated by server \u2014 items dropped (benign)", { status: response.status, size: batch.length, error: responseError });
|
|
4794
|
+
}
|
|
4795
|
+
} else {
|
|
4796
|
+
console.error("[Paywallo:QUEUE] event batch failed PERMANENTLY (4xx) \u2014 items dropped", { status: response.status, size: batch.length, responseBody: typeof response.data === "string" ? response.data.slice(0, 1e3) : JSON.stringify(response.data).slice(0, 1e3), firstItemBody: typeof batch[0].body === "string" ? batch[0].body.slice(0, 1e3) : JSON.stringify(batch[0].body).slice(0, 1e3) });
|
|
4797
|
+
}
|
|
4738
4798
|
this.log("Event batch failed permanently (4xx) - removing from queue", {
|
|
4739
4799
|
status: response.status,
|
|
4740
|
-
size: batch.length
|
|
4800
|
+
size: batch.length,
|
|
4801
|
+
isDuplicate
|
|
4741
4802
|
});
|
|
4742
4803
|
} else {
|
|
4743
4804
|
for (const item of batch) {
|
|
4744
4805
|
retryIds.add(item.id);
|
|
4745
4806
|
}
|
|
4807
|
+
console.error("[Paywallo:QUEUE] event batch server error (5xx) \u2014 will retry", { status: response.status, size: batch.length });
|
|
4746
4808
|
this.log("Event batch failed with server error - will retry", {
|
|
4747
4809
|
status: response.status,
|
|
4748
4810
|
size: batch.length
|
|
@@ -4763,12 +4825,22 @@ var init_QueueProcessor = __esm({
|
|
|
4763
4825
|
});
|
|
4764
4826
|
const persistedHeaders = item.headers ?? {};
|
|
4765
4827
|
const freshHeaders = this.config.getFreshHeaders?.() ?? {};
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4828
|
+
let response;
|
|
4829
|
+
try {
|
|
4830
|
+
response = await this.httpClient.request(item.url, {
|
|
4831
|
+
method: item.method,
|
|
4832
|
+
body: item.body,
|
|
4833
|
+
headers: { ...persistedHeaders, ...freshHeaders },
|
|
4834
|
+
skipRetry: false
|
|
4835
|
+
});
|
|
4836
|
+
} catch (error) {
|
|
4837
|
+
console.error("[Paywallo:QUEUE] individual item network error \u2014 will retry", { id: item.id, url: item.url, error: error instanceof Error ? error.message : String(error) });
|
|
4838
|
+
this.log("Request threw network error - will retry", {
|
|
4839
|
+
id: item.id,
|
|
4840
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4841
|
+
});
|
|
4842
|
+
return { success: false, permanent: false };
|
|
4843
|
+
}
|
|
4772
4844
|
if (response.ok) {
|
|
4773
4845
|
this.log("Queue item processed successfully", { id: item.id });
|
|
4774
4846
|
return { success: true, permanent: false };
|
|
@@ -5059,6 +5131,7 @@ var init_IAPService = __esm({
|
|
|
5059
5131
|
this.purchaseInFlight = productId;
|
|
5060
5132
|
if (this.debug) console.log(`[Paywallo PURCHASE] starting ${productId}`);
|
|
5061
5133
|
try {
|
|
5134
|
+
void this.emitter.emitCheckoutStarted(productId);
|
|
5062
5135
|
const purchase = await nativeStoreKit.purchase(productId);
|
|
5063
5136
|
if (!purchase) {
|
|
5064
5137
|
if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} cancelled`);
|
|
@@ -5853,10 +5926,10 @@ var init_PlanService = __esm({
|
|
|
5853
5926
|
}
|
|
5854
5927
|
async getCurrentPlan(forceRefresh = false) {
|
|
5855
5928
|
if (!forceRefresh) {
|
|
5856
|
-
const
|
|
5857
|
-
if (
|
|
5858
|
-
const current =
|
|
5859
|
-
(p) => p.identifier ===
|
|
5929
|
+
const cached2 = this.cache.get();
|
|
5930
|
+
if (cached2 && !cached2.isStale) {
|
|
5931
|
+
const current = cached2.data.plans.find(
|
|
5932
|
+
(p) => p.identifier === cached2.data.currentIdentifier
|
|
5860
5933
|
);
|
|
5861
5934
|
return current ?? null;
|
|
5862
5935
|
}
|
|
@@ -5883,8 +5956,8 @@ var init_PlanService = __esm({
|
|
|
5883
5956
|
}
|
|
5884
5957
|
async getAllPlans(forceRefresh = false) {
|
|
5885
5958
|
if (!forceRefresh) {
|
|
5886
|
-
const
|
|
5887
|
-
if (
|
|
5959
|
+
const cached2 = this.cache.get();
|
|
5960
|
+
if (cached2 && !cached2.isStale) return cached2.data;
|
|
5888
5961
|
}
|
|
5889
5962
|
if (!this.cache.get()) {
|
|
5890
5963
|
try {
|
|
@@ -6002,6 +6075,163 @@ var init_analytics = __esm({
|
|
|
6002
6075
|
}
|
|
6003
6076
|
});
|
|
6004
6077
|
|
|
6078
|
+
// src/core/events/schemas.ts
|
|
6079
|
+
import { z } from "zod";
|
|
6080
|
+
var lifecycleEventSchema, identifyEventSchema, paywallEventSchema, transactionEventSchema, onboardingEventSchema, notificationEventSchema, customEventSchema, EVENT_SCHEMAS;
|
|
6081
|
+
var init_schemas = __esm({
|
|
6082
|
+
"src/core/events/schemas.ts"() {
|
|
6083
|
+
"use strict";
|
|
6084
|
+
lifecycleEventSchema = z.object({
|
|
6085
|
+
type: z.enum([
|
|
6086
|
+
"install",
|
|
6087
|
+
"cold_start",
|
|
6088
|
+
"foreground",
|
|
6089
|
+
"background",
|
|
6090
|
+
"session_start",
|
|
6091
|
+
"session_end"
|
|
6092
|
+
]),
|
|
6093
|
+
session_id: z.string().optional(),
|
|
6094
|
+
duration_s: z.number().nonnegative().optional(),
|
|
6095
|
+
first_ever: z.boolean().optional(),
|
|
6096
|
+
// Contexto leve transportado em lifecycle (útil pra cold_start/install)
|
|
6097
|
+
app_version: z.string().optional(),
|
|
6098
|
+
device_type: z.enum(["ios", "android"]).optional(),
|
|
6099
|
+
os_version: z.string().optional(),
|
|
6100
|
+
// Timestamps opcionais — o EventBatcher injeta `timestamp` no envelope
|
|
6101
|
+
started_at: z.string().optional(),
|
|
6102
|
+
ended_at: z.string().optional()
|
|
6103
|
+
}).strict();
|
|
6104
|
+
identifyEventSchema = z.object({
|
|
6105
|
+
distinct_id: z.string().min(1),
|
|
6106
|
+
email: z.email().optional(),
|
|
6107
|
+
traits: z.record(z.string(), z.unknown()).optional(),
|
|
6108
|
+
attribution: z.record(z.string(), z.unknown()).optional(),
|
|
6109
|
+
device: z.record(z.string(), z.unknown()).optional()
|
|
6110
|
+
}).strict();
|
|
6111
|
+
paywallEventSchema = z.object({
|
|
6112
|
+
type: z.enum(["viewed", "closed", "purchased"]),
|
|
6113
|
+
paywall_id: z.string().min(1),
|
|
6114
|
+
placement: z.string().optional(),
|
|
6115
|
+
variant_id: z.string().optional(),
|
|
6116
|
+
variant_key: z.string().optional(),
|
|
6117
|
+
campaign_id: z.string().optional(),
|
|
6118
|
+
// `closed`-specific
|
|
6119
|
+
closed_at: z.string().optional(),
|
|
6120
|
+
duration_s: z.number().nonnegative().optional(),
|
|
6121
|
+
close_reason: z.enum(["dismiss", "cta", "purchase", "error", "timeout"]).optional(),
|
|
6122
|
+
scroll_depth: z.number().min(0).max(1).optional(),
|
|
6123
|
+
// `purchased`-specific (mínimo — transaction canônica leva detalhes)
|
|
6124
|
+
product_id: z.string().optional()
|
|
6125
|
+
}).strict();
|
|
6126
|
+
transactionEventSchema = z.object({
|
|
6127
|
+
type: z.enum([
|
|
6128
|
+
"completed",
|
|
6129
|
+
"failed",
|
|
6130
|
+
"refunded",
|
|
6131
|
+
"renewed",
|
|
6132
|
+
"canceled",
|
|
6133
|
+
"expired"
|
|
6134
|
+
]),
|
|
6135
|
+
tx_id: z.string().min(1),
|
|
6136
|
+
amount: z.number().optional(),
|
|
6137
|
+
currency: z.string().length(3).optional(),
|
|
6138
|
+
product_id: z.string().optional(),
|
|
6139
|
+
subscription_id: z.string().optional(),
|
|
6140
|
+
paywall_id: z.string().optional(),
|
|
6141
|
+
variant_id: z.string().optional(),
|
|
6142
|
+
reason: z.string().optional()
|
|
6143
|
+
}).strict();
|
|
6144
|
+
onboardingEventSchema = z.object({
|
|
6145
|
+
type: z.enum(["step", "complete", "drop"]),
|
|
6146
|
+
step_index: z.number().int().nonnegative().optional(),
|
|
6147
|
+
step_name: z.string().optional(),
|
|
6148
|
+
variant_key: z.string().optional(),
|
|
6149
|
+
time_on_prev_s: z.number().nonnegative().optional()
|
|
6150
|
+
}).strict();
|
|
6151
|
+
notificationEventSchema = z.object({
|
|
6152
|
+
type: z.enum(["delivered", "displayed", "clicked", "dismissed"]),
|
|
6153
|
+
notification_id: z.string().optional(),
|
|
6154
|
+
campaign_id: z.string().optional(),
|
|
6155
|
+
variant_key: z.string().nullable().optional(),
|
|
6156
|
+
message_id: z.string().optional(),
|
|
6157
|
+
device_id: z.string().optional(),
|
|
6158
|
+
push_platform: z.enum(["ios", "android"]).optional(),
|
|
6159
|
+
timezone: z.string().optional(),
|
|
6160
|
+
failure_reason: z.string().optional(),
|
|
6161
|
+
app_user_id: z.string().optional()
|
|
6162
|
+
}).strict();
|
|
6163
|
+
customEventSchema = z.object({
|
|
6164
|
+
name: z.string().min(1).regex(
|
|
6165
|
+
/^(\$[a-zA-Z0-9_]+|[a-z][a-z0-9_]*)$/,
|
|
6166
|
+
"custom event name must be snake_case or $reserved"
|
|
6167
|
+
),
|
|
6168
|
+
props: z.record(z.string(), z.unknown()).optional()
|
|
6169
|
+
}).strict();
|
|
6170
|
+
EVENT_SCHEMAS = {
|
|
6171
|
+
lifecycle: lifecycleEventSchema,
|
|
6172
|
+
identify: identifyEventSchema,
|
|
6173
|
+
paywall: paywallEventSchema,
|
|
6174
|
+
transaction: transactionEventSchema,
|
|
6175
|
+
onboarding: onboardingEventSchema,
|
|
6176
|
+
notification: notificationEventSchema,
|
|
6177
|
+
custom: customEventSchema
|
|
6178
|
+
};
|
|
6179
|
+
}
|
|
6180
|
+
});
|
|
6181
|
+
|
|
6182
|
+
// src/core/events/eventFamilies.ts
|
|
6183
|
+
function isDeprecatedEvent(eventName) {
|
|
6184
|
+
return DEPRECATED_EVENT_NAMES.includes(eventName);
|
|
6185
|
+
}
|
|
6186
|
+
function isCanonicalFamily(name) {
|
|
6187
|
+
return CANONICAL_FAMILIES.includes(name);
|
|
6188
|
+
}
|
|
6189
|
+
function detectFamily(eventName) {
|
|
6190
|
+
if (isCanonicalFamily(eventName) && eventName !== "custom") {
|
|
6191
|
+
return eventName;
|
|
6192
|
+
}
|
|
6193
|
+
return "custom";
|
|
6194
|
+
}
|
|
6195
|
+
function validateEvent(eventName, properties) {
|
|
6196
|
+
const family = detectFamily(eventName);
|
|
6197
|
+
const schema = EVENT_SCHEMAS[family];
|
|
6198
|
+
const input = family === "custom" ? { name: eventName, ...properties ? { props: properties } : {} } : properties ?? {};
|
|
6199
|
+
const parsed = schema.safeParse(input);
|
|
6200
|
+
if (parsed.success) {
|
|
6201
|
+
return { ok: true, family, data: parsed.data };
|
|
6202
|
+
}
|
|
6203
|
+
return { ok: false, family, error: parsed.error };
|
|
6204
|
+
}
|
|
6205
|
+
function formatValidationError(error) {
|
|
6206
|
+
return error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
6207
|
+
}
|
|
6208
|
+
var CANONICAL_FAMILIES, DEPRECATED_EVENT_NAMES;
|
|
6209
|
+
var init_eventFamilies = __esm({
|
|
6210
|
+
"src/core/events/eventFamilies.ts"() {
|
|
6211
|
+
"use strict";
|
|
6212
|
+
init_schemas();
|
|
6213
|
+
CANONICAL_FAMILIES = [
|
|
6214
|
+
"lifecycle",
|
|
6215
|
+
"identify",
|
|
6216
|
+
"paywall",
|
|
6217
|
+
"transaction",
|
|
6218
|
+
"onboarding",
|
|
6219
|
+
"notification",
|
|
6220
|
+
"custom"
|
|
6221
|
+
];
|
|
6222
|
+
DEPRECATED_EVENT_NAMES = [
|
|
6223
|
+
"$paywall_purchased",
|
|
6224
|
+
"$paywall_product_selected",
|
|
6225
|
+
"$core_action",
|
|
6226
|
+
"$campaign_impression",
|
|
6227
|
+
"$app_open",
|
|
6228
|
+
"$app_background",
|
|
6229
|
+
"$app_foreground",
|
|
6230
|
+
"session.end"
|
|
6231
|
+
];
|
|
6232
|
+
}
|
|
6233
|
+
});
|
|
6234
|
+
|
|
6005
6235
|
// src/core/PaywalloAnalytics.ts
|
|
6006
6236
|
function isValidEventName(name) {
|
|
6007
6237
|
if (name.startsWith("$")) return /^\$[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
|
|
@@ -6010,7 +6240,7 @@ function isValidEventName(name) {
|
|
|
6010
6240
|
async function trackEvent(apiClient, state, eventName, options) {
|
|
6011
6241
|
const distinctId = identityManager.getDistinctId();
|
|
6012
6242
|
if (!distinctId) {
|
|
6013
|
-
|
|
6243
|
+
console.error("[Paywallo:DROP] event dropped \u2014 no distinctId available yet", { eventName });
|
|
6014
6244
|
return;
|
|
6015
6245
|
}
|
|
6016
6246
|
if (!isValidEventName(eventName)) {
|
|
@@ -6019,10 +6249,14 @@ async function trackEvent(apiClient, state, eventName, options) {
|
|
|
6019
6249
|
}
|
|
6020
6250
|
if (state.config?.debug && eventName.startsWith("$purchase")) console.log(`[Paywallo TRACK] ${eventName}`, options?.properties ?? {});
|
|
6021
6251
|
const sessionId = sessionManager.getSessionId();
|
|
6252
|
+
const family = detectFamily(eventName);
|
|
6253
|
+
const isCustomFamily = family === "custom";
|
|
6254
|
+
const identityProps = isCustomFamily ? identityManager.getProperties() : {};
|
|
6255
|
+
const sessionProp = isCustomFamily && sessionId ? { sessionId } : {};
|
|
6022
6256
|
await apiClient.trackEvent(
|
|
6023
6257
|
eventName,
|
|
6024
6258
|
distinctId,
|
|
6025
|
-
{ ...
|
|
6259
|
+
{ ...identityProps, ...options?.properties, ...sessionProp },
|
|
6026
6260
|
options?.timestamp,
|
|
6027
6261
|
options?.priority ? { priority: options.priority } : void 0
|
|
6028
6262
|
);
|
|
@@ -6052,6 +6286,7 @@ var init_PaywalloAnalytics = __esm({
|
|
|
6052
6286
|
init_identity();
|
|
6053
6287
|
init_session();
|
|
6054
6288
|
init_ClientError();
|
|
6289
|
+
init_eventFamilies();
|
|
6055
6290
|
}
|
|
6056
6291
|
});
|
|
6057
6292
|
|
|
@@ -6316,49 +6551,49 @@ var init_SubscriptionCache = __esm({
|
|
|
6316
6551
|
await this.storage.remove(keyFor(distinctId));
|
|
6317
6552
|
return null;
|
|
6318
6553
|
}
|
|
6319
|
-
const
|
|
6320
|
-
if (
|
|
6321
|
-
|
|
6554
|
+
const cached2 = parsed;
|
|
6555
|
+
if (cached2.data.subscription?.expiresAt) {
|
|
6556
|
+
cached2.data.subscription.expiresAt = new Date(cached2.data.subscription.expiresAt);
|
|
6322
6557
|
}
|
|
6323
|
-
if (
|
|
6324
|
-
|
|
6325
|
-
|
|
6558
|
+
if (cached2.data.subscription?.originalPurchaseDate) {
|
|
6559
|
+
cached2.data.subscription.originalPurchaseDate = new Date(
|
|
6560
|
+
cached2.data.subscription.originalPurchaseDate
|
|
6326
6561
|
);
|
|
6327
6562
|
}
|
|
6328
|
-
if (
|
|
6329
|
-
|
|
6330
|
-
|
|
6563
|
+
if (cached2.data.subscription?.latestPurchaseDate) {
|
|
6564
|
+
cached2.data.subscription.latestPurchaseDate = new Date(
|
|
6565
|
+
cached2.data.subscription.latestPurchaseDate
|
|
6331
6566
|
);
|
|
6332
6567
|
}
|
|
6333
|
-
if (
|
|
6334
|
-
|
|
6335
|
-
|
|
6568
|
+
if (cached2.data.subscription?.cancellationDate) {
|
|
6569
|
+
cached2.data.subscription.cancellationDate = new Date(
|
|
6570
|
+
cached2.data.subscription.cancellationDate
|
|
6336
6571
|
);
|
|
6337
6572
|
}
|
|
6338
|
-
if (
|
|
6339
|
-
|
|
6340
|
-
|
|
6573
|
+
if (cached2.data.subscription?.gracePeriodExpiresAt) {
|
|
6574
|
+
cached2.data.subscription.gracePeriodExpiresAt = new Date(
|
|
6575
|
+
cached2.data.subscription.gracePeriodExpiresAt
|
|
6341
6576
|
);
|
|
6342
6577
|
}
|
|
6343
|
-
|
|
6344
|
-
if (!
|
|
6345
|
-
this.memoryCache.set(distinctId,
|
|
6578
|
+
cached2.isStale = this.isExpired(cached2);
|
|
6579
|
+
if (!cached2.isStale) {
|
|
6580
|
+
this.memoryCache.set(distinctId, cached2);
|
|
6346
6581
|
}
|
|
6347
|
-
return
|
|
6582
|
+
return cached2;
|
|
6348
6583
|
} catch {
|
|
6349
6584
|
return null;
|
|
6350
6585
|
}
|
|
6351
6586
|
}
|
|
6352
6587
|
async set(distinctId, data) {
|
|
6353
6588
|
if (!distinctId) return;
|
|
6354
|
-
const
|
|
6589
|
+
const cached2 = {
|
|
6355
6590
|
data,
|
|
6356
6591
|
cachedAt: Date.now(),
|
|
6357
6592
|
isStale: false
|
|
6358
6593
|
};
|
|
6359
|
-
this.memoryCache.set(distinctId,
|
|
6594
|
+
this.memoryCache.set(distinctId, cached2);
|
|
6360
6595
|
try {
|
|
6361
|
-
await this.storage.set(keyFor(distinctId), JSON.stringify(
|
|
6596
|
+
await this.storage.set(keyFor(distinctId), JSON.stringify(cached2));
|
|
6362
6597
|
await this.addToIndex(distinctId);
|
|
6363
6598
|
} catch {
|
|
6364
6599
|
}
|
|
@@ -6402,8 +6637,8 @@ var init_SubscriptionCache = __esm({
|
|
|
6402
6637
|
...allKeys.map((k) => this.storage.remove(keyFor(k)))
|
|
6403
6638
|
]);
|
|
6404
6639
|
}
|
|
6405
|
-
isExpired(
|
|
6406
|
-
return Date.now() -
|
|
6640
|
+
isExpired(cached2) {
|
|
6641
|
+
return Date.now() - cached2.cachedAt > this.ttl;
|
|
6407
6642
|
}
|
|
6408
6643
|
setTTL(ttl) {
|
|
6409
6644
|
this.ttl = ttl;
|
|
@@ -6459,9 +6694,9 @@ var init_SubscriptionManager = __esm({
|
|
|
6459
6694
|
return this.getEmptyStatus();
|
|
6460
6695
|
}
|
|
6461
6696
|
if (!forceRefresh) {
|
|
6462
|
-
const
|
|
6463
|
-
if (
|
|
6464
|
-
return
|
|
6697
|
+
const cached2 = await this.cache.get(this.cacheKey());
|
|
6698
|
+
if (cached2 && !cached2.isStale) {
|
|
6699
|
+
return cached2.data;
|
|
6465
6700
|
}
|
|
6466
6701
|
}
|
|
6467
6702
|
try {
|
|
@@ -6471,9 +6706,9 @@ var init_SubscriptionManager = __esm({
|
|
|
6471
6706
|
return status;
|
|
6472
6707
|
} catch (error) {
|
|
6473
6708
|
this.log("Failed to fetch subscription status", error);
|
|
6474
|
-
const
|
|
6475
|
-
if (
|
|
6476
|
-
return
|
|
6709
|
+
const cached2 = await this.cache.get(this.cacheKey());
|
|
6710
|
+
if (cached2) {
|
|
6711
|
+
return cached2.data;
|
|
6477
6712
|
}
|
|
6478
6713
|
return this.getEmptyStatus();
|
|
6479
6714
|
}
|
|
@@ -6888,163 +7123,6 @@ var init_ApiClientQueue = __esm({
|
|
|
6888
7123
|
}
|
|
6889
7124
|
});
|
|
6890
7125
|
|
|
6891
|
-
// src/core/events/schemas.ts
|
|
6892
|
-
import { z } from "zod";
|
|
6893
|
-
var lifecycleEventSchema, identifyEventSchema, paywallEventSchema, transactionEventSchema, onboardingEventSchema, notificationEventSchema, customEventSchema, EVENT_SCHEMAS;
|
|
6894
|
-
var init_schemas = __esm({
|
|
6895
|
-
"src/core/events/schemas.ts"() {
|
|
6896
|
-
"use strict";
|
|
6897
|
-
lifecycleEventSchema = z.object({
|
|
6898
|
-
type: z.enum([
|
|
6899
|
-
"install",
|
|
6900
|
-
"cold_start",
|
|
6901
|
-
"foreground",
|
|
6902
|
-
"background",
|
|
6903
|
-
"session_start",
|
|
6904
|
-
"session_end"
|
|
6905
|
-
]),
|
|
6906
|
-
session_id: z.string().optional(),
|
|
6907
|
-
duration_s: z.number().nonnegative().optional(),
|
|
6908
|
-
first_ever: z.boolean().optional(),
|
|
6909
|
-
// Contexto leve transportado em lifecycle (útil pra cold_start/install)
|
|
6910
|
-
app_version: z.string().optional(),
|
|
6911
|
-
device_type: z.enum(["ios", "android"]).optional(),
|
|
6912
|
-
os_version: z.string().optional(),
|
|
6913
|
-
// Timestamps opcionais — o EventBatcher injeta `timestamp` no envelope
|
|
6914
|
-
started_at: z.string().optional(),
|
|
6915
|
-
ended_at: z.string().optional()
|
|
6916
|
-
}).strict();
|
|
6917
|
-
identifyEventSchema = z.object({
|
|
6918
|
-
distinct_id: z.string().min(1),
|
|
6919
|
-
email: z.email().optional(),
|
|
6920
|
-
traits: z.record(z.string(), z.unknown()).optional(),
|
|
6921
|
-
attribution: z.record(z.string(), z.unknown()).optional(),
|
|
6922
|
-
device: z.record(z.string(), z.unknown()).optional()
|
|
6923
|
-
}).strict();
|
|
6924
|
-
paywallEventSchema = z.object({
|
|
6925
|
-
type: z.enum(["viewed", "closed", "purchased"]),
|
|
6926
|
-
paywall_id: z.string().min(1),
|
|
6927
|
-
placement: z.string().optional(),
|
|
6928
|
-
variant_id: z.string().optional(),
|
|
6929
|
-
variant_key: z.string().optional(),
|
|
6930
|
-
campaign_id: z.string().optional(),
|
|
6931
|
-
// `closed`-specific
|
|
6932
|
-
closed_at: z.string().optional(),
|
|
6933
|
-
duration_s: z.number().nonnegative().optional(),
|
|
6934
|
-
close_reason: z.enum(["dismiss", "cta", "purchase", "error", "timeout"]).optional(),
|
|
6935
|
-
scroll_depth: z.number().min(0).max(1).optional(),
|
|
6936
|
-
// `purchased`-specific (mínimo — transaction canônica leva detalhes)
|
|
6937
|
-
product_id: z.string().optional()
|
|
6938
|
-
}).strict();
|
|
6939
|
-
transactionEventSchema = z.object({
|
|
6940
|
-
type: z.enum([
|
|
6941
|
-
"completed",
|
|
6942
|
-
"failed",
|
|
6943
|
-
"refunded",
|
|
6944
|
-
"renewed",
|
|
6945
|
-
"canceled",
|
|
6946
|
-
"expired"
|
|
6947
|
-
]),
|
|
6948
|
-
tx_id: z.string().min(1),
|
|
6949
|
-
amount: z.number().optional(),
|
|
6950
|
-
currency: z.string().length(3).optional(),
|
|
6951
|
-
product_id: z.string().optional(),
|
|
6952
|
-
subscription_id: z.string().optional(),
|
|
6953
|
-
paywall_id: z.string().optional(),
|
|
6954
|
-
variant_id: z.string().optional(),
|
|
6955
|
-
reason: z.string().optional()
|
|
6956
|
-
}).strict();
|
|
6957
|
-
onboardingEventSchema = z.object({
|
|
6958
|
-
type: z.enum(["step", "complete", "drop"]),
|
|
6959
|
-
step_index: z.number().int().nonnegative().optional(),
|
|
6960
|
-
step_name: z.string().optional(),
|
|
6961
|
-
variant_key: z.string().optional(),
|
|
6962
|
-
time_on_prev_s: z.number().nonnegative().optional()
|
|
6963
|
-
}).strict();
|
|
6964
|
-
notificationEventSchema = z.object({
|
|
6965
|
-
type: z.enum(["delivered", "displayed", "clicked", "dismissed"]),
|
|
6966
|
-
notification_id: z.string().optional(),
|
|
6967
|
-
campaign_id: z.string().optional(),
|
|
6968
|
-
variant_key: z.string().nullable().optional(),
|
|
6969
|
-
message_id: z.string().optional(),
|
|
6970
|
-
device_id: z.string().optional(),
|
|
6971
|
-
push_platform: z.enum(["ios", "android"]).optional(),
|
|
6972
|
-
timezone: z.string().optional(),
|
|
6973
|
-
failure_reason: z.string().optional(),
|
|
6974
|
-
app_user_id: z.string().optional()
|
|
6975
|
-
}).strict();
|
|
6976
|
-
customEventSchema = z.object({
|
|
6977
|
-
name: z.string().min(1).regex(
|
|
6978
|
-
/^(\$[a-zA-Z0-9_]+|[a-z][a-z0-9_]*)$/,
|
|
6979
|
-
"custom event name must be snake_case or $reserved"
|
|
6980
|
-
),
|
|
6981
|
-
props: z.record(z.string(), z.unknown()).optional()
|
|
6982
|
-
}).strict();
|
|
6983
|
-
EVENT_SCHEMAS = {
|
|
6984
|
-
lifecycle: lifecycleEventSchema,
|
|
6985
|
-
identify: identifyEventSchema,
|
|
6986
|
-
paywall: paywallEventSchema,
|
|
6987
|
-
transaction: transactionEventSchema,
|
|
6988
|
-
onboarding: onboardingEventSchema,
|
|
6989
|
-
notification: notificationEventSchema,
|
|
6990
|
-
custom: customEventSchema
|
|
6991
|
-
};
|
|
6992
|
-
}
|
|
6993
|
-
});
|
|
6994
|
-
|
|
6995
|
-
// src/core/events/eventFamilies.ts
|
|
6996
|
-
function isDeprecatedEvent(eventName) {
|
|
6997
|
-
return DEPRECATED_EVENT_NAMES.includes(eventName);
|
|
6998
|
-
}
|
|
6999
|
-
function isCanonicalFamily(name) {
|
|
7000
|
-
return CANONICAL_FAMILIES.includes(name);
|
|
7001
|
-
}
|
|
7002
|
-
function detectFamily(eventName) {
|
|
7003
|
-
if (isCanonicalFamily(eventName) && eventName !== "custom") {
|
|
7004
|
-
return eventName;
|
|
7005
|
-
}
|
|
7006
|
-
return "custom";
|
|
7007
|
-
}
|
|
7008
|
-
function validateEvent(eventName, properties) {
|
|
7009
|
-
const family = detectFamily(eventName);
|
|
7010
|
-
const schema = EVENT_SCHEMAS[family];
|
|
7011
|
-
const input = family === "custom" ? { name: eventName, ...properties ? { props: properties } : {} } : properties ?? {};
|
|
7012
|
-
const parsed = schema.safeParse(input);
|
|
7013
|
-
if (parsed.success) {
|
|
7014
|
-
return { ok: true, family, data: parsed.data };
|
|
7015
|
-
}
|
|
7016
|
-
return { ok: false, family, error: parsed.error };
|
|
7017
|
-
}
|
|
7018
|
-
function formatValidationError(error) {
|
|
7019
|
-
return error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
7020
|
-
}
|
|
7021
|
-
var CANONICAL_FAMILIES, DEPRECATED_EVENT_NAMES;
|
|
7022
|
-
var init_eventFamilies = __esm({
|
|
7023
|
-
"src/core/events/eventFamilies.ts"() {
|
|
7024
|
-
"use strict";
|
|
7025
|
-
init_schemas();
|
|
7026
|
-
CANONICAL_FAMILIES = [
|
|
7027
|
-
"lifecycle",
|
|
7028
|
-
"identify",
|
|
7029
|
-
"paywall",
|
|
7030
|
-
"transaction",
|
|
7031
|
-
"onboarding",
|
|
7032
|
-
"notification",
|
|
7033
|
-
"custom"
|
|
7034
|
-
];
|
|
7035
|
-
DEPRECATED_EVENT_NAMES = [
|
|
7036
|
-
"$paywall_purchased",
|
|
7037
|
-
"$paywall_product_selected",
|
|
7038
|
-
"$core_action",
|
|
7039
|
-
"$campaign_impression",
|
|
7040
|
-
"$app_open",
|
|
7041
|
-
"$app_background",
|
|
7042
|
-
"$app_foreground",
|
|
7043
|
-
"session.end"
|
|
7044
|
-
];
|
|
7045
|
-
}
|
|
7046
|
-
});
|
|
7047
|
-
|
|
7048
7126
|
// src/core/eventEnvelopeV2.ts
|
|
7049
7127
|
import { Platform as Platform13 } from "react-native";
|
|
7050
7128
|
import { z as z2 } from "zod";
|
|
@@ -7074,6 +7152,9 @@ function buildV2Envelope(events, providerContext) {
|
|
|
7074
7152
|
if (context.distinct_id === void 0 && events.length > 0) {
|
|
7075
7153
|
context.distinct_id = events[0].distinctId;
|
|
7076
7154
|
}
|
|
7155
|
+
if (!context.distinct_id || context.distinct_id.length === 0) {
|
|
7156
|
+
console.error("[Paywallo:ENVELOPE] distinct_id is empty after resolution \u2014 envelope will fail schema", { providerContext, eventCount: events.length, firstEventName: events[0]?.eventName });
|
|
7157
|
+
}
|
|
7077
7158
|
return { context, events: v2Events };
|
|
7078
7159
|
}
|
|
7079
7160
|
function resolveProviderContext(provider) {
|
|
@@ -7125,12 +7206,18 @@ var init_eventEnvelopeV2 = __esm({
|
|
|
7125
7206
|
session_id: z2.string().optional(),
|
|
7126
7207
|
device_id: z2.string().optional(),
|
|
7127
7208
|
app_version: z2.string().optional(),
|
|
7209
|
+
app_build: z2.string().optional(),
|
|
7210
|
+
bundle_id: z2.string().optional(),
|
|
7128
7211
|
sdk_version: z2.string().optional(),
|
|
7129
7212
|
platform: z2.string().optional(),
|
|
7130
7213
|
os_version: z2.string().optional(),
|
|
7131
7214
|
device_model: z2.string().optional(),
|
|
7132
7215
|
timezone: z2.string().optional(),
|
|
7133
7216
|
locale: z2.string().optional(),
|
|
7217
|
+
carrier: z2.string().optional(),
|
|
7218
|
+
screen_width: z2.number().optional(),
|
|
7219
|
+
screen_height: z2.number().optional(),
|
|
7220
|
+
screen_density: z2.number().optional(),
|
|
7134
7221
|
attribution: z2.record(z2.string(), z2.unknown()).optional(),
|
|
7135
7222
|
ids: z2.record(z2.string(), z2.unknown()).optional()
|
|
7136
7223
|
});
|
|
@@ -7143,12 +7230,21 @@ var init_eventEnvelopeV2 = __esm({
|
|
|
7143
7230
|
session_id: ["session_id", "sessionId"],
|
|
7144
7231
|
device_id: ["device_id", "deviceId"],
|
|
7145
7232
|
app_version: ["app_version", "appVersion"],
|
|
7233
|
+
// F2.2 — alias só snake_case canônico: campos vêm do context provider, não
|
|
7234
|
+
// de properties de evento. Evita promover (e deletar) chaves do payload do
|
|
7235
|
+
// $app_installed que o server lê (screenWidth etc.).
|
|
7236
|
+
app_build: ["app_build"],
|
|
7237
|
+
bundle_id: ["bundle_id"],
|
|
7146
7238
|
sdk_version: ["sdk_version", "sdkVersion"],
|
|
7147
7239
|
platform: ["platform"],
|
|
7148
7240
|
os_version: ["os_version", "osVersion", "systemVersion"],
|
|
7149
7241
|
device_model: ["device_model", "deviceModel", "model"],
|
|
7150
7242
|
timezone: ["timezone", "timeZone"],
|
|
7151
7243
|
locale: ["locale"],
|
|
7244
|
+
carrier: ["carrier_ctx"],
|
|
7245
|
+
screen_width: ["screen_width"],
|
|
7246
|
+
screen_height: ["screen_height"],
|
|
7247
|
+
screen_density: ["screen_density"],
|
|
7152
7248
|
attribution: ["attribution"],
|
|
7153
7249
|
ids: ["ids"]
|
|
7154
7250
|
};
|
|
@@ -7275,8 +7371,8 @@ var init_EventBatcher = __esm({
|
|
|
7275
7371
|
for (const event of drained) {
|
|
7276
7372
|
try {
|
|
7277
7373
|
await this.postBatch([event], `event_critical:${event.eventName}`, "critical");
|
|
7278
|
-
} catch {
|
|
7279
|
-
|
|
7374
|
+
} catch (err) {
|
|
7375
|
+
console.error("[Paywallo:CRITICAL] critical event post failed", { eventName: event.eventName, error: err instanceof Error ? err.message : String(err) });
|
|
7280
7376
|
}
|
|
7281
7377
|
}
|
|
7282
7378
|
}
|
|
@@ -7289,8 +7385,8 @@ var init_EventBatcher = __esm({
|
|
|
7289
7385
|
}
|
|
7290
7386
|
try {
|
|
7291
7387
|
await this.postBatch(batch, `event_batch:${batch.length}`);
|
|
7292
|
-
} catch {
|
|
7293
|
-
|
|
7388
|
+
} catch (err) {
|
|
7389
|
+
console.error("[Paywallo:NORMAL] batch post failed \u2014 relying on OfflineQueue", { batchSize: batch.length, error: err instanceof Error ? err.message : String(err) });
|
|
7294
7390
|
}
|
|
7295
7391
|
}
|
|
7296
7392
|
async postBatch(events, label, priority = "normal") {
|
|
@@ -7305,6 +7401,7 @@ var init_EventBatcher = __esm({
|
|
|
7305
7401
|
const parsed = v2EnvelopeSchema.safeParse(envelope);
|
|
7306
7402
|
if (!parsed.success) {
|
|
7307
7403
|
if (this.debug) console.log("[Paywallo EVENTS] V2 envelope schema validation failed:", parsed.error.issues);
|
|
7404
|
+
console.error("[Paywallo:CRITICAL] V2 envelope schema validation failed", { issues: parsed.error.issues, eventCount: events.length, firstEventName: events[0]?.eventName, context: providerContext });
|
|
7308
7405
|
throw parsed.error;
|
|
7309
7406
|
}
|
|
7310
7407
|
await this.post(V2_BATCH_ENDPOINT, parsed.data, label, priority);
|
|
@@ -7887,7 +7984,8 @@ function setupNotifications(apiClient, config, environment, eventPipeline) {
|
|
|
7887
7984
|
httpClient: apiClient.getHttpClient(),
|
|
7888
7985
|
secureStorage: new SecureStorage(config.debug),
|
|
7889
7986
|
getDeviceId: () => identityManager.getDeviceId(),
|
|
7890
|
-
getDistinctId: () => identityManager.getDistinctId()
|
|
7987
|
+
getDistinctId: () => identityManager.getDistinctId(),
|
|
7988
|
+
environment: environment === "Sandbox" ? "sandbox" : "production"
|
|
7891
7989
|
});
|
|
7892
7990
|
notificationsManager.setupHandlers({ eventBatcher: eventPipeline });
|
|
7893
7991
|
notificationsManager.initialize({
|
|
@@ -7971,7 +8069,38 @@ var init_PaywalloState = __esm({
|
|
|
7971
8069
|
}
|
|
7972
8070
|
});
|
|
7973
8071
|
|
|
8072
|
+
// src/utils/detectEnvironment.ts
|
|
8073
|
+
import { NativeModules as NativeModules8 } from "react-native";
|
|
8074
|
+
async function detectEnvironment() {
|
|
8075
|
+
if (cached !== null) return cached;
|
|
8076
|
+
if (typeof globalThis.__DEV__ !== "undefined" && globalThis.__DEV__) {
|
|
8077
|
+
cached = "sandbox";
|
|
8078
|
+
return cached;
|
|
8079
|
+
}
|
|
8080
|
+
if (!nativeModule) {
|
|
8081
|
+
cached = "production";
|
|
8082
|
+
return cached;
|
|
8083
|
+
}
|
|
8084
|
+
try {
|
|
8085
|
+
const result = await nativeModule.detect();
|
|
8086
|
+
cached = result === "sandbox" ? "sandbox" : "production";
|
|
8087
|
+
return cached;
|
|
8088
|
+
} catch {
|
|
8089
|
+
cached = "production";
|
|
8090
|
+
return cached;
|
|
8091
|
+
}
|
|
8092
|
+
}
|
|
8093
|
+
var nativeModule, cached;
|
|
8094
|
+
var init_detectEnvironment = __esm({
|
|
8095
|
+
"src/utils/detectEnvironment.ts"() {
|
|
8096
|
+
"use strict";
|
|
8097
|
+
nativeModule = NativeModules8.PaywalloEnv ?? null;
|
|
8098
|
+
cached = null;
|
|
8099
|
+
}
|
|
8100
|
+
});
|
|
8101
|
+
|
|
7974
8102
|
// src/core/PaywalloInitializer.ts
|
|
8103
|
+
import { Dimensions as Dimensions4, PixelRatio as PixelRatio2 } from "react-native";
|
|
7975
8104
|
async function initWithRetry(state, config) {
|
|
7976
8105
|
while (state.initAttempts <= MAX_INIT_RETRIES) {
|
|
7977
8106
|
try {
|
|
@@ -8050,7 +8179,13 @@ async function resolveSessionFlags(state, keys, apiClient, distinctId) {
|
|
|
8050
8179
|
}
|
|
8051
8180
|
}
|
|
8052
8181
|
async function doInit(state, config) {
|
|
8053
|
-
|
|
8182
|
+
let environment;
|
|
8183
|
+
if (config.environment) {
|
|
8184
|
+
environment = config.environment;
|
|
8185
|
+
} else {
|
|
8186
|
+
const detected = await detectEnvironment();
|
|
8187
|
+
environment = detected === "sandbox" ? "Sandbox" : "Production";
|
|
8188
|
+
}
|
|
8054
8189
|
const offlineQueueEnabled = config.offlineQueueEnabled !== false;
|
|
8055
8190
|
await Promise.all([
|
|
8056
8191
|
networkMonitor.initialize({ debug: config.debug }),
|
|
@@ -8138,15 +8273,31 @@ async function doInit(state, config) {
|
|
|
8138
8273
|
timezone = opts.timeZone || void 0;
|
|
8139
8274
|
} catch {
|
|
8140
8275
|
}
|
|
8276
|
+
let screenWidth;
|
|
8277
|
+
let screenHeight;
|
|
8278
|
+
let screenDensity;
|
|
8279
|
+
try {
|
|
8280
|
+
const screen = Dimensions4.get("screen");
|
|
8281
|
+
screenWidth = Math.round(screen.width) || void 0;
|
|
8282
|
+
screenHeight = Math.round(screen.height) || void 0;
|
|
8283
|
+
screenDensity = PixelRatio2.get() || void 0;
|
|
8284
|
+
} catch {
|
|
8285
|
+
}
|
|
8141
8286
|
return {
|
|
8142
8287
|
distinct_id: identityManager.getDistinctId() || void 0,
|
|
8143
8288
|
device_id: identityManager.getDeviceId() ?? void 0,
|
|
8144
8289
|
session_id: sessionManager.getSessionId() ?? void 0,
|
|
8145
8290
|
app_version: device?.appVersion && device.appVersion !== "0.0.0" ? device.appVersion : void 0,
|
|
8291
|
+
app_build: device?.buildNumber && device.buildNumber !== "0" ? device.buildNumber : void 0,
|
|
8292
|
+
bundle_id: device?.bundleId || void 0,
|
|
8146
8293
|
os_version: device?.systemVersion && device.systemVersion !== "0.0.0" ? device.systemVersion : void 0,
|
|
8147
8294
|
device_model: device?.modelId || device?.model || void 0,
|
|
8148
8295
|
timezone,
|
|
8149
8296
|
locale,
|
|
8297
|
+
carrier: device?.carrier || void 0,
|
|
8298
|
+
screen_width: screenWidth,
|
|
8299
|
+
screen_height: screenHeight,
|
|
8300
|
+
screen_density: screenDensity,
|
|
8150
8301
|
...attribution && Object.keys(attribution).length > 0 && { attribution },
|
|
8151
8302
|
...ids && Object.keys(ids).length > 0 && { ids }
|
|
8152
8303
|
};
|
|
@@ -8218,6 +8369,7 @@ var init_PaywalloInitializer = __esm({
|
|
|
8218
8369
|
init_PaywalloState();
|
|
8219
8370
|
init_queue();
|
|
8220
8371
|
init_NativeStorage();
|
|
8372
|
+
init_detectEnvironment();
|
|
8221
8373
|
}
|
|
8222
8374
|
});
|
|
8223
8375
|
|
|
@@ -8675,8 +8827,8 @@ var init_OfferingService = __esm({
|
|
|
8675
8827
|
this.config = config;
|
|
8676
8828
|
}
|
|
8677
8829
|
async getOfferings(ids) {
|
|
8678
|
-
const
|
|
8679
|
-
if (
|
|
8830
|
+
const cached2 = this.getMemCache();
|
|
8831
|
+
if (cached2 && !cached2.isStale) return this.filterByIds(cached2.data, ids);
|
|
8680
8832
|
if (!this.cache) {
|
|
8681
8833
|
try {
|
|
8682
8834
|
const stored = await secureStorage.get(OFFERINGS_CACHE_KEY);
|
|
@@ -9215,9 +9367,10 @@ function useSubscription() {
|
|
|
9215
9367
|
|
|
9216
9368
|
// src/PaywalloProvider.tsx
|
|
9217
9369
|
import { useCallback as useCallback19, useEffect as useEffect12, useLayoutEffect, useMemo as useMemo2, useRef as useRef10, useState as useState16 } from "react";
|
|
9218
|
-
import { Animated as Animated3, Dimensions as
|
|
9370
|
+
import { Animated as Animated3, Dimensions as Dimensions5, Easing as Easing2, StyleSheet as StyleSheet3 } from "react-native";
|
|
9219
9371
|
init_PaywalloClient();
|
|
9220
9372
|
init_SecureStorage();
|
|
9373
|
+
init_NativeStorage();
|
|
9221
9374
|
init_campaign();
|
|
9222
9375
|
init_paywall();
|
|
9223
9376
|
init_paywall();
|
|
@@ -9249,12 +9402,13 @@ async function detectOsVersion() {
|
|
|
9249
9402
|
}
|
|
9250
9403
|
|
|
9251
9404
|
// src/hooks/useAppAutoEvents.ts
|
|
9252
|
-
var FIRST_SEEN_KEY = "@paywallo:
|
|
9405
|
+
var FIRST_SEEN_KEY = "@paywallo:firstSeen_v226";
|
|
9253
9406
|
var DEBOUNCE_MS = 1e3;
|
|
9254
9407
|
function useAppAutoEvents(config) {
|
|
9255
9408
|
const { isInitialized, getAppVersion: getAppVersion2, getSessionId, emitLifecycle, storageGet, storageSet, debug } = config;
|
|
9256
9409
|
const didRunRef = useRef7(false);
|
|
9257
9410
|
useEffect11(() => {
|
|
9411
|
+
if (debug) console.log("[Paywallo EVENTS] useEffect fired", { isInitialized, didRunRefCurrent: didRunRef.current });
|
|
9258
9412
|
if (!isInitialized || didRunRef.current) return;
|
|
9259
9413
|
const timer = setTimeout(() => {
|
|
9260
9414
|
if (didRunRef.current) return;
|
|
@@ -9264,17 +9418,23 @@ function useAppAutoEvents(config) {
|
|
|
9264
9418
|
const appVersion = getAppVersion2();
|
|
9265
9419
|
const deviceType = await detectPlatform3();
|
|
9266
9420
|
const osVersion = await detectOsVersion();
|
|
9421
|
+
if (debug) console.log("[Paywallo EVENTS] computed device context", { deviceType, osVersion, appVersion, sessionId, hasSessionId: !!sessionId });
|
|
9267
9422
|
try {
|
|
9423
|
+
if (debug) console.log("[Paywallo EVENTS] checking install \u2014 about to read FIRST_SEEN_KEY");
|
|
9268
9424
|
const firstSeen = await storageGet(FIRST_SEEN_KEY);
|
|
9269
9425
|
if (!firstSeen) {
|
|
9270
9426
|
await emitLifecycle({ type: "install", device_type: deviceType, os_version: osVersion, app_version: appVersion, ...sessionId ? { session_id: sessionId } : {} }, "critical");
|
|
9271
9427
|
await storageSet(FIRST_SEEN_KEY, (/* @__PURE__ */ new Date()).toISOString());
|
|
9428
|
+
if (debug) console.log("[Paywallo EVENTS] install event emitted (first time)");
|
|
9272
9429
|
if (debug) console.log("[Paywallo EVENTS] lifecycle install emitted");
|
|
9430
|
+
} else {
|
|
9431
|
+
if (debug) console.log("[Paywallo EVENTS] install skipped \u2014 firstSeen already exists", { firstSeen });
|
|
9273
9432
|
}
|
|
9274
9433
|
} catch (err) {
|
|
9275
9434
|
if (debug) console.log("[Paywallo EVENTS] lifecycle install check failed", err);
|
|
9276
9435
|
}
|
|
9277
9436
|
try {
|
|
9437
|
+
if (debug) console.log("[Paywallo EVENTS] emitting cold_start");
|
|
9278
9438
|
await emitLifecycle({ type: "cold_start", app_version: appVersion, device_type: deviceType, os_version: osVersion, ...sessionId ? { session_id: sessionId } : {} }, "normal");
|
|
9279
9439
|
if (debug) console.log("[Paywallo EVENTS] lifecycle cold_start emitted");
|
|
9280
9440
|
} catch (err) {
|
|
@@ -9589,9 +9749,9 @@ function subscriptionInfoToSub(s) {
|
|
|
9589
9749
|
}
|
|
9590
9750
|
async function resolveOfflineFromCache(distinctId) {
|
|
9591
9751
|
try {
|
|
9592
|
-
const
|
|
9593
|
-
if (!
|
|
9594
|
-
const sub =
|
|
9752
|
+
const cached2 = await subscriptionCache.get(distinctId);
|
|
9753
|
+
if (!cached2) return false;
|
|
9754
|
+
const sub = cached2.data.subscription;
|
|
9595
9755
|
return isSubscriptionStillActive(
|
|
9596
9756
|
sub ? { status: sub.status, expiresAt: sub.expiresAt ?? null } : null
|
|
9597
9757
|
);
|
|
@@ -9661,8 +9821,8 @@ function useSubscriptionSync() {
|
|
|
9661
9821
|
if (!apiClient || !distinctId) return false;
|
|
9662
9822
|
const storeKitResult = await checkStoreKitActive(PaywalloClient.getConfig()?.debug);
|
|
9663
9823
|
if (storeKitResult !== null) return storeKitResult;
|
|
9664
|
-
const
|
|
9665
|
-
if (
|
|
9824
|
+
const cached2 = cacheRef.current;
|
|
9825
|
+
if (cached2 && cached2.expiresAt > Date.now()) return isActiveCacheEntry(cached2);
|
|
9666
9826
|
const persistedResult = await checkPersistedCache(distinctId, (sub) => setCacheAndState(sub));
|
|
9667
9827
|
if (persistedResult !== null) return persistedResult;
|
|
9668
9828
|
try {
|
|
@@ -9679,8 +9839,8 @@ function useSubscriptionSync() {
|
|
|
9679
9839
|
const apiClient = PaywalloClient.getApiClient();
|
|
9680
9840
|
const distinctId = PaywalloClient.getDistinctId();
|
|
9681
9841
|
if (!apiClient || !distinctId) return null;
|
|
9682
|
-
const
|
|
9683
|
-
if (
|
|
9842
|
+
const cached2 = cacheRef.current;
|
|
9843
|
+
if (cached2 && cached2.expiresAt > Date.now()) return cached2.data;
|
|
9684
9844
|
try {
|
|
9685
9845
|
return await fetchAndCache();
|
|
9686
9846
|
} catch {
|
|
@@ -9696,6 +9856,196 @@ function useSubscriptionSync() {
|
|
|
9696
9856
|
return { subscription, hasActiveSubscription, getSubscription, invalidateCache };
|
|
9697
9857
|
}
|
|
9698
9858
|
|
|
9859
|
+
// src/domains/paywall/SuperwallAutoBridge.ts
|
|
9860
|
+
init_PaywalloClient();
|
|
9861
|
+
|
|
9862
|
+
// src/core/utils/eventId.ts
|
|
9863
|
+
function hashString(input) {
|
|
9864
|
+
let h1 = 3735928559;
|
|
9865
|
+
let h2 = 1103547991;
|
|
9866
|
+
for (let i = 0; i < input.length; i++) {
|
|
9867
|
+
const ch = input.charCodeAt(i);
|
|
9868
|
+
h1 = Math.imul(h1 ^ ch, 2654435769);
|
|
9869
|
+
h2 = Math.imul(h2 ^ ch, 1598716949);
|
|
9870
|
+
}
|
|
9871
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 73244475);
|
|
9872
|
+
h1 ^= Math.imul(h2 ^ h2 >>> 13, 351452291);
|
|
9873
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 73244475);
|
|
9874
|
+
h2 ^= Math.imul(h1 ^ h1 >>> 13, 351452291);
|
|
9875
|
+
const toHex = (n) => (n >>> 0).toString(16).padStart(8, "0");
|
|
9876
|
+
return toHex(h1) + toHex(h2);
|
|
9877
|
+
}
|
|
9878
|
+
function buildDeterministicEventId(parts) {
|
|
9879
|
+
const raw = hashString(parts.join("|"));
|
|
9880
|
+
const p1 = raw.slice(0, 8);
|
|
9881
|
+
const p2 = raw.slice(8, 12);
|
|
9882
|
+
const p3 = "4" + raw.slice(13, 16);
|
|
9883
|
+
const p4 = (parseInt(raw[16], 16) & 3 | 8).toString(16) + raw.slice(17, 20);
|
|
9884
|
+
const p5 = raw.padEnd(32, "0").slice(20, 32);
|
|
9885
|
+
return `${p1}-${p2}-${p3}-${p4}-${p5}`;
|
|
9886
|
+
}
|
|
9887
|
+
|
|
9888
|
+
// src/domains/paywall/SuperwallAutoBridge.ts
|
|
9889
|
+
var bridgeStarted = false;
|
|
9890
|
+
var unsubscribe = null;
|
|
9891
|
+
var debugEnabled = false;
|
|
9892
|
+
function log(msg, data) {
|
|
9893
|
+
if (!debugEnabled) return;
|
|
9894
|
+
if (data !== void 0) {
|
|
9895
|
+
console.log(`[Paywallo:SuperwallBridge] ${msg}`, data);
|
|
9896
|
+
} else {
|
|
9897
|
+
console.log(`[Paywallo:SuperwallBridge] ${msg}`);
|
|
9898
|
+
}
|
|
9899
|
+
}
|
|
9900
|
+
function minuteBucket(ts) {
|
|
9901
|
+
return String(Math.floor(ts / 6e4));
|
|
9902
|
+
}
|
|
9903
|
+
function buildEventId(type, identifier, ts) {
|
|
9904
|
+
return buildDeterministicEventId(["sw", type, identifier, minuteBucket(ts)]);
|
|
9905
|
+
}
|
|
9906
|
+
async function trackPaywallEvent(type, identifier, ts, extra) {
|
|
9907
|
+
const _eventId = buildEventId(type, identifier, ts);
|
|
9908
|
+
try {
|
|
9909
|
+
const cleanExtra = {};
|
|
9910
|
+
if (extra) {
|
|
9911
|
+
for (const [k, v] of Object.entries(extra)) if (v !== void 0) cleanExtra[k] = v;
|
|
9912
|
+
}
|
|
9913
|
+
await PaywalloClient.track("paywall", {
|
|
9914
|
+
priority: "critical",
|
|
9915
|
+
properties: {
|
|
9916
|
+
type,
|
|
9917
|
+
paywall_id: identifier,
|
|
9918
|
+
...cleanExtra
|
|
9919
|
+
}
|
|
9920
|
+
});
|
|
9921
|
+
} catch (err) {
|
|
9922
|
+
log("track error", { type, identifier, eventId: _eventId, err: String(err) });
|
|
9923
|
+
}
|
|
9924
|
+
}
|
|
9925
|
+
function mapDismissToCloseReason(resultType) {
|
|
9926
|
+
switch (resultType) {
|
|
9927
|
+
case "purchased":
|
|
9928
|
+
return "purchased";
|
|
9929
|
+
case "restored":
|
|
9930
|
+
return "purchased";
|
|
9931
|
+
// restore counts as a purchase outcome for funnel
|
|
9932
|
+
case "declined":
|
|
9933
|
+
return "dismissed";
|
|
9934
|
+
default:
|
|
9935
|
+
return void 0;
|
|
9936
|
+
}
|
|
9937
|
+
}
|
|
9938
|
+
function buildCallbacks() {
|
|
9939
|
+
return {
|
|
9940
|
+
onPaywallPresent: (info) => {
|
|
9941
|
+
const ts = Date.now();
|
|
9942
|
+
log("onPaywallPresent", { identifier: info.identifier });
|
|
9943
|
+
void trackPaywallEvent("viewed", info.identifier, ts, {
|
|
9944
|
+
placement: info.presentedByEventWithName,
|
|
9945
|
+
variant_id: info.experiment?.variant?.id,
|
|
9946
|
+
campaign_id: info.experiment?.id
|
|
9947
|
+
});
|
|
9948
|
+
},
|
|
9949
|
+
onPaywallDismiss: (info, result) => {
|
|
9950
|
+
const ts = Date.now();
|
|
9951
|
+
log("onPaywallDismiss", { identifier: info.identifier, result: result.type });
|
|
9952
|
+
void trackPaywallEvent("closed", info.identifier, ts, {
|
|
9953
|
+
placement: info.presentedByEventWithName,
|
|
9954
|
+
variant_id: info.experiment?.variant?.id,
|
|
9955
|
+
campaign_id: info.experiment?.id,
|
|
9956
|
+
close_reason: mapDismissToCloseReason(result.type),
|
|
9957
|
+
closed_at: new Date(ts).toISOString()
|
|
9958
|
+
});
|
|
9959
|
+
},
|
|
9960
|
+
onPurchase: (params) => {
|
|
9961
|
+
const ts = Date.now();
|
|
9962
|
+
const safe = params !== null && typeof params === "object" ? params : {};
|
|
9963
|
+
const productId = "productId" in safe && typeof safe.productId === "string" ? safe.productId : "unknown";
|
|
9964
|
+
const identifier = "paywallIdentifier" in safe && typeof safe.paywallIdentifier === "string" ? safe.paywallIdentifier : productId;
|
|
9965
|
+
log("onPurchase", { productId, identifier });
|
|
9966
|
+
void trackPaywallEvent("purchased", identifier, ts, { product_id: productId });
|
|
9967
|
+
},
|
|
9968
|
+
// Global Superwall event listener — used specifically for `transactionComplete`
|
|
9969
|
+
// which is the only event that exposes the full product (currency/price) +
|
|
9970
|
+
// transaction (storeTransactionId) shape needed to create a server-side
|
|
9971
|
+
// `transactions` row. Other events are skipped (paywallOpen/Dismiss already
|
|
9972
|
+
// handled above with their typed callbacks).
|
|
9973
|
+
onSuperwallEvent: (info) => {
|
|
9974
|
+
const ev = info?.event;
|
|
9975
|
+
if (!ev || ev.event !== "transactionComplete") return;
|
|
9976
|
+
const ts = Date.now();
|
|
9977
|
+
const product = ev.product;
|
|
9978
|
+
const transaction = ev.transaction;
|
|
9979
|
+
if (!product) {
|
|
9980
|
+
log("transactionComplete missing product \u2014 skipping transaction track");
|
|
9981
|
+
return;
|
|
9982
|
+
}
|
|
9983
|
+
const productId = product.productIdentifier ?? product.id ?? "unknown";
|
|
9984
|
+
const currency = product.currencyCode ?? "USD";
|
|
9985
|
+
const price = product.price ?? 0;
|
|
9986
|
+
const transactionId = transaction?.storeTransactionId ?? transaction?.originalTransactionIdentifier ?? `${productId}_${String(ts)}`;
|
|
9987
|
+
const isTrial = product.hasFreeTrial === true || (product.trialPeriodPrice ?? 0) > 0;
|
|
9988
|
+
const paywallIdentifier = ev.paywallInfo?.identifier;
|
|
9989
|
+
log("onSuperwallEvent: transactionComplete", { productId, currency, price, transactionId, isTrial });
|
|
9990
|
+
void (async () => {
|
|
9991
|
+
try {
|
|
9992
|
+
await PaywalloClient.track("transaction", {
|
|
9993
|
+
priority: "critical",
|
|
9994
|
+
properties: {
|
|
9995
|
+
type: isTrial ? "trial_started" : "completed",
|
|
9996
|
+
// Server's IngestTranslator looks for `transaction_id`/`product_id`
|
|
9997
|
+
// /`amount`/`currency`/`paywall_id` keys (not `tx_id`). PurchaseHandler
|
|
9998
|
+
// then resolves type=trial/new/renewal based on amount being 0 vs >0.
|
|
9999
|
+
transaction_id: transactionId,
|
|
10000
|
+
product_id: productId,
|
|
10001
|
+
amount: price,
|
|
10002
|
+
currency,
|
|
10003
|
+
...paywallIdentifier ? { paywall_id: paywallIdentifier } : {}
|
|
10004
|
+
}
|
|
10005
|
+
});
|
|
10006
|
+
} catch (err) {
|
|
10007
|
+
log("transactionComplete track error", { err: String(err) });
|
|
10008
|
+
}
|
|
10009
|
+
})();
|
|
10010
|
+
}
|
|
10011
|
+
};
|
|
10012
|
+
}
|
|
10013
|
+
function startSuperwallAutoBridge(debug = false) {
|
|
10014
|
+
debugEnabled = debug;
|
|
10015
|
+
if (bridgeStarted) {
|
|
10016
|
+
log("already started, skipping");
|
|
10017
|
+
return;
|
|
10018
|
+
}
|
|
10019
|
+
void detectAndStart();
|
|
10020
|
+
}
|
|
10021
|
+
async function detectAndStart() {
|
|
10022
|
+
let subscribeFn;
|
|
10023
|
+
try {
|
|
10024
|
+
const mod = await import("expo-superwall");
|
|
10025
|
+
if (!mod || typeof mod.subscribeToSuperwallEvents !== "function") {
|
|
10026
|
+
const internal = await import("expo-superwall/build/src/internal/superwallEventBridge");
|
|
10027
|
+
if (typeof internal.subscribeToSuperwallEvents !== "function") {
|
|
10028
|
+
log("expo-superwall subscribeToSuperwallEvents not found \u2014 bridge inactive");
|
|
10029
|
+
return;
|
|
10030
|
+
}
|
|
10031
|
+
subscribeFn = internal.subscribeToSuperwallEvents;
|
|
10032
|
+
} else {
|
|
10033
|
+
subscribeFn = mod.subscribeToSuperwallEvents;
|
|
10034
|
+
}
|
|
10035
|
+
} catch {
|
|
10036
|
+
log("expo-superwall not installed \u2014 bridge inactive");
|
|
10037
|
+
return;
|
|
10038
|
+
}
|
|
10039
|
+
try {
|
|
10040
|
+
const callbacks = buildCallbacks();
|
|
10041
|
+
unsubscribe = subscribeFn(void 0, () => callbacks);
|
|
10042
|
+
bridgeStarted = true;
|
|
10043
|
+
log("bridge started");
|
|
10044
|
+
} catch (err) {
|
|
10045
|
+
log("subscribe failed", { err: String(err) });
|
|
10046
|
+
}
|
|
10047
|
+
}
|
|
10048
|
+
|
|
9699
10049
|
// src/PaywalloProvider.tsx
|
|
9700
10050
|
init_localization();
|
|
9701
10051
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
@@ -9725,8 +10075,18 @@ function PaywalloProvider({ children, config }) {
|
|
|
9725
10075
|
for (const [k, v] of Object.entries(props)) if (v !== void 0) flat[k] = v;
|
|
9726
10076
|
return PaywalloClient.track("lifecycle", { properties: flat, priority });
|
|
9727
10077
|
},
|
|
9728
|
-
storageGet: (key) =>
|
|
9729
|
-
|
|
10078
|
+
storageGet: async (key) => {
|
|
10079
|
+
if (key === "@paywallo:firstSeen_v226") {
|
|
10080
|
+
return nativeStorage.get(key);
|
|
10081
|
+
}
|
|
10082
|
+
return secureStorageRef.current.get(key);
|
|
10083
|
+
},
|
|
10084
|
+
storageSet: async (key, value) => {
|
|
10085
|
+
if (key === "@paywallo:firstSeen_v226") {
|
|
10086
|
+
return nativeStorage.set(key, value);
|
|
10087
|
+
}
|
|
10088
|
+
return secureStorageRef.current.set(key, value);
|
|
10089
|
+
},
|
|
9730
10090
|
debug: config.debug
|
|
9731
10091
|
});
|
|
9732
10092
|
const triggerRepreload = useCallback19((placement) => {
|
|
@@ -9734,7 +10094,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
9734
10094
|
});
|
|
9735
10095
|
}, []);
|
|
9736
10096
|
const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
|
|
9737
|
-
const SCREEN_HEIGHT = useMemo2(() =>
|
|
10097
|
+
const SCREEN_HEIGHT = useMemo2(() => Dimensions5.get("window").height, []);
|
|
9738
10098
|
const slideAnim = useRef10(new Animated3.Value(SCREEN_HEIGHT)).current;
|
|
9739
10099
|
const handlePreloadedWebViewReady = useCallback19(() => {
|
|
9740
10100
|
if (PaywalloClient.getConfig()?.debug) {
|
|
@@ -9773,6 +10133,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
9773
10133
|
PaywalloClient.init(configRef.current).then(async () => {
|
|
9774
10134
|
if (!mounted) return;
|
|
9775
10135
|
setDistinctId(PaywalloClient.getDistinctId());
|
|
10136
|
+
startSuperwallAutoBridge(configRef.current.debug);
|
|
9776
10137
|
setIsInitialized(true);
|
|
9777
10138
|
if (!autoPreloadTriggeredRef.current) {
|
|
9778
10139
|
autoPreloadTriggeredRef.current = true;
|