@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.js
CHANGED
|
@@ -830,9 +830,9 @@ var init_CampaignGateService = __esm({
|
|
|
830
830
|
}
|
|
831
831
|
}
|
|
832
832
|
getPreloadedCampaign(placement) {
|
|
833
|
-
const
|
|
834
|
-
if (!
|
|
835
|
-
const age = Date.now() -
|
|
833
|
+
const cached2 = this.preloadedCampaigns.get(placement);
|
|
834
|
+
if (!cached2) return null;
|
|
835
|
+
const age = Date.now() - cached2.preloadedAt;
|
|
836
836
|
if (age >= PRELOAD_CACHE_TTL_MS) {
|
|
837
837
|
this.preloadedCampaigns.delete(placement);
|
|
838
838
|
return null;
|
|
@@ -841,7 +841,7 @@ var init_CampaignGateService = __esm({
|
|
|
841
841
|
void this.preloadCampaign(this.apiClientRef, placement).catch(() => {
|
|
842
842
|
});
|
|
843
843
|
}
|
|
844
|
-
return
|
|
844
|
+
return cached2.campaign;
|
|
845
845
|
}
|
|
846
846
|
async preloadAllActive(apiClient, debug) {
|
|
847
847
|
this.apiClientRef = apiClient;
|
|
@@ -2024,13 +2024,21 @@ var init_NativePushBridge = __esm({
|
|
|
2024
2024
|
if (!raw || typeof raw !== "object") return null;
|
|
2025
2025
|
const r = raw;
|
|
2026
2026
|
const notification = r["notification"];
|
|
2027
|
+
const asString = (v) => typeof v === "string" ? v : void 0;
|
|
2027
2028
|
return {
|
|
2028
|
-
messageId:
|
|
2029
|
+
messageId: asString(r["messageId"]),
|
|
2029
2030
|
data: typeof r["data"] === "object" && r["data"] !== null ? r["data"] : void 0,
|
|
2030
2031
|
notification: notification && typeof notification === "object" ? {
|
|
2031
|
-
title:
|
|
2032
|
-
body:
|
|
2032
|
+
title: asString(notification["title"]),
|
|
2033
|
+
body: asString(notification["body"]),
|
|
2034
|
+
imageUrl: asString(notification["imageUrl"])
|
|
2033
2035
|
} : void 0,
|
|
2036
|
+
// Pass through top-level title/body/imageUrl emitted by the native modules
|
|
2037
|
+
// (both iOS and Android currently emit these at the top level rather than
|
|
2038
|
+
// nested under `notification`).
|
|
2039
|
+
title: asString(r["title"]),
|
|
2040
|
+
body: asString(r["body"]),
|
|
2041
|
+
imageUrl: asString(r["imageUrl"]),
|
|
2034
2042
|
sentTime: typeof r["sentTime"] === "number" ? r["sentTime"] : void 0
|
|
2035
2043
|
};
|
|
2036
2044
|
} catch {
|
|
@@ -2165,12 +2173,13 @@ function extractPayload(data) {
|
|
|
2165
2173
|
}
|
|
2166
2174
|
function setupForegroundHandler(deps, onReceive) {
|
|
2167
2175
|
const { bridge, tracker, debug } = deps;
|
|
2168
|
-
const
|
|
2176
|
+
const unsubscribe2 = bridge.onMessage(async (message) => {
|
|
2169
2177
|
const data = message.data ?? {};
|
|
2170
2178
|
const payload = extractPayload({
|
|
2171
2179
|
...data,
|
|
2172
|
-
title: data["title"] ?? message.notification?.title ?? "",
|
|
2173
|
-
body: data["body"] ?? message.notification?.body ?? ""
|
|
2180
|
+
title: data["title"] ?? message.notification?.title ?? message.title ?? "",
|
|
2181
|
+
body: data["body"] ?? message.notification?.body ?? message.body ?? "",
|
|
2182
|
+
imageUrl: data["imageUrl"] ?? message.notification?.imageUrl ?? message.imageUrl
|
|
2174
2183
|
});
|
|
2175
2184
|
if (debug) console.log("[Paywallo PUSH] foreground message received:", payload.notificationId);
|
|
2176
2185
|
onReceive(payload);
|
|
@@ -2182,7 +2191,7 @@ function setupForegroundHandler(deps, onReceive) {
|
|
|
2182
2191
|
messageId
|
|
2183
2192
|
});
|
|
2184
2193
|
});
|
|
2185
|
-
return
|
|
2194
|
+
return unsubscribe2;
|
|
2186
2195
|
}
|
|
2187
2196
|
var init_ForegroundHandler = __esm({
|
|
2188
2197
|
"src/domains/notifications/handlers/ForegroundHandler.ts"() {
|
|
@@ -2361,8 +2370,8 @@ async function getInitialNotificationPayload(bridge) {
|
|
|
2361
2370
|
notificationId: String(data["notificationId"] ?? data["notification_id"] ?? ""),
|
|
2362
2371
|
campaignId: String(data["campaignId"] ?? data["campaign_id"] ?? ""),
|
|
2363
2372
|
variantKey: data["variantKey"] != null ? String(data["variantKey"]) : void 0,
|
|
2364
|
-
title: String(data["title"] ?? message.notification?.title ?? ""),
|
|
2365
|
-
body: String(data["body"] ?? message.notification?.body ?? ""),
|
|
2373
|
+
title: String(data["title"] ?? message.notification?.title ?? message.title ?? ""),
|
|
2374
|
+
body: String(data["body"] ?? message.notification?.body ?? message.body ?? ""),
|
|
2366
2375
|
data
|
|
2367
2376
|
};
|
|
2368
2377
|
}
|
|
@@ -2377,8 +2386,8 @@ var init_NotificationHandlerSetup = __esm({
|
|
|
2377
2386
|
// src/core/version.ts
|
|
2378
2387
|
function resolveVersion() {
|
|
2379
2388
|
try {
|
|
2380
|
-
if ("2.2.
|
|
2381
|
-
return "2.2.
|
|
2389
|
+
if ("2.2.6") {
|
|
2390
|
+
return "2.2.6";
|
|
2382
2391
|
}
|
|
2383
2392
|
} catch {
|
|
2384
2393
|
}
|
|
@@ -2450,9 +2459,6 @@ function mapPermissionStatus(status) {
|
|
|
2450
2459
|
function detectPlatform2() {
|
|
2451
2460
|
return import_react_native11.Platform.OS === "ios" ? "ios" : "android";
|
|
2452
2461
|
}
|
|
2453
|
-
function detectEnvironment() {
|
|
2454
|
-
return typeof __DEV__ !== "undefined" && __DEV__ ? "sandbox" : "production";
|
|
2455
|
-
}
|
|
2456
2462
|
async function getAppVersion() {
|
|
2457
2463
|
try {
|
|
2458
2464
|
const { collectDeviceInfo: collectDeviceInfo2 } = await Promise.resolve().then(() => (init_deviceInfo(), deviceInfo_exports));
|
|
@@ -2493,7 +2499,7 @@ async function clearPersistedToken(secureStorage2) {
|
|
|
2493
2499
|
} catch {
|
|
2494
2500
|
}
|
|
2495
2501
|
}
|
|
2496
|
-
async function buildRegistration(token, getDeviceId, getDistinctId) {
|
|
2502
|
+
async function buildRegistration(token, getDeviceId, getDistinctId, environment) {
|
|
2497
2503
|
const appVersion = await getAppVersion();
|
|
2498
2504
|
return {
|
|
2499
2505
|
token,
|
|
@@ -2504,7 +2510,7 @@ async function buildRegistration(token, getDeviceId, getDistinctId) {
|
|
|
2504
2510
|
deviceId: getDeviceId?.() ?? void 0,
|
|
2505
2511
|
locale: await getLocale(),
|
|
2506
2512
|
timezone: getTimezone2(),
|
|
2507
|
-
environment
|
|
2513
|
+
environment
|
|
2508
2514
|
};
|
|
2509
2515
|
}
|
|
2510
2516
|
async function registerToken(deps, token) {
|
|
@@ -2513,7 +2519,8 @@ async function registerToken(deps, token) {
|
|
|
2513
2519
|
if (debug) console.log("[Paywallo PUSH] httpClient not ready, skipping token registration");
|
|
2514
2520
|
return;
|
|
2515
2521
|
}
|
|
2516
|
-
const
|
|
2522
|
+
const { environment } = deps;
|
|
2523
|
+
const registration = await buildRegistration(token, getDeviceId, getDistinctId, environment);
|
|
2517
2524
|
try {
|
|
2518
2525
|
await httpClient.post(
|
|
2519
2526
|
TOKEN_ENDPOINT,
|
|
@@ -2563,7 +2570,8 @@ async function registerAndPersistToken(deps, token) {
|
|
|
2563
2570
|
appKey: deps.appKey,
|
|
2564
2571
|
getDeviceId: deps.getDeviceId,
|
|
2565
2572
|
getDistinctId: deps.getDistinctId,
|
|
2566
|
-
debug: deps.debug
|
|
2573
|
+
debug: deps.debug,
|
|
2574
|
+
environment: deps.environment
|
|
2567
2575
|
},
|
|
2568
2576
|
token
|
|
2569
2577
|
);
|
|
@@ -2580,7 +2588,8 @@ function subscribeTokenRefresh(bridge, deps, onToken) {
|
|
|
2580
2588
|
appKey: deps.appKey,
|
|
2581
2589
|
getDeviceId: deps.getDeviceId,
|
|
2582
2590
|
getDistinctId: deps.getDistinctId,
|
|
2583
|
-
debug: deps.debug
|
|
2591
|
+
debug: deps.debug,
|
|
2592
|
+
environment: deps.environment
|
|
2584
2593
|
},
|
|
2585
2594
|
newToken
|
|
2586
2595
|
).catch(() => void 0);
|
|
@@ -2788,6 +2797,7 @@ var init_NotificationsManager = __esm({
|
|
|
2788
2797
|
this.getDistinctId = null;
|
|
2789
2798
|
this.appKey = null;
|
|
2790
2799
|
this.apiBaseUrl = null;
|
|
2800
|
+
this.environment = "production";
|
|
2791
2801
|
this.tokenRefreshCleanup = null;
|
|
2792
2802
|
this.foregroundCleanup = null;
|
|
2793
2803
|
this.pendingHandlersConfig = null;
|
|
@@ -2807,6 +2817,7 @@ var init_NotificationsManager = __esm({
|
|
|
2807
2817
|
this.secureStorage = deps.secureStorage;
|
|
2808
2818
|
this.getDeviceId = deps.getDeviceId;
|
|
2809
2819
|
if (deps.getDistinctId) this.getDistinctId = deps.getDistinctId;
|
|
2820
|
+
if (deps.environment) this.environment = deps.environment;
|
|
2810
2821
|
}
|
|
2811
2822
|
async initialize(config) {
|
|
2812
2823
|
this.applyConfig(config);
|
|
@@ -2849,7 +2860,8 @@ var init_NotificationsManager = __esm({
|
|
|
2849
2860
|
appKey: this.appKey,
|
|
2850
2861
|
getDeviceId: this.getDeviceId,
|
|
2851
2862
|
getDistinctId: this.getDistinctId,
|
|
2852
|
-
debug: this.debug
|
|
2863
|
+
debug: this.debug,
|
|
2864
|
+
environment: this.environment
|
|
2853
2865
|
};
|
|
2854
2866
|
}
|
|
2855
2867
|
async doRegisterToken(token) {
|
|
@@ -3910,6 +3922,32 @@ var init_IAPTransactionEmitter = __esm({
|
|
|
3910
3922
|
if (this.getDebug()) console.log("[Paywallo PURCHASE] transaction event emission failed", err);
|
|
3911
3923
|
}
|
|
3912
3924
|
}
|
|
3925
|
+
/**
|
|
3926
|
+
* Emits `checkout_started` quando o usuário inicia a compra (toca em comprar),
|
|
3927
|
+
* antes do StoreKit/Billing abrir — conta mesmo que a sheet nativa seja cancelada.
|
|
3928
|
+
* Vira InitiateCheckout no Meta/TikTok. Fire-and-forget — uma falha de tracking
|
|
3929
|
+
* nunca pode travar a compra.
|
|
3930
|
+
*/
|
|
3931
|
+
async emitCheckoutStarted(productId) {
|
|
3932
|
+
try {
|
|
3933
|
+
const apiClient = this.getApiClientFn();
|
|
3934
|
+
if (!apiClient) return;
|
|
3935
|
+
const distinctId = PaywalloClient.getDistinctId();
|
|
3936
|
+
if (!distinctId) return;
|
|
3937
|
+
const product = this.getProductFromCache(productId);
|
|
3938
|
+
const properties = {
|
|
3939
|
+
product_id: productId,
|
|
3940
|
+
amount: product?.priceValue ?? 0,
|
|
3941
|
+
currency: product?.currency ?? "USD"
|
|
3942
|
+
};
|
|
3943
|
+
await apiClient.trackEvent("checkout_started", distinctId, properties, void 0, {
|
|
3944
|
+
priority: "critical"
|
|
3945
|
+
});
|
|
3946
|
+
if (this.getDebug()) console.log("[Paywallo PURCHASE] emitted checkout_started", productId);
|
|
3947
|
+
} catch (err) {
|
|
3948
|
+
if (this.getDebug()) console.log("[Paywallo PURCHASE] checkout_started emission failed", err);
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3913
3951
|
/**
|
|
3914
3952
|
* Forwards a native transaction update to the EventBatcher.
|
|
3915
3953
|
* Public so tests can exercise the wiring without a fake NativeEventEmitter.
|
|
@@ -3932,9 +3970,9 @@ var init_IAPTransactionEmitter = __esm({
|
|
|
3932
3970
|
product_id: update.productId
|
|
3933
3971
|
};
|
|
3934
3972
|
if (update.type !== "canceled") {
|
|
3935
|
-
const
|
|
3936
|
-
const amount = update.amount ??
|
|
3937
|
-
const currency = update.currency ??
|
|
3973
|
+
const cached2 = this.getProductFromCache(update.productId);
|
|
3974
|
+
const amount = update.amount ?? cached2?.priceValue;
|
|
3975
|
+
const currency = update.currency ?? cached2?.currency;
|
|
3938
3976
|
if (typeof amount === "number" && amount > 0) properties.amount = amount;
|
|
3939
3977
|
if (currency) properties.currency = currency;
|
|
3940
3978
|
}
|
|
@@ -4113,10 +4151,10 @@ var init_OfflineQueueStorage = __esm({
|
|
|
4113
4151
|
init_NativeStorage();
|
|
4114
4152
|
init_QueueJournal();
|
|
4115
4153
|
OfflineQueueStorage = class {
|
|
4116
|
-
constructor(config, emit,
|
|
4154
|
+
constructor(config, emit, log2) {
|
|
4117
4155
|
this.config = config;
|
|
4118
4156
|
this.emit = emit;
|
|
4119
|
-
this.log =
|
|
4157
|
+
this.log = log2;
|
|
4120
4158
|
}
|
|
4121
4159
|
async loadFromStorage(queue) {
|
|
4122
4160
|
const { merged, recovered, hadJournal } = await mergeJournalIntoMain(
|
|
@@ -4723,12 +4761,25 @@ var init_QueueProcessor = __esm({
|
|
|
4723
4761
|
const freshHeaders = this.config.getFreshHeaders?.() ?? {};
|
|
4724
4762
|
const headers = { ...persistedHeaders, ...freshHeaders };
|
|
4725
4763
|
this.log("Sending event batch", { size: batch.length, batchIndex: Math.floor(i / BATCH_SIZE) });
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4764
|
+
let response;
|
|
4765
|
+
try {
|
|
4766
|
+
response = await this.httpClient.request("/sdk/ingest/batch", {
|
|
4767
|
+
method: "POST",
|
|
4768
|
+
body: { events: bodies },
|
|
4769
|
+
headers,
|
|
4770
|
+
skipRetry: false
|
|
4771
|
+
});
|
|
4772
|
+
} catch (error) {
|
|
4773
|
+
for (const item of batch) {
|
|
4774
|
+
retryIds.add(item.id);
|
|
4775
|
+
}
|
|
4776
|
+
console.error("[Paywallo:QUEUE] event batch network error \u2014 items will retry", { size: batch.length, error: error instanceof Error ? error.message : String(error) });
|
|
4777
|
+
this.log("Event batch threw network error - will retry", {
|
|
4778
|
+
size: batch.length,
|
|
4779
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4780
|
+
});
|
|
4781
|
+
continue;
|
|
4782
|
+
}
|
|
4732
4783
|
if (response.ok) {
|
|
4733
4784
|
for (const item of batch) {
|
|
4734
4785
|
await offlineQueue.removeItem(item.id);
|
|
@@ -4740,14 +4791,25 @@ var init_QueueProcessor = __esm({
|
|
|
4740
4791
|
await offlineQueue.removeItem(item.id);
|
|
4741
4792
|
failed++;
|
|
4742
4793
|
}
|
|
4794
|
+
const responseError = typeof response.data === "object" && response.data !== null && "error" in response.data ? response.data.error : void 0;
|
|
4795
|
+
const isDuplicate = typeof responseError === "string" && responseError.toUpperCase().includes("DUPLICATE");
|
|
4796
|
+
if (isDuplicate) {
|
|
4797
|
+
if (this.config.debug) {
|
|
4798
|
+
console.log("[Paywallo:QUEUE] event batch deduplicated by server \u2014 items dropped (benign)", { status: response.status, size: batch.length, error: responseError });
|
|
4799
|
+
}
|
|
4800
|
+
} else {
|
|
4801
|
+
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) });
|
|
4802
|
+
}
|
|
4743
4803
|
this.log("Event batch failed permanently (4xx) - removing from queue", {
|
|
4744
4804
|
status: response.status,
|
|
4745
|
-
size: batch.length
|
|
4805
|
+
size: batch.length,
|
|
4806
|
+
isDuplicate
|
|
4746
4807
|
});
|
|
4747
4808
|
} else {
|
|
4748
4809
|
for (const item of batch) {
|
|
4749
4810
|
retryIds.add(item.id);
|
|
4750
4811
|
}
|
|
4812
|
+
console.error("[Paywallo:QUEUE] event batch server error (5xx) \u2014 will retry", { status: response.status, size: batch.length });
|
|
4751
4813
|
this.log("Event batch failed with server error - will retry", {
|
|
4752
4814
|
status: response.status,
|
|
4753
4815
|
size: batch.length
|
|
@@ -4768,12 +4830,22 @@ var init_QueueProcessor = __esm({
|
|
|
4768
4830
|
});
|
|
4769
4831
|
const persistedHeaders = item.headers ?? {};
|
|
4770
4832
|
const freshHeaders = this.config.getFreshHeaders?.() ?? {};
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4833
|
+
let response;
|
|
4834
|
+
try {
|
|
4835
|
+
response = await this.httpClient.request(item.url, {
|
|
4836
|
+
method: item.method,
|
|
4837
|
+
body: item.body,
|
|
4838
|
+
headers: { ...persistedHeaders, ...freshHeaders },
|
|
4839
|
+
skipRetry: false
|
|
4840
|
+
});
|
|
4841
|
+
} catch (error) {
|
|
4842
|
+
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) });
|
|
4843
|
+
this.log("Request threw network error - will retry", {
|
|
4844
|
+
id: item.id,
|
|
4845
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4846
|
+
});
|
|
4847
|
+
return { success: false, permanent: false };
|
|
4848
|
+
}
|
|
4777
4849
|
if (response.ok) {
|
|
4778
4850
|
this.log("Queue item processed successfully", { id: item.id });
|
|
4779
4851
|
return { success: true, permanent: false };
|
|
@@ -5064,6 +5136,7 @@ var init_IAPService = __esm({
|
|
|
5064
5136
|
this.purchaseInFlight = productId;
|
|
5065
5137
|
if (this.debug) console.log(`[Paywallo PURCHASE] starting ${productId}`);
|
|
5066
5138
|
try {
|
|
5139
|
+
void this.emitter.emitCheckoutStarted(productId);
|
|
5067
5140
|
const purchase = await nativeStoreKit.purchase(productId);
|
|
5068
5141
|
if (!purchase) {
|
|
5069
5142
|
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
|
+
var import_zod, lifecycleEventSchema, identifyEventSchema, paywallEventSchema, transactionEventSchema, onboardingEventSchema, notificationEventSchema, customEventSchema, EVENT_SCHEMAS;
|
|
6080
|
+
var init_schemas = __esm({
|
|
6081
|
+
"src/core/events/schemas.ts"() {
|
|
6082
|
+
"use strict";
|
|
6083
|
+
import_zod = require("zod");
|
|
6084
|
+
lifecycleEventSchema = import_zod.z.object({
|
|
6085
|
+
type: import_zod.z.enum([
|
|
6086
|
+
"install",
|
|
6087
|
+
"cold_start",
|
|
6088
|
+
"foreground",
|
|
6089
|
+
"background",
|
|
6090
|
+
"session_start",
|
|
6091
|
+
"session_end"
|
|
6092
|
+
]),
|
|
6093
|
+
session_id: import_zod.z.string().optional(),
|
|
6094
|
+
duration_s: import_zod.z.number().nonnegative().optional(),
|
|
6095
|
+
first_ever: import_zod.z.boolean().optional(),
|
|
6096
|
+
// Contexto leve transportado em lifecycle (útil pra cold_start/install)
|
|
6097
|
+
app_version: import_zod.z.string().optional(),
|
|
6098
|
+
device_type: import_zod.z.enum(["ios", "android"]).optional(),
|
|
6099
|
+
os_version: import_zod.z.string().optional(),
|
|
6100
|
+
// Timestamps opcionais — o EventBatcher injeta `timestamp` no envelope
|
|
6101
|
+
started_at: import_zod.z.string().optional(),
|
|
6102
|
+
ended_at: import_zod.z.string().optional()
|
|
6103
|
+
}).strict();
|
|
6104
|
+
identifyEventSchema = import_zod.z.object({
|
|
6105
|
+
distinct_id: import_zod.z.string().min(1),
|
|
6106
|
+
email: import_zod.z.email().optional(),
|
|
6107
|
+
traits: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
|
|
6108
|
+
attribution: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
|
|
6109
|
+
device: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
6110
|
+
}).strict();
|
|
6111
|
+
paywallEventSchema = import_zod.z.object({
|
|
6112
|
+
type: import_zod.z.enum(["viewed", "closed", "purchased"]),
|
|
6113
|
+
paywall_id: import_zod.z.string().min(1),
|
|
6114
|
+
placement: import_zod.z.string().optional(),
|
|
6115
|
+
variant_id: import_zod.z.string().optional(),
|
|
6116
|
+
variant_key: import_zod.z.string().optional(),
|
|
6117
|
+
campaign_id: import_zod.z.string().optional(),
|
|
6118
|
+
// `closed`-specific
|
|
6119
|
+
closed_at: import_zod.z.string().optional(),
|
|
6120
|
+
duration_s: import_zod.z.number().nonnegative().optional(),
|
|
6121
|
+
close_reason: import_zod.z.enum(["dismiss", "cta", "purchase", "error", "timeout"]).optional(),
|
|
6122
|
+
scroll_depth: import_zod.z.number().min(0).max(1).optional(),
|
|
6123
|
+
// `purchased`-specific (mínimo — transaction canônica leva detalhes)
|
|
6124
|
+
product_id: import_zod.z.string().optional()
|
|
6125
|
+
}).strict();
|
|
6126
|
+
transactionEventSchema = import_zod.z.object({
|
|
6127
|
+
type: import_zod.z.enum([
|
|
6128
|
+
"completed",
|
|
6129
|
+
"failed",
|
|
6130
|
+
"refunded",
|
|
6131
|
+
"renewed",
|
|
6132
|
+
"canceled",
|
|
6133
|
+
"expired"
|
|
6134
|
+
]),
|
|
6135
|
+
tx_id: import_zod.z.string().min(1),
|
|
6136
|
+
amount: import_zod.z.number().optional(),
|
|
6137
|
+
currency: import_zod.z.string().length(3).optional(),
|
|
6138
|
+
product_id: import_zod.z.string().optional(),
|
|
6139
|
+
subscription_id: import_zod.z.string().optional(),
|
|
6140
|
+
paywall_id: import_zod.z.string().optional(),
|
|
6141
|
+
variant_id: import_zod.z.string().optional(),
|
|
6142
|
+
reason: import_zod.z.string().optional()
|
|
6143
|
+
}).strict();
|
|
6144
|
+
onboardingEventSchema = import_zod.z.object({
|
|
6145
|
+
type: import_zod.z.enum(["step", "complete", "drop"]),
|
|
6146
|
+
step_index: import_zod.z.number().int().nonnegative().optional(),
|
|
6147
|
+
step_name: import_zod.z.string().optional(),
|
|
6148
|
+
variant_key: import_zod.z.string().optional(),
|
|
6149
|
+
time_on_prev_s: import_zod.z.number().nonnegative().optional()
|
|
6150
|
+
}).strict();
|
|
6151
|
+
notificationEventSchema = import_zod.z.object({
|
|
6152
|
+
type: import_zod.z.enum(["delivered", "displayed", "clicked", "dismissed"]),
|
|
6153
|
+
notification_id: import_zod.z.string().optional(),
|
|
6154
|
+
campaign_id: import_zod.z.string().optional(),
|
|
6155
|
+
variant_key: import_zod.z.string().nullable().optional(),
|
|
6156
|
+
message_id: import_zod.z.string().optional(),
|
|
6157
|
+
device_id: import_zod.z.string().optional(),
|
|
6158
|
+
push_platform: import_zod.z.enum(["ios", "android"]).optional(),
|
|
6159
|
+
timezone: import_zod.z.string().optional(),
|
|
6160
|
+
failure_reason: import_zod.z.string().optional(),
|
|
6161
|
+
app_user_id: import_zod.z.string().optional()
|
|
6162
|
+
}).strict();
|
|
6163
|
+
customEventSchema = import_zod.z.object({
|
|
6164
|
+
name: import_zod.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: import_zod.z.record(import_zod.z.string(), import_zod.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
|
-
var import_zod, lifecycleEventSchema, identifyEventSchema, paywallEventSchema, transactionEventSchema, onboardingEventSchema, notificationEventSchema, customEventSchema, EVENT_SCHEMAS;
|
|
6893
|
-
var init_schemas = __esm({
|
|
6894
|
-
"src/core/events/schemas.ts"() {
|
|
6895
|
-
"use strict";
|
|
6896
|
-
import_zod = require("zod");
|
|
6897
|
-
lifecycleEventSchema = import_zod.z.object({
|
|
6898
|
-
type: import_zod.z.enum([
|
|
6899
|
-
"install",
|
|
6900
|
-
"cold_start",
|
|
6901
|
-
"foreground",
|
|
6902
|
-
"background",
|
|
6903
|
-
"session_start",
|
|
6904
|
-
"session_end"
|
|
6905
|
-
]),
|
|
6906
|
-
session_id: import_zod.z.string().optional(),
|
|
6907
|
-
duration_s: import_zod.z.number().nonnegative().optional(),
|
|
6908
|
-
first_ever: import_zod.z.boolean().optional(),
|
|
6909
|
-
// Contexto leve transportado em lifecycle (útil pra cold_start/install)
|
|
6910
|
-
app_version: import_zod.z.string().optional(),
|
|
6911
|
-
device_type: import_zod.z.enum(["ios", "android"]).optional(),
|
|
6912
|
-
os_version: import_zod.z.string().optional(),
|
|
6913
|
-
// Timestamps opcionais — o EventBatcher injeta `timestamp` no envelope
|
|
6914
|
-
started_at: import_zod.z.string().optional(),
|
|
6915
|
-
ended_at: import_zod.z.string().optional()
|
|
6916
|
-
}).strict();
|
|
6917
|
-
identifyEventSchema = import_zod.z.object({
|
|
6918
|
-
distinct_id: import_zod.z.string().min(1),
|
|
6919
|
-
email: import_zod.z.email().optional(),
|
|
6920
|
-
traits: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
|
|
6921
|
-
attribution: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
|
|
6922
|
-
device: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
6923
|
-
}).strict();
|
|
6924
|
-
paywallEventSchema = import_zod.z.object({
|
|
6925
|
-
type: import_zod.z.enum(["viewed", "closed", "purchased"]),
|
|
6926
|
-
paywall_id: import_zod.z.string().min(1),
|
|
6927
|
-
placement: import_zod.z.string().optional(),
|
|
6928
|
-
variant_id: import_zod.z.string().optional(),
|
|
6929
|
-
variant_key: import_zod.z.string().optional(),
|
|
6930
|
-
campaign_id: import_zod.z.string().optional(),
|
|
6931
|
-
// `closed`-specific
|
|
6932
|
-
closed_at: import_zod.z.string().optional(),
|
|
6933
|
-
duration_s: import_zod.z.number().nonnegative().optional(),
|
|
6934
|
-
close_reason: import_zod.z.enum(["dismiss", "cta", "purchase", "error", "timeout"]).optional(),
|
|
6935
|
-
scroll_depth: import_zod.z.number().min(0).max(1).optional(),
|
|
6936
|
-
// `purchased`-specific (mínimo — transaction canônica leva detalhes)
|
|
6937
|
-
product_id: import_zod.z.string().optional()
|
|
6938
|
-
}).strict();
|
|
6939
|
-
transactionEventSchema = import_zod.z.object({
|
|
6940
|
-
type: import_zod.z.enum([
|
|
6941
|
-
"completed",
|
|
6942
|
-
"failed",
|
|
6943
|
-
"refunded",
|
|
6944
|
-
"renewed",
|
|
6945
|
-
"canceled",
|
|
6946
|
-
"expired"
|
|
6947
|
-
]),
|
|
6948
|
-
tx_id: import_zod.z.string().min(1),
|
|
6949
|
-
amount: import_zod.z.number().optional(),
|
|
6950
|
-
currency: import_zod.z.string().length(3).optional(),
|
|
6951
|
-
product_id: import_zod.z.string().optional(),
|
|
6952
|
-
subscription_id: import_zod.z.string().optional(),
|
|
6953
|
-
paywall_id: import_zod.z.string().optional(),
|
|
6954
|
-
variant_id: import_zod.z.string().optional(),
|
|
6955
|
-
reason: import_zod.z.string().optional()
|
|
6956
|
-
}).strict();
|
|
6957
|
-
onboardingEventSchema = import_zod.z.object({
|
|
6958
|
-
type: import_zod.z.enum(["step", "complete", "drop"]),
|
|
6959
|
-
step_index: import_zod.z.number().int().nonnegative().optional(),
|
|
6960
|
-
step_name: import_zod.z.string().optional(),
|
|
6961
|
-
variant_key: import_zod.z.string().optional(),
|
|
6962
|
-
time_on_prev_s: import_zod.z.number().nonnegative().optional()
|
|
6963
|
-
}).strict();
|
|
6964
|
-
notificationEventSchema = import_zod.z.object({
|
|
6965
|
-
type: import_zod.z.enum(["delivered", "displayed", "clicked", "dismissed"]),
|
|
6966
|
-
notification_id: import_zod.z.string().optional(),
|
|
6967
|
-
campaign_id: import_zod.z.string().optional(),
|
|
6968
|
-
variant_key: import_zod.z.string().nullable().optional(),
|
|
6969
|
-
message_id: import_zod.z.string().optional(),
|
|
6970
|
-
device_id: import_zod.z.string().optional(),
|
|
6971
|
-
push_platform: import_zod.z.enum(["ios", "android"]).optional(),
|
|
6972
|
-
timezone: import_zod.z.string().optional(),
|
|
6973
|
-
failure_reason: import_zod.z.string().optional(),
|
|
6974
|
-
app_user_id: import_zod.z.string().optional()
|
|
6975
|
-
}).strict();
|
|
6976
|
-
customEventSchema = import_zod.z.object({
|
|
6977
|
-
name: import_zod.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: import_zod.z.record(import_zod.z.string(), import_zod.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
|
function buildV2Envelope(events, providerContext) {
|
|
7050
7128
|
const context = { ...providerContext };
|
|
@@ -7072,6 +7150,9 @@ function buildV2Envelope(events, providerContext) {
|
|
|
7072
7150
|
if (context.distinct_id === void 0 && events.length > 0) {
|
|
7073
7151
|
context.distinct_id = events[0].distinctId;
|
|
7074
7152
|
}
|
|
7153
|
+
if (!context.distinct_id || context.distinct_id.length === 0) {
|
|
7154
|
+
console.error("[Paywallo:ENVELOPE] distinct_id is empty after resolution \u2014 envelope will fail schema", { providerContext, eventCount: events.length, firstEventName: events[0]?.eventName });
|
|
7155
|
+
}
|
|
7075
7156
|
return { context, events: v2Events };
|
|
7076
7157
|
}
|
|
7077
7158
|
function resolveProviderContext(provider) {
|
|
@@ -7125,12 +7206,18 @@ var init_eventEnvelopeV2 = __esm({
|
|
|
7125
7206
|
session_id: import_zod2.z.string().optional(),
|
|
7126
7207
|
device_id: import_zod2.z.string().optional(),
|
|
7127
7208
|
app_version: import_zod2.z.string().optional(),
|
|
7209
|
+
app_build: import_zod2.z.string().optional(),
|
|
7210
|
+
bundle_id: import_zod2.z.string().optional(),
|
|
7128
7211
|
sdk_version: import_zod2.z.string().optional(),
|
|
7129
7212
|
platform: import_zod2.z.string().optional(),
|
|
7130
7213
|
os_version: import_zod2.z.string().optional(),
|
|
7131
7214
|
device_model: import_zod2.z.string().optional(),
|
|
7132
7215
|
timezone: import_zod2.z.string().optional(),
|
|
7133
7216
|
locale: import_zod2.z.string().optional(),
|
|
7217
|
+
carrier: import_zod2.z.string().optional(),
|
|
7218
|
+
screen_width: import_zod2.z.number().optional(),
|
|
7219
|
+
screen_height: import_zod2.z.number().optional(),
|
|
7220
|
+
screen_density: import_zod2.z.number().optional(),
|
|
7134
7221
|
attribution: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional(),
|
|
7135
7222
|
ids: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.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,6 +8069,36 @@ var init_PaywalloState = __esm({
|
|
|
7971
8069
|
}
|
|
7972
8070
|
});
|
|
7973
8071
|
|
|
8072
|
+
// src/utils/detectEnvironment.ts
|
|
8073
|
+
async function detectEnvironment() {
|
|
8074
|
+
if (cached !== null) return cached;
|
|
8075
|
+
if (typeof globalThis.__DEV__ !== "undefined" && globalThis.__DEV__) {
|
|
8076
|
+
cached = "sandbox";
|
|
8077
|
+
return cached;
|
|
8078
|
+
}
|
|
8079
|
+
if (!nativeModule) {
|
|
8080
|
+
cached = "production";
|
|
8081
|
+
return cached;
|
|
8082
|
+
}
|
|
8083
|
+
try {
|
|
8084
|
+
const result = await nativeModule.detect();
|
|
8085
|
+
cached = result === "sandbox" ? "sandbox" : "production";
|
|
8086
|
+
return cached;
|
|
8087
|
+
} catch {
|
|
8088
|
+
cached = "production";
|
|
8089
|
+
return cached;
|
|
8090
|
+
}
|
|
8091
|
+
}
|
|
8092
|
+
var import_react_native24, nativeModule, cached;
|
|
8093
|
+
var init_detectEnvironment = __esm({
|
|
8094
|
+
"src/utils/detectEnvironment.ts"() {
|
|
8095
|
+
"use strict";
|
|
8096
|
+
import_react_native24 = require("react-native");
|
|
8097
|
+
nativeModule = import_react_native24.NativeModules.PaywalloEnv ?? null;
|
|
8098
|
+
cached = null;
|
|
8099
|
+
}
|
|
8100
|
+
});
|
|
8101
|
+
|
|
7974
8102
|
// src/core/PaywalloInitializer.ts
|
|
7975
8103
|
async function initWithRetry(state, config) {
|
|
7976
8104
|
while (state.initAttempts <= MAX_INIT_RETRIES) {
|
|
@@ -8050,7 +8178,13 @@ async function resolveSessionFlags(state, keys, apiClient, distinctId) {
|
|
|
8050
8178
|
}
|
|
8051
8179
|
}
|
|
8052
8180
|
async function doInit(state, config) {
|
|
8053
|
-
|
|
8181
|
+
let environment;
|
|
8182
|
+
if (config.environment) {
|
|
8183
|
+
environment = config.environment;
|
|
8184
|
+
} else {
|
|
8185
|
+
const detected = await detectEnvironment();
|
|
8186
|
+
environment = detected === "sandbox" ? "Sandbox" : "Production";
|
|
8187
|
+
}
|
|
8054
8188
|
const offlineQueueEnabled = config.offlineQueueEnabled !== false;
|
|
8055
8189
|
await Promise.all([
|
|
8056
8190
|
networkMonitor.initialize({ debug: config.debug }),
|
|
@@ -8138,15 +8272,31 @@ async function doInit(state, config) {
|
|
|
8138
8272
|
timezone = opts.timeZone || void 0;
|
|
8139
8273
|
} catch {
|
|
8140
8274
|
}
|
|
8275
|
+
let screenWidth;
|
|
8276
|
+
let screenHeight;
|
|
8277
|
+
let screenDensity;
|
|
8278
|
+
try {
|
|
8279
|
+
const screen = import_react_native25.Dimensions.get("screen");
|
|
8280
|
+
screenWidth = Math.round(screen.width) || void 0;
|
|
8281
|
+
screenHeight = Math.round(screen.height) || void 0;
|
|
8282
|
+
screenDensity = import_react_native25.PixelRatio.get() || void 0;
|
|
8283
|
+
} catch {
|
|
8284
|
+
}
|
|
8141
8285
|
return {
|
|
8142
8286
|
distinct_id: identityManager.getDistinctId() || void 0,
|
|
8143
8287
|
device_id: identityManager.getDeviceId() ?? void 0,
|
|
8144
8288
|
session_id: sessionManager.getSessionId() ?? void 0,
|
|
8145
8289
|
app_version: device?.appVersion && device.appVersion !== "0.0.0" ? device.appVersion : void 0,
|
|
8290
|
+
app_build: device?.buildNumber && device.buildNumber !== "0" ? device.buildNumber : void 0,
|
|
8291
|
+
bundle_id: device?.bundleId || void 0,
|
|
8146
8292
|
os_version: device?.systemVersion && device.systemVersion !== "0.0.0" ? device.systemVersion : void 0,
|
|
8147
8293
|
device_model: device?.modelId || device?.model || void 0,
|
|
8148
8294
|
timezone,
|
|
8149
8295
|
locale,
|
|
8296
|
+
carrier: device?.carrier || void 0,
|
|
8297
|
+
screen_width: screenWidth,
|
|
8298
|
+
screen_height: screenHeight,
|
|
8299
|
+
screen_density: screenDensity,
|
|
8150
8300
|
...attribution && Object.keys(attribution).length > 0 && { attribution },
|
|
8151
8301
|
...ids && Object.keys(ids).length > 0 && { ids }
|
|
8152
8302
|
};
|
|
@@ -8196,9 +8346,11 @@ async function doInit(state, config) {
|
|
|
8196
8346
|
setupAutoPreload(state, apiClient, config, distinctId);
|
|
8197
8347
|
startBatchPreload(apiClient, config.debug);
|
|
8198
8348
|
}
|
|
8349
|
+
var import_react_native25;
|
|
8199
8350
|
var init_PaywalloInitializer = __esm({
|
|
8200
8351
|
"src/core/PaywalloInitializer.ts"() {
|
|
8201
8352
|
"use strict";
|
|
8353
|
+
import_react_native25 = require("react-native");
|
|
8202
8354
|
init_identity();
|
|
8203
8355
|
init_AdvertisingIdManager();
|
|
8204
8356
|
init_DeepLinkAttributionCapture();
|
|
@@ -8218,6 +8370,7 @@ var init_PaywalloInitializer = __esm({
|
|
|
8218
8370
|
init_PaywalloState();
|
|
8219
8371
|
init_queue();
|
|
8220
8372
|
init_NativeStorage();
|
|
8373
|
+
init_detectEnvironment();
|
|
8221
8374
|
}
|
|
8222
8375
|
});
|
|
8223
8376
|
|
|
@@ -8622,7 +8775,7 @@ __export(OfferingService_exports, {
|
|
|
8622
8775
|
offeringService: () => offeringService
|
|
8623
8776
|
});
|
|
8624
8777
|
function resolveProductId(raw) {
|
|
8625
|
-
const isIos =
|
|
8778
|
+
const isIos = import_react_native26.Platform.OS === "ios";
|
|
8626
8779
|
const productId = isIos ? raw.apple_product_id : raw.google_product_id;
|
|
8627
8780
|
return productId ?? "";
|
|
8628
8781
|
}
|
|
@@ -8641,7 +8794,7 @@ function mapProduct2(raw) {
|
|
|
8641
8794
|
};
|
|
8642
8795
|
}
|
|
8643
8796
|
function pickPlatformProduct(products) {
|
|
8644
|
-
const isIos =
|
|
8797
|
+
const isIos = import_react_native26.Platform.OS === "ios";
|
|
8645
8798
|
const match = products.find((p) => isIos ? p.apple_product_id : p.google_product_id);
|
|
8646
8799
|
return match ?? products[0] ?? null;
|
|
8647
8800
|
}
|
|
@@ -8657,11 +8810,11 @@ function mapOffering(raw) {
|
|
|
8657
8810
|
product: mapProduct2(rawProduct)
|
|
8658
8811
|
};
|
|
8659
8812
|
}
|
|
8660
|
-
var
|
|
8813
|
+
var import_react_native26, OFFERINGS_CACHE_KEY, DEFAULT_TTL3, OfferingService, offeringService;
|
|
8661
8814
|
var init_OfferingService = __esm({
|
|
8662
8815
|
"src/domains/offering/OfferingService.ts"() {
|
|
8663
8816
|
"use strict";
|
|
8664
|
-
|
|
8817
|
+
import_react_native26 = require("react-native");
|
|
8665
8818
|
init_SecureStorage();
|
|
8666
8819
|
OFFERINGS_CACHE_KEY = "@paywallo:offerings_cache";
|
|
8667
8820
|
DEFAULT_TTL3 = 5 * 60 * 1e3;
|
|
@@ -8675,8 +8828,8 @@ var init_OfferingService = __esm({
|
|
|
8675
8828
|
this.config = config;
|
|
8676
8829
|
}
|
|
8677
8830
|
async getOfferings(ids) {
|
|
8678
|
-
const
|
|
8679
|
-
if (
|
|
8831
|
+
const cached2 = this.getMemCache();
|
|
8832
|
+
if (cached2 && !cached2.isStale) return this.filterByIds(cached2.data, ids);
|
|
8680
8833
|
if (!this.cache) {
|
|
8681
8834
|
try {
|
|
8682
8835
|
const stored = await secureStorage.get(OFFERINGS_CACHE_KEY);
|
|
@@ -9292,9 +9445,10 @@ function useSubscription() {
|
|
|
9292
9445
|
|
|
9293
9446
|
// src/PaywalloProvider.tsx
|
|
9294
9447
|
var import_react25 = require("react");
|
|
9295
|
-
var
|
|
9448
|
+
var import_react_native27 = require("react-native");
|
|
9296
9449
|
init_PaywalloClient();
|
|
9297
9450
|
init_SecureStorage();
|
|
9451
|
+
init_NativeStorage();
|
|
9298
9452
|
init_campaign();
|
|
9299
9453
|
init_paywall();
|
|
9300
9454
|
init_paywall();
|
|
@@ -9326,12 +9480,13 @@ async function detectOsVersion() {
|
|
|
9326
9480
|
}
|
|
9327
9481
|
|
|
9328
9482
|
// src/hooks/useAppAutoEvents.ts
|
|
9329
|
-
var FIRST_SEEN_KEY = "@paywallo:
|
|
9483
|
+
var FIRST_SEEN_KEY = "@paywallo:firstSeen_v226";
|
|
9330
9484
|
var DEBOUNCE_MS = 1e3;
|
|
9331
9485
|
function useAppAutoEvents(config) {
|
|
9332
9486
|
const { isInitialized, getAppVersion: getAppVersion2, getSessionId, emitLifecycle, storageGet, storageSet, debug } = config;
|
|
9333
9487
|
const didRunRef = (0, import_react20.useRef)(false);
|
|
9334
9488
|
(0, import_react20.useEffect)(() => {
|
|
9489
|
+
if (debug) console.log("[Paywallo EVENTS] useEffect fired", { isInitialized, didRunRefCurrent: didRunRef.current });
|
|
9335
9490
|
if (!isInitialized || didRunRef.current) return;
|
|
9336
9491
|
const timer = setTimeout(() => {
|
|
9337
9492
|
if (didRunRef.current) return;
|
|
@@ -9341,17 +9496,23 @@ function useAppAutoEvents(config) {
|
|
|
9341
9496
|
const appVersion = getAppVersion2();
|
|
9342
9497
|
const deviceType = await detectPlatform3();
|
|
9343
9498
|
const osVersion = await detectOsVersion();
|
|
9499
|
+
if (debug) console.log("[Paywallo EVENTS] computed device context", { deviceType, osVersion, appVersion, sessionId, hasSessionId: !!sessionId });
|
|
9344
9500
|
try {
|
|
9501
|
+
if (debug) console.log("[Paywallo EVENTS] checking install \u2014 about to read FIRST_SEEN_KEY");
|
|
9345
9502
|
const firstSeen = await storageGet(FIRST_SEEN_KEY);
|
|
9346
9503
|
if (!firstSeen) {
|
|
9347
9504
|
await emitLifecycle({ type: "install", device_type: deviceType, os_version: osVersion, app_version: appVersion, ...sessionId ? { session_id: sessionId } : {} }, "critical");
|
|
9348
9505
|
await storageSet(FIRST_SEEN_KEY, (/* @__PURE__ */ new Date()).toISOString());
|
|
9506
|
+
if (debug) console.log("[Paywallo EVENTS] install event emitted (first time)");
|
|
9349
9507
|
if (debug) console.log("[Paywallo EVENTS] lifecycle install emitted");
|
|
9508
|
+
} else {
|
|
9509
|
+
if (debug) console.log("[Paywallo EVENTS] install skipped \u2014 firstSeen already exists", { firstSeen });
|
|
9350
9510
|
}
|
|
9351
9511
|
} catch (err) {
|
|
9352
9512
|
if (debug) console.log("[Paywallo EVENTS] lifecycle install check failed", err);
|
|
9353
9513
|
}
|
|
9354
9514
|
try {
|
|
9515
|
+
if (debug) console.log("[Paywallo EVENTS] emitting cold_start");
|
|
9355
9516
|
await emitLifecycle({ type: "cold_start", app_version: appVersion, device_type: deviceType, os_version: osVersion, ...sessionId ? { session_id: sessionId } : {} }, "normal");
|
|
9356
9517
|
if (debug) console.log("[Paywallo EVENTS] lifecycle cold_start emitted");
|
|
9357
9518
|
} catch (err) {
|
|
@@ -9666,9 +9827,9 @@ function subscriptionInfoToSub(s) {
|
|
|
9666
9827
|
}
|
|
9667
9828
|
async function resolveOfflineFromCache(distinctId) {
|
|
9668
9829
|
try {
|
|
9669
|
-
const
|
|
9670
|
-
if (!
|
|
9671
|
-
const sub =
|
|
9830
|
+
const cached2 = await subscriptionCache.get(distinctId);
|
|
9831
|
+
if (!cached2) return false;
|
|
9832
|
+
const sub = cached2.data.subscription;
|
|
9672
9833
|
return isSubscriptionStillActive(
|
|
9673
9834
|
sub ? { status: sub.status, expiresAt: sub.expiresAt ?? null } : null
|
|
9674
9835
|
);
|
|
@@ -9738,8 +9899,8 @@ function useSubscriptionSync() {
|
|
|
9738
9899
|
if (!apiClient || !distinctId) return false;
|
|
9739
9900
|
const storeKitResult = await checkStoreKitActive(PaywalloClient.getConfig()?.debug);
|
|
9740
9901
|
if (storeKitResult !== null) return storeKitResult;
|
|
9741
|
-
const
|
|
9742
|
-
if (
|
|
9902
|
+
const cached2 = cacheRef.current;
|
|
9903
|
+
if (cached2 && cached2.expiresAt > Date.now()) return isActiveCacheEntry(cached2);
|
|
9743
9904
|
const persistedResult = await checkPersistedCache(distinctId, (sub) => setCacheAndState(sub));
|
|
9744
9905
|
if (persistedResult !== null) return persistedResult;
|
|
9745
9906
|
try {
|
|
@@ -9756,8 +9917,8 @@ function useSubscriptionSync() {
|
|
|
9756
9917
|
const apiClient = PaywalloClient.getApiClient();
|
|
9757
9918
|
const distinctId = PaywalloClient.getDistinctId();
|
|
9758
9919
|
if (!apiClient || !distinctId) return null;
|
|
9759
|
-
const
|
|
9760
|
-
if (
|
|
9920
|
+
const cached2 = cacheRef.current;
|
|
9921
|
+
if (cached2 && cached2.expiresAt > Date.now()) return cached2.data;
|
|
9761
9922
|
try {
|
|
9762
9923
|
return await fetchAndCache();
|
|
9763
9924
|
} catch {
|
|
@@ -9773,6 +9934,196 @@ function useSubscriptionSync() {
|
|
|
9773
9934
|
return { subscription, hasActiveSubscription, getSubscription, invalidateCache };
|
|
9774
9935
|
}
|
|
9775
9936
|
|
|
9937
|
+
// src/domains/paywall/SuperwallAutoBridge.ts
|
|
9938
|
+
init_PaywalloClient();
|
|
9939
|
+
|
|
9940
|
+
// src/core/utils/eventId.ts
|
|
9941
|
+
function hashString(input) {
|
|
9942
|
+
let h1 = 3735928559;
|
|
9943
|
+
let h2 = 1103547991;
|
|
9944
|
+
for (let i = 0; i < input.length; i++) {
|
|
9945
|
+
const ch = input.charCodeAt(i);
|
|
9946
|
+
h1 = Math.imul(h1 ^ ch, 2654435769);
|
|
9947
|
+
h2 = Math.imul(h2 ^ ch, 1598716949);
|
|
9948
|
+
}
|
|
9949
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 73244475);
|
|
9950
|
+
h1 ^= Math.imul(h2 ^ h2 >>> 13, 351452291);
|
|
9951
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 73244475);
|
|
9952
|
+
h2 ^= Math.imul(h1 ^ h1 >>> 13, 351452291);
|
|
9953
|
+
const toHex = (n) => (n >>> 0).toString(16).padStart(8, "0");
|
|
9954
|
+
return toHex(h1) + toHex(h2);
|
|
9955
|
+
}
|
|
9956
|
+
function buildDeterministicEventId(parts) {
|
|
9957
|
+
const raw = hashString(parts.join("|"));
|
|
9958
|
+
const p1 = raw.slice(0, 8);
|
|
9959
|
+
const p2 = raw.slice(8, 12);
|
|
9960
|
+
const p3 = "4" + raw.slice(13, 16);
|
|
9961
|
+
const p4 = (parseInt(raw[16], 16) & 3 | 8).toString(16) + raw.slice(17, 20);
|
|
9962
|
+
const p5 = raw.padEnd(32, "0").slice(20, 32);
|
|
9963
|
+
return `${p1}-${p2}-${p3}-${p4}-${p5}`;
|
|
9964
|
+
}
|
|
9965
|
+
|
|
9966
|
+
// src/domains/paywall/SuperwallAutoBridge.ts
|
|
9967
|
+
var bridgeStarted = false;
|
|
9968
|
+
var unsubscribe = null;
|
|
9969
|
+
var debugEnabled = false;
|
|
9970
|
+
function log(msg, data) {
|
|
9971
|
+
if (!debugEnabled) return;
|
|
9972
|
+
if (data !== void 0) {
|
|
9973
|
+
console.log(`[Paywallo:SuperwallBridge] ${msg}`, data);
|
|
9974
|
+
} else {
|
|
9975
|
+
console.log(`[Paywallo:SuperwallBridge] ${msg}`);
|
|
9976
|
+
}
|
|
9977
|
+
}
|
|
9978
|
+
function minuteBucket(ts) {
|
|
9979
|
+
return String(Math.floor(ts / 6e4));
|
|
9980
|
+
}
|
|
9981
|
+
function buildEventId(type, identifier, ts) {
|
|
9982
|
+
return buildDeterministicEventId(["sw", type, identifier, minuteBucket(ts)]);
|
|
9983
|
+
}
|
|
9984
|
+
async function trackPaywallEvent(type, identifier, ts, extra) {
|
|
9985
|
+
const _eventId = buildEventId(type, identifier, ts);
|
|
9986
|
+
try {
|
|
9987
|
+
const cleanExtra = {};
|
|
9988
|
+
if (extra) {
|
|
9989
|
+
for (const [k, v] of Object.entries(extra)) if (v !== void 0) cleanExtra[k] = v;
|
|
9990
|
+
}
|
|
9991
|
+
await PaywalloClient.track("paywall", {
|
|
9992
|
+
priority: "critical",
|
|
9993
|
+
properties: {
|
|
9994
|
+
type,
|
|
9995
|
+
paywall_id: identifier,
|
|
9996
|
+
...cleanExtra
|
|
9997
|
+
}
|
|
9998
|
+
});
|
|
9999
|
+
} catch (err) {
|
|
10000
|
+
log("track error", { type, identifier, eventId: _eventId, err: String(err) });
|
|
10001
|
+
}
|
|
10002
|
+
}
|
|
10003
|
+
function mapDismissToCloseReason(resultType) {
|
|
10004
|
+
switch (resultType) {
|
|
10005
|
+
case "purchased":
|
|
10006
|
+
return "purchased";
|
|
10007
|
+
case "restored":
|
|
10008
|
+
return "purchased";
|
|
10009
|
+
// restore counts as a purchase outcome for funnel
|
|
10010
|
+
case "declined":
|
|
10011
|
+
return "dismissed";
|
|
10012
|
+
default:
|
|
10013
|
+
return void 0;
|
|
10014
|
+
}
|
|
10015
|
+
}
|
|
10016
|
+
function buildCallbacks() {
|
|
10017
|
+
return {
|
|
10018
|
+
onPaywallPresent: (info) => {
|
|
10019
|
+
const ts = Date.now();
|
|
10020
|
+
log("onPaywallPresent", { identifier: info.identifier });
|
|
10021
|
+
void trackPaywallEvent("viewed", info.identifier, ts, {
|
|
10022
|
+
placement: info.presentedByEventWithName,
|
|
10023
|
+
variant_id: info.experiment?.variant?.id,
|
|
10024
|
+
campaign_id: info.experiment?.id
|
|
10025
|
+
});
|
|
10026
|
+
},
|
|
10027
|
+
onPaywallDismiss: (info, result) => {
|
|
10028
|
+
const ts = Date.now();
|
|
10029
|
+
log("onPaywallDismiss", { identifier: info.identifier, result: result.type });
|
|
10030
|
+
void trackPaywallEvent("closed", info.identifier, ts, {
|
|
10031
|
+
placement: info.presentedByEventWithName,
|
|
10032
|
+
variant_id: info.experiment?.variant?.id,
|
|
10033
|
+
campaign_id: info.experiment?.id,
|
|
10034
|
+
close_reason: mapDismissToCloseReason(result.type),
|
|
10035
|
+
closed_at: new Date(ts).toISOString()
|
|
10036
|
+
});
|
|
10037
|
+
},
|
|
10038
|
+
onPurchase: (params) => {
|
|
10039
|
+
const ts = Date.now();
|
|
10040
|
+
const safe = params !== null && typeof params === "object" ? params : {};
|
|
10041
|
+
const productId = "productId" in safe && typeof safe.productId === "string" ? safe.productId : "unknown";
|
|
10042
|
+
const identifier = "paywallIdentifier" in safe && typeof safe.paywallIdentifier === "string" ? safe.paywallIdentifier : productId;
|
|
10043
|
+
log("onPurchase", { productId, identifier });
|
|
10044
|
+
void trackPaywallEvent("purchased", identifier, ts, { product_id: productId });
|
|
10045
|
+
},
|
|
10046
|
+
// Global Superwall event listener — used specifically for `transactionComplete`
|
|
10047
|
+
// which is the only event that exposes the full product (currency/price) +
|
|
10048
|
+
// transaction (storeTransactionId) shape needed to create a server-side
|
|
10049
|
+
// `transactions` row. Other events are skipped (paywallOpen/Dismiss already
|
|
10050
|
+
// handled above with their typed callbacks).
|
|
10051
|
+
onSuperwallEvent: (info) => {
|
|
10052
|
+
const ev = info?.event;
|
|
10053
|
+
if (!ev || ev.event !== "transactionComplete") return;
|
|
10054
|
+
const ts = Date.now();
|
|
10055
|
+
const product = ev.product;
|
|
10056
|
+
const transaction = ev.transaction;
|
|
10057
|
+
if (!product) {
|
|
10058
|
+
log("transactionComplete missing product \u2014 skipping transaction track");
|
|
10059
|
+
return;
|
|
10060
|
+
}
|
|
10061
|
+
const productId = product.productIdentifier ?? product.id ?? "unknown";
|
|
10062
|
+
const currency = product.currencyCode ?? "USD";
|
|
10063
|
+
const price = product.price ?? 0;
|
|
10064
|
+
const transactionId = transaction?.storeTransactionId ?? transaction?.originalTransactionIdentifier ?? `${productId}_${String(ts)}`;
|
|
10065
|
+
const isTrial = product.hasFreeTrial === true || (product.trialPeriodPrice ?? 0) > 0;
|
|
10066
|
+
const paywallIdentifier = ev.paywallInfo?.identifier;
|
|
10067
|
+
log("onSuperwallEvent: transactionComplete", { productId, currency, price, transactionId, isTrial });
|
|
10068
|
+
void (async () => {
|
|
10069
|
+
try {
|
|
10070
|
+
await PaywalloClient.track("transaction", {
|
|
10071
|
+
priority: "critical",
|
|
10072
|
+
properties: {
|
|
10073
|
+
type: isTrial ? "trial_started" : "completed",
|
|
10074
|
+
// Server's IngestTranslator looks for `transaction_id`/`product_id`
|
|
10075
|
+
// /`amount`/`currency`/`paywall_id` keys (not `tx_id`). PurchaseHandler
|
|
10076
|
+
// then resolves type=trial/new/renewal based on amount being 0 vs >0.
|
|
10077
|
+
transaction_id: transactionId,
|
|
10078
|
+
product_id: productId,
|
|
10079
|
+
amount: price,
|
|
10080
|
+
currency,
|
|
10081
|
+
...paywallIdentifier ? { paywall_id: paywallIdentifier } : {}
|
|
10082
|
+
}
|
|
10083
|
+
});
|
|
10084
|
+
} catch (err) {
|
|
10085
|
+
log("transactionComplete track error", { err: String(err) });
|
|
10086
|
+
}
|
|
10087
|
+
})();
|
|
10088
|
+
}
|
|
10089
|
+
};
|
|
10090
|
+
}
|
|
10091
|
+
function startSuperwallAutoBridge(debug = false) {
|
|
10092
|
+
debugEnabled = debug;
|
|
10093
|
+
if (bridgeStarted) {
|
|
10094
|
+
log("already started, skipping");
|
|
10095
|
+
return;
|
|
10096
|
+
}
|
|
10097
|
+
void detectAndStart();
|
|
10098
|
+
}
|
|
10099
|
+
async function detectAndStart() {
|
|
10100
|
+
let subscribeFn;
|
|
10101
|
+
try {
|
|
10102
|
+
const mod = await import("expo-superwall");
|
|
10103
|
+
if (!mod || typeof mod.subscribeToSuperwallEvents !== "function") {
|
|
10104
|
+
const internal = await import("expo-superwall/build/src/internal/superwallEventBridge");
|
|
10105
|
+
if (typeof internal.subscribeToSuperwallEvents !== "function") {
|
|
10106
|
+
log("expo-superwall subscribeToSuperwallEvents not found \u2014 bridge inactive");
|
|
10107
|
+
return;
|
|
10108
|
+
}
|
|
10109
|
+
subscribeFn = internal.subscribeToSuperwallEvents;
|
|
10110
|
+
} else {
|
|
10111
|
+
subscribeFn = mod.subscribeToSuperwallEvents;
|
|
10112
|
+
}
|
|
10113
|
+
} catch {
|
|
10114
|
+
log("expo-superwall not installed \u2014 bridge inactive");
|
|
10115
|
+
return;
|
|
10116
|
+
}
|
|
10117
|
+
try {
|
|
10118
|
+
const callbacks = buildCallbacks();
|
|
10119
|
+
unsubscribe = subscribeFn(void 0, () => callbacks);
|
|
10120
|
+
bridgeStarted = true;
|
|
10121
|
+
log("bridge started");
|
|
10122
|
+
} catch (err) {
|
|
10123
|
+
log("subscribe failed", { err: String(err) });
|
|
10124
|
+
}
|
|
10125
|
+
}
|
|
10126
|
+
|
|
9776
10127
|
// src/PaywalloProvider.tsx
|
|
9777
10128
|
init_localization();
|
|
9778
10129
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
@@ -9802,8 +10153,18 @@ function PaywalloProvider({ children, config }) {
|
|
|
9802
10153
|
for (const [k, v] of Object.entries(props)) if (v !== void 0) flat[k] = v;
|
|
9803
10154
|
return PaywalloClient.track("lifecycle", { properties: flat, priority });
|
|
9804
10155
|
},
|
|
9805
|
-
storageGet: (key) =>
|
|
9806
|
-
|
|
10156
|
+
storageGet: async (key) => {
|
|
10157
|
+
if (key === "@paywallo:firstSeen_v226") {
|
|
10158
|
+
return nativeStorage.get(key);
|
|
10159
|
+
}
|
|
10160
|
+
return secureStorageRef.current.get(key);
|
|
10161
|
+
},
|
|
10162
|
+
storageSet: async (key, value) => {
|
|
10163
|
+
if (key === "@paywallo:firstSeen_v226") {
|
|
10164
|
+
return nativeStorage.set(key, value);
|
|
10165
|
+
}
|
|
10166
|
+
return secureStorageRef.current.set(key, value);
|
|
10167
|
+
},
|
|
9807
10168
|
debug: config.debug
|
|
9808
10169
|
});
|
|
9809
10170
|
const triggerRepreload = (0, import_react25.useCallback)((placement) => {
|
|
@@ -9811,8 +10172,8 @@ function PaywalloProvider({ children, config }) {
|
|
|
9811
10172
|
});
|
|
9812
10173
|
}, []);
|
|
9813
10174
|
const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
|
|
9814
|
-
const SCREEN_HEIGHT = (0, import_react25.useMemo)(() =>
|
|
9815
|
-
const slideAnim = (0, import_react25.useRef)(new
|
|
10175
|
+
const SCREEN_HEIGHT = (0, import_react25.useMemo)(() => import_react_native27.Dimensions.get("window").height, []);
|
|
10176
|
+
const slideAnim = (0, import_react25.useRef)(new import_react_native27.Animated.Value(SCREEN_HEIGHT)).current;
|
|
9816
10177
|
const handlePreloadedWebViewReady = (0, import_react25.useCallback)(() => {
|
|
9817
10178
|
if (PaywalloClient.getConfig()?.debug) {
|
|
9818
10179
|
console.log(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
|
|
@@ -9821,7 +10182,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
9821
10182
|
}, [preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
|
|
9822
10183
|
const handlePreloadedClose = (0, import_react25.useCallback)(() => {
|
|
9823
10184
|
const placement = preloadedWebView?.placement;
|
|
9824
|
-
|
|
10185
|
+
import_react_native27.Animated.timing(slideAnim, { toValue: SCREEN_HEIGHT, duration: 200, easing: import_react_native27.Easing.in(import_react_native27.Easing.cubic), useNativeDriver: true }).start(() => {
|
|
9825
10186
|
baseHandlePreloadedClose();
|
|
9826
10187
|
if (placement) triggerRepreload(placement);
|
|
9827
10188
|
});
|
|
@@ -9850,6 +10211,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
9850
10211
|
PaywalloClient.init(configRef.current).then(async () => {
|
|
9851
10212
|
if (!mounted) return;
|
|
9852
10213
|
setDistinctId(PaywalloClient.getDistinctId());
|
|
10214
|
+
startSuperwallAutoBridge(configRef.current.debug);
|
|
9853
10215
|
setIsInitialized(true);
|
|
9854
10216
|
if (!autoPreloadTriggeredRef.current) {
|
|
9855
10217
|
autoPreloadTriggeredRef.current = true;
|
|
@@ -10070,7 +10432,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
10070
10432
|
(0, import_react25.useLayoutEffect)(() => {
|
|
10071
10433
|
if (showingPreloadedWebView) {
|
|
10072
10434
|
slideAnim.setValue(SCREEN_HEIGHT);
|
|
10073
|
-
|
|
10435
|
+
import_react_native27.Animated.timing(slideAnim, { toValue: 0, duration: 200, easing: import_react_native27.Easing.out(import_react_native27.Easing.cubic), useNativeDriver: true }).start();
|
|
10074
10436
|
}
|
|
10075
10437
|
}, [showingPreloadedWebView, slideAnim, SCREEN_HEIGHT]);
|
|
10076
10438
|
const preloadStyle = [
|
|
@@ -10080,7 +10442,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
10080
10442
|
];
|
|
10081
10443
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(PaywalloContext.Provider, { value: contextValue, children: [
|
|
10082
10444
|
children,
|
|
10083
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PaywallErrorBoundary, { children: !!preloadedWebView?.craftData && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
10445
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PaywallErrorBoundary, { children: !!preloadedWebView?.craftData && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native27.Animated.View, { style: preloadStyle, pointerEvents: showingPreloadedWebView ? "auto" : "none", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
10084
10446
|
PaywallWebView,
|
|
10085
10447
|
{
|
|
10086
10448
|
paywallId: preloadedWebView.paywallId,
|
|
@@ -10111,7 +10473,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
10111
10473
|
) })
|
|
10112
10474
|
] });
|
|
10113
10475
|
}
|
|
10114
|
-
var styles3 =
|
|
10476
|
+
var styles3 = import_react_native27.StyleSheet.create({
|
|
10115
10477
|
preloadOverlay: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#ffffff" },
|
|
10116
10478
|
hidden: { zIndex: -1, opacity: 0, width: 1, height: 1, overflow: "hidden" },
|
|
10117
10479
|
visible: { zIndex: 9999, opacity: 1 }
|