@virex-tech/paywallo-sdk 2.2.5 → 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/src/main/java/com/paywallo/sdk/PaywalloEnvModule.kt +31 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloSdkPackage.kt +2 -1
- package/dist/index.d.mts +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +579 -247
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +569 -238
- 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;
|
|
@@ -2173,7 +2173,7 @@ function extractPayload(data) {
|
|
|
2173
2173
|
}
|
|
2174
2174
|
function setupForegroundHandler(deps, onReceive) {
|
|
2175
2175
|
const { bridge, tracker, debug } = deps;
|
|
2176
|
-
const
|
|
2176
|
+
const unsubscribe2 = bridge.onMessage(async (message) => {
|
|
2177
2177
|
const data = message.data ?? {};
|
|
2178
2178
|
const payload = extractPayload({
|
|
2179
2179
|
...data,
|
|
@@ -2191,7 +2191,7 @@ function setupForegroundHandler(deps, onReceive) {
|
|
|
2191
2191
|
messageId
|
|
2192
2192
|
});
|
|
2193
2193
|
});
|
|
2194
|
-
return
|
|
2194
|
+
return unsubscribe2;
|
|
2195
2195
|
}
|
|
2196
2196
|
var init_ForegroundHandler = __esm({
|
|
2197
2197
|
"src/domains/notifications/handlers/ForegroundHandler.ts"() {
|
|
@@ -2386,8 +2386,8 @@ var init_NotificationHandlerSetup = __esm({
|
|
|
2386
2386
|
// src/core/version.ts
|
|
2387
2387
|
function resolveVersion() {
|
|
2388
2388
|
try {
|
|
2389
|
-
if ("2.2.
|
|
2390
|
-
return "2.2.
|
|
2389
|
+
if ("2.2.6") {
|
|
2390
|
+
return "2.2.6";
|
|
2391
2391
|
}
|
|
2392
2392
|
} catch {
|
|
2393
2393
|
}
|
|
@@ -2459,9 +2459,6 @@ function mapPermissionStatus(status) {
|
|
|
2459
2459
|
function detectPlatform2() {
|
|
2460
2460
|
return import_react_native11.Platform.OS === "ios" ? "ios" : "android";
|
|
2461
2461
|
}
|
|
2462
|
-
function detectEnvironment() {
|
|
2463
|
-
return typeof __DEV__ !== "undefined" && __DEV__ ? "sandbox" : "production";
|
|
2464
|
-
}
|
|
2465
2462
|
async function getAppVersion() {
|
|
2466
2463
|
try {
|
|
2467
2464
|
const { collectDeviceInfo: collectDeviceInfo2 } = await Promise.resolve().then(() => (init_deviceInfo(), deviceInfo_exports));
|
|
@@ -2502,7 +2499,7 @@ async function clearPersistedToken(secureStorage2) {
|
|
|
2502
2499
|
} catch {
|
|
2503
2500
|
}
|
|
2504
2501
|
}
|
|
2505
|
-
async function buildRegistration(token, getDeviceId, getDistinctId) {
|
|
2502
|
+
async function buildRegistration(token, getDeviceId, getDistinctId, environment) {
|
|
2506
2503
|
const appVersion = await getAppVersion();
|
|
2507
2504
|
return {
|
|
2508
2505
|
token,
|
|
@@ -2513,7 +2510,7 @@ async function buildRegistration(token, getDeviceId, getDistinctId) {
|
|
|
2513
2510
|
deviceId: getDeviceId?.() ?? void 0,
|
|
2514
2511
|
locale: await getLocale(),
|
|
2515
2512
|
timezone: getTimezone2(),
|
|
2516
|
-
environment
|
|
2513
|
+
environment
|
|
2517
2514
|
};
|
|
2518
2515
|
}
|
|
2519
2516
|
async function registerToken(deps, token) {
|
|
@@ -2522,7 +2519,8 @@ async function registerToken(deps, token) {
|
|
|
2522
2519
|
if (debug) console.log("[Paywallo PUSH] httpClient not ready, skipping token registration");
|
|
2523
2520
|
return;
|
|
2524
2521
|
}
|
|
2525
|
-
const
|
|
2522
|
+
const { environment } = deps;
|
|
2523
|
+
const registration = await buildRegistration(token, getDeviceId, getDistinctId, environment);
|
|
2526
2524
|
try {
|
|
2527
2525
|
await httpClient.post(
|
|
2528
2526
|
TOKEN_ENDPOINT,
|
|
@@ -2572,7 +2570,8 @@ async function registerAndPersistToken(deps, token) {
|
|
|
2572
2570
|
appKey: deps.appKey,
|
|
2573
2571
|
getDeviceId: deps.getDeviceId,
|
|
2574
2572
|
getDistinctId: deps.getDistinctId,
|
|
2575
|
-
debug: deps.debug
|
|
2573
|
+
debug: deps.debug,
|
|
2574
|
+
environment: deps.environment
|
|
2576
2575
|
},
|
|
2577
2576
|
token
|
|
2578
2577
|
);
|
|
@@ -2589,7 +2588,8 @@ function subscribeTokenRefresh(bridge, deps, onToken) {
|
|
|
2589
2588
|
appKey: deps.appKey,
|
|
2590
2589
|
getDeviceId: deps.getDeviceId,
|
|
2591
2590
|
getDistinctId: deps.getDistinctId,
|
|
2592
|
-
debug: deps.debug
|
|
2591
|
+
debug: deps.debug,
|
|
2592
|
+
environment: deps.environment
|
|
2593
2593
|
},
|
|
2594
2594
|
newToken
|
|
2595
2595
|
).catch(() => void 0);
|
|
@@ -2797,6 +2797,7 @@ var init_NotificationsManager = __esm({
|
|
|
2797
2797
|
this.getDistinctId = null;
|
|
2798
2798
|
this.appKey = null;
|
|
2799
2799
|
this.apiBaseUrl = null;
|
|
2800
|
+
this.environment = "production";
|
|
2800
2801
|
this.tokenRefreshCleanup = null;
|
|
2801
2802
|
this.foregroundCleanup = null;
|
|
2802
2803
|
this.pendingHandlersConfig = null;
|
|
@@ -2816,6 +2817,7 @@ var init_NotificationsManager = __esm({
|
|
|
2816
2817
|
this.secureStorage = deps.secureStorage;
|
|
2817
2818
|
this.getDeviceId = deps.getDeviceId;
|
|
2818
2819
|
if (deps.getDistinctId) this.getDistinctId = deps.getDistinctId;
|
|
2820
|
+
if (deps.environment) this.environment = deps.environment;
|
|
2819
2821
|
}
|
|
2820
2822
|
async initialize(config) {
|
|
2821
2823
|
this.applyConfig(config);
|
|
@@ -2858,7 +2860,8 @@ var init_NotificationsManager = __esm({
|
|
|
2858
2860
|
appKey: this.appKey,
|
|
2859
2861
|
getDeviceId: this.getDeviceId,
|
|
2860
2862
|
getDistinctId: this.getDistinctId,
|
|
2861
|
-
debug: this.debug
|
|
2863
|
+
debug: this.debug,
|
|
2864
|
+
environment: this.environment
|
|
2862
2865
|
};
|
|
2863
2866
|
}
|
|
2864
2867
|
async doRegisterToken(token) {
|
|
@@ -3919,6 +3922,32 @@ var init_IAPTransactionEmitter = __esm({
|
|
|
3919
3922
|
if (this.getDebug()) console.log("[Paywallo PURCHASE] transaction event emission failed", err);
|
|
3920
3923
|
}
|
|
3921
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
|
+
}
|
|
3922
3951
|
/**
|
|
3923
3952
|
* Forwards a native transaction update to the EventBatcher.
|
|
3924
3953
|
* Public so tests can exercise the wiring without a fake NativeEventEmitter.
|
|
@@ -3941,9 +3970,9 @@ var init_IAPTransactionEmitter = __esm({
|
|
|
3941
3970
|
product_id: update.productId
|
|
3942
3971
|
};
|
|
3943
3972
|
if (update.type !== "canceled") {
|
|
3944
|
-
const
|
|
3945
|
-
const amount = update.amount ??
|
|
3946
|
-
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;
|
|
3947
3976
|
if (typeof amount === "number" && amount > 0) properties.amount = amount;
|
|
3948
3977
|
if (currency) properties.currency = currency;
|
|
3949
3978
|
}
|
|
@@ -4122,10 +4151,10 @@ var init_OfflineQueueStorage = __esm({
|
|
|
4122
4151
|
init_NativeStorage();
|
|
4123
4152
|
init_QueueJournal();
|
|
4124
4153
|
OfflineQueueStorage = class {
|
|
4125
|
-
constructor(config, emit,
|
|
4154
|
+
constructor(config, emit, log2) {
|
|
4126
4155
|
this.config = config;
|
|
4127
4156
|
this.emit = emit;
|
|
4128
|
-
this.log =
|
|
4157
|
+
this.log = log2;
|
|
4129
4158
|
}
|
|
4130
4159
|
async loadFromStorage(queue) {
|
|
4131
4160
|
const { merged, recovered, hadJournal } = await mergeJournalIntoMain(
|
|
@@ -4744,6 +4773,7 @@ var init_QueueProcessor = __esm({
|
|
|
4744
4773
|
for (const item of batch) {
|
|
4745
4774
|
retryIds.add(item.id);
|
|
4746
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) });
|
|
4747
4777
|
this.log("Event batch threw network error - will retry", {
|
|
4748
4778
|
size: batch.length,
|
|
4749
4779
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -4761,14 +4791,25 @@ var init_QueueProcessor = __esm({
|
|
|
4761
4791
|
await offlineQueue.removeItem(item.id);
|
|
4762
4792
|
failed++;
|
|
4763
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
|
+
}
|
|
4764
4803
|
this.log("Event batch failed permanently (4xx) - removing from queue", {
|
|
4765
4804
|
status: response.status,
|
|
4766
|
-
size: batch.length
|
|
4805
|
+
size: batch.length,
|
|
4806
|
+
isDuplicate
|
|
4767
4807
|
});
|
|
4768
4808
|
} else {
|
|
4769
4809
|
for (const item of batch) {
|
|
4770
4810
|
retryIds.add(item.id);
|
|
4771
4811
|
}
|
|
4812
|
+
console.error("[Paywallo:QUEUE] event batch server error (5xx) \u2014 will retry", { status: response.status, size: batch.length });
|
|
4772
4813
|
this.log("Event batch failed with server error - will retry", {
|
|
4773
4814
|
status: response.status,
|
|
4774
4815
|
size: batch.length
|
|
@@ -4798,6 +4839,7 @@ var init_QueueProcessor = __esm({
|
|
|
4798
4839
|
skipRetry: false
|
|
4799
4840
|
});
|
|
4800
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) });
|
|
4801
4843
|
this.log("Request threw network error - will retry", {
|
|
4802
4844
|
id: item.id,
|
|
4803
4845
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -5094,6 +5136,7 @@ var init_IAPService = __esm({
|
|
|
5094
5136
|
this.purchaseInFlight = productId;
|
|
5095
5137
|
if (this.debug) console.log(`[Paywallo PURCHASE] starting ${productId}`);
|
|
5096
5138
|
try {
|
|
5139
|
+
void this.emitter.emitCheckoutStarted(productId);
|
|
5097
5140
|
const purchase = await nativeStoreKit.purchase(productId);
|
|
5098
5141
|
if (!purchase) {
|
|
5099
5142
|
if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} cancelled`);
|
|
@@ -5883,10 +5926,10 @@ var init_PlanService = __esm({
|
|
|
5883
5926
|
}
|
|
5884
5927
|
async getCurrentPlan(forceRefresh = false) {
|
|
5885
5928
|
if (!forceRefresh) {
|
|
5886
|
-
const
|
|
5887
|
-
if (
|
|
5888
|
-
const current =
|
|
5889
|
-
(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
|
|
5890
5933
|
);
|
|
5891
5934
|
return current ?? null;
|
|
5892
5935
|
}
|
|
@@ -5913,8 +5956,8 @@ var init_PlanService = __esm({
|
|
|
5913
5956
|
}
|
|
5914
5957
|
async getAllPlans(forceRefresh = false) {
|
|
5915
5958
|
if (!forceRefresh) {
|
|
5916
|
-
const
|
|
5917
|
-
if (
|
|
5959
|
+
const cached2 = this.cache.get();
|
|
5960
|
+
if (cached2 && !cached2.isStale) return cached2.data;
|
|
5918
5961
|
}
|
|
5919
5962
|
if (!this.cache.get()) {
|
|
5920
5963
|
try {
|
|
@@ -6032,6 +6075,163 @@ var init_analytics = __esm({
|
|
|
6032
6075
|
}
|
|
6033
6076
|
});
|
|
6034
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
|
+
|
|
6035
6235
|
// src/core/PaywalloAnalytics.ts
|
|
6036
6236
|
function isValidEventName(name) {
|
|
6037
6237
|
if (name.startsWith("$")) return /^\$[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
|
|
@@ -6040,7 +6240,7 @@ function isValidEventName(name) {
|
|
|
6040
6240
|
async function trackEvent(apiClient, state, eventName, options) {
|
|
6041
6241
|
const distinctId = identityManager.getDistinctId();
|
|
6042
6242
|
if (!distinctId) {
|
|
6043
|
-
|
|
6243
|
+
console.error("[Paywallo:DROP] event dropped \u2014 no distinctId available yet", { eventName });
|
|
6044
6244
|
return;
|
|
6045
6245
|
}
|
|
6046
6246
|
if (!isValidEventName(eventName)) {
|
|
@@ -6049,10 +6249,14 @@ async function trackEvent(apiClient, state, eventName, options) {
|
|
|
6049
6249
|
}
|
|
6050
6250
|
if (state.config?.debug && eventName.startsWith("$purchase")) console.log(`[Paywallo TRACK] ${eventName}`, options?.properties ?? {});
|
|
6051
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 } : {};
|
|
6052
6256
|
await apiClient.trackEvent(
|
|
6053
6257
|
eventName,
|
|
6054
6258
|
distinctId,
|
|
6055
|
-
{ ...
|
|
6259
|
+
{ ...identityProps, ...options?.properties, ...sessionProp },
|
|
6056
6260
|
options?.timestamp,
|
|
6057
6261
|
options?.priority ? { priority: options.priority } : void 0
|
|
6058
6262
|
);
|
|
@@ -6082,6 +6286,7 @@ var init_PaywalloAnalytics = __esm({
|
|
|
6082
6286
|
init_identity();
|
|
6083
6287
|
init_session();
|
|
6084
6288
|
init_ClientError();
|
|
6289
|
+
init_eventFamilies();
|
|
6085
6290
|
}
|
|
6086
6291
|
});
|
|
6087
6292
|
|
|
@@ -6346,49 +6551,49 @@ var init_SubscriptionCache = __esm({
|
|
|
6346
6551
|
await this.storage.remove(keyFor(distinctId));
|
|
6347
6552
|
return null;
|
|
6348
6553
|
}
|
|
6349
|
-
const
|
|
6350
|
-
if (
|
|
6351
|
-
|
|
6554
|
+
const cached2 = parsed;
|
|
6555
|
+
if (cached2.data.subscription?.expiresAt) {
|
|
6556
|
+
cached2.data.subscription.expiresAt = new Date(cached2.data.subscription.expiresAt);
|
|
6352
6557
|
}
|
|
6353
|
-
if (
|
|
6354
|
-
|
|
6355
|
-
|
|
6558
|
+
if (cached2.data.subscription?.originalPurchaseDate) {
|
|
6559
|
+
cached2.data.subscription.originalPurchaseDate = new Date(
|
|
6560
|
+
cached2.data.subscription.originalPurchaseDate
|
|
6356
6561
|
);
|
|
6357
6562
|
}
|
|
6358
|
-
if (
|
|
6359
|
-
|
|
6360
|
-
|
|
6563
|
+
if (cached2.data.subscription?.latestPurchaseDate) {
|
|
6564
|
+
cached2.data.subscription.latestPurchaseDate = new Date(
|
|
6565
|
+
cached2.data.subscription.latestPurchaseDate
|
|
6361
6566
|
);
|
|
6362
6567
|
}
|
|
6363
|
-
if (
|
|
6364
|
-
|
|
6365
|
-
|
|
6568
|
+
if (cached2.data.subscription?.cancellationDate) {
|
|
6569
|
+
cached2.data.subscription.cancellationDate = new Date(
|
|
6570
|
+
cached2.data.subscription.cancellationDate
|
|
6366
6571
|
);
|
|
6367
6572
|
}
|
|
6368
|
-
if (
|
|
6369
|
-
|
|
6370
|
-
|
|
6573
|
+
if (cached2.data.subscription?.gracePeriodExpiresAt) {
|
|
6574
|
+
cached2.data.subscription.gracePeriodExpiresAt = new Date(
|
|
6575
|
+
cached2.data.subscription.gracePeriodExpiresAt
|
|
6371
6576
|
);
|
|
6372
6577
|
}
|
|
6373
|
-
|
|
6374
|
-
if (!
|
|
6375
|
-
this.memoryCache.set(distinctId,
|
|
6578
|
+
cached2.isStale = this.isExpired(cached2);
|
|
6579
|
+
if (!cached2.isStale) {
|
|
6580
|
+
this.memoryCache.set(distinctId, cached2);
|
|
6376
6581
|
}
|
|
6377
|
-
return
|
|
6582
|
+
return cached2;
|
|
6378
6583
|
} catch {
|
|
6379
6584
|
return null;
|
|
6380
6585
|
}
|
|
6381
6586
|
}
|
|
6382
6587
|
async set(distinctId, data) {
|
|
6383
6588
|
if (!distinctId) return;
|
|
6384
|
-
const
|
|
6589
|
+
const cached2 = {
|
|
6385
6590
|
data,
|
|
6386
6591
|
cachedAt: Date.now(),
|
|
6387
6592
|
isStale: false
|
|
6388
6593
|
};
|
|
6389
|
-
this.memoryCache.set(distinctId,
|
|
6594
|
+
this.memoryCache.set(distinctId, cached2);
|
|
6390
6595
|
try {
|
|
6391
|
-
await this.storage.set(keyFor(distinctId), JSON.stringify(
|
|
6596
|
+
await this.storage.set(keyFor(distinctId), JSON.stringify(cached2));
|
|
6392
6597
|
await this.addToIndex(distinctId);
|
|
6393
6598
|
} catch {
|
|
6394
6599
|
}
|
|
@@ -6432,8 +6637,8 @@ var init_SubscriptionCache = __esm({
|
|
|
6432
6637
|
...allKeys.map((k) => this.storage.remove(keyFor(k)))
|
|
6433
6638
|
]);
|
|
6434
6639
|
}
|
|
6435
|
-
isExpired(
|
|
6436
|
-
return Date.now() -
|
|
6640
|
+
isExpired(cached2) {
|
|
6641
|
+
return Date.now() - cached2.cachedAt > this.ttl;
|
|
6437
6642
|
}
|
|
6438
6643
|
setTTL(ttl) {
|
|
6439
6644
|
this.ttl = ttl;
|
|
@@ -6489,9 +6694,9 @@ var init_SubscriptionManager = __esm({
|
|
|
6489
6694
|
return this.getEmptyStatus();
|
|
6490
6695
|
}
|
|
6491
6696
|
if (!forceRefresh) {
|
|
6492
|
-
const
|
|
6493
|
-
if (
|
|
6494
|
-
return
|
|
6697
|
+
const cached2 = await this.cache.get(this.cacheKey());
|
|
6698
|
+
if (cached2 && !cached2.isStale) {
|
|
6699
|
+
return cached2.data;
|
|
6495
6700
|
}
|
|
6496
6701
|
}
|
|
6497
6702
|
try {
|
|
@@ -6501,9 +6706,9 @@ var init_SubscriptionManager = __esm({
|
|
|
6501
6706
|
return status;
|
|
6502
6707
|
} catch (error) {
|
|
6503
6708
|
this.log("Failed to fetch subscription status", error);
|
|
6504
|
-
const
|
|
6505
|
-
if (
|
|
6506
|
-
return
|
|
6709
|
+
const cached2 = await this.cache.get(this.cacheKey());
|
|
6710
|
+
if (cached2) {
|
|
6711
|
+
return cached2.data;
|
|
6507
6712
|
}
|
|
6508
6713
|
return this.getEmptyStatus();
|
|
6509
6714
|
}
|
|
@@ -6918,163 +7123,6 @@ var init_ApiClientQueue = __esm({
|
|
|
6918
7123
|
}
|
|
6919
7124
|
});
|
|
6920
7125
|
|
|
6921
|
-
// src/core/events/schemas.ts
|
|
6922
|
-
var import_zod, lifecycleEventSchema, identifyEventSchema, paywallEventSchema, transactionEventSchema, onboardingEventSchema, notificationEventSchema, customEventSchema, EVENT_SCHEMAS;
|
|
6923
|
-
var init_schemas = __esm({
|
|
6924
|
-
"src/core/events/schemas.ts"() {
|
|
6925
|
-
"use strict";
|
|
6926
|
-
import_zod = require("zod");
|
|
6927
|
-
lifecycleEventSchema = import_zod.z.object({
|
|
6928
|
-
type: import_zod.z.enum([
|
|
6929
|
-
"install",
|
|
6930
|
-
"cold_start",
|
|
6931
|
-
"foreground",
|
|
6932
|
-
"background",
|
|
6933
|
-
"session_start",
|
|
6934
|
-
"session_end"
|
|
6935
|
-
]),
|
|
6936
|
-
session_id: import_zod.z.string().optional(),
|
|
6937
|
-
duration_s: import_zod.z.number().nonnegative().optional(),
|
|
6938
|
-
first_ever: import_zod.z.boolean().optional(),
|
|
6939
|
-
// Contexto leve transportado em lifecycle (útil pra cold_start/install)
|
|
6940
|
-
app_version: import_zod.z.string().optional(),
|
|
6941
|
-
device_type: import_zod.z.enum(["ios", "android"]).optional(),
|
|
6942
|
-
os_version: import_zod.z.string().optional(),
|
|
6943
|
-
// Timestamps opcionais — o EventBatcher injeta `timestamp` no envelope
|
|
6944
|
-
started_at: import_zod.z.string().optional(),
|
|
6945
|
-
ended_at: import_zod.z.string().optional()
|
|
6946
|
-
}).strict();
|
|
6947
|
-
identifyEventSchema = import_zod.z.object({
|
|
6948
|
-
distinct_id: import_zod.z.string().min(1),
|
|
6949
|
-
email: import_zod.z.email().optional(),
|
|
6950
|
-
traits: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
|
|
6951
|
-
attribution: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
|
|
6952
|
-
device: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
6953
|
-
}).strict();
|
|
6954
|
-
paywallEventSchema = import_zod.z.object({
|
|
6955
|
-
type: import_zod.z.enum(["viewed", "closed", "purchased"]),
|
|
6956
|
-
paywall_id: import_zod.z.string().min(1),
|
|
6957
|
-
placement: import_zod.z.string().optional(),
|
|
6958
|
-
variant_id: import_zod.z.string().optional(),
|
|
6959
|
-
variant_key: import_zod.z.string().optional(),
|
|
6960
|
-
campaign_id: import_zod.z.string().optional(),
|
|
6961
|
-
// `closed`-specific
|
|
6962
|
-
closed_at: import_zod.z.string().optional(),
|
|
6963
|
-
duration_s: import_zod.z.number().nonnegative().optional(),
|
|
6964
|
-
close_reason: import_zod.z.enum(["dismiss", "cta", "purchase", "error", "timeout"]).optional(),
|
|
6965
|
-
scroll_depth: import_zod.z.number().min(0).max(1).optional(),
|
|
6966
|
-
// `purchased`-specific (mínimo — transaction canônica leva detalhes)
|
|
6967
|
-
product_id: import_zod.z.string().optional()
|
|
6968
|
-
}).strict();
|
|
6969
|
-
transactionEventSchema = import_zod.z.object({
|
|
6970
|
-
type: import_zod.z.enum([
|
|
6971
|
-
"completed",
|
|
6972
|
-
"failed",
|
|
6973
|
-
"refunded",
|
|
6974
|
-
"renewed",
|
|
6975
|
-
"canceled",
|
|
6976
|
-
"expired"
|
|
6977
|
-
]),
|
|
6978
|
-
tx_id: import_zod.z.string().min(1),
|
|
6979
|
-
amount: import_zod.z.number().optional(),
|
|
6980
|
-
currency: import_zod.z.string().length(3).optional(),
|
|
6981
|
-
product_id: import_zod.z.string().optional(),
|
|
6982
|
-
subscription_id: import_zod.z.string().optional(),
|
|
6983
|
-
paywall_id: import_zod.z.string().optional(),
|
|
6984
|
-
variant_id: import_zod.z.string().optional(),
|
|
6985
|
-
reason: import_zod.z.string().optional()
|
|
6986
|
-
}).strict();
|
|
6987
|
-
onboardingEventSchema = import_zod.z.object({
|
|
6988
|
-
type: import_zod.z.enum(["step", "complete", "drop"]),
|
|
6989
|
-
step_index: import_zod.z.number().int().nonnegative().optional(),
|
|
6990
|
-
step_name: import_zod.z.string().optional(),
|
|
6991
|
-
variant_key: import_zod.z.string().optional(),
|
|
6992
|
-
time_on_prev_s: import_zod.z.number().nonnegative().optional()
|
|
6993
|
-
}).strict();
|
|
6994
|
-
notificationEventSchema = import_zod.z.object({
|
|
6995
|
-
type: import_zod.z.enum(["delivered", "displayed", "clicked", "dismissed"]),
|
|
6996
|
-
notification_id: import_zod.z.string().optional(),
|
|
6997
|
-
campaign_id: import_zod.z.string().optional(),
|
|
6998
|
-
variant_key: import_zod.z.string().nullable().optional(),
|
|
6999
|
-
message_id: import_zod.z.string().optional(),
|
|
7000
|
-
device_id: import_zod.z.string().optional(),
|
|
7001
|
-
push_platform: import_zod.z.enum(["ios", "android"]).optional(),
|
|
7002
|
-
timezone: import_zod.z.string().optional(),
|
|
7003
|
-
failure_reason: import_zod.z.string().optional(),
|
|
7004
|
-
app_user_id: import_zod.z.string().optional()
|
|
7005
|
-
}).strict();
|
|
7006
|
-
customEventSchema = import_zod.z.object({
|
|
7007
|
-
name: import_zod.z.string().min(1).regex(
|
|
7008
|
-
/^(\$[a-zA-Z0-9_]+|[a-z][a-z0-9_]*)$/,
|
|
7009
|
-
"custom event name must be snake_case or $reserved"
|
|
7010
|
-
),
|
|
7011
|
-
props: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
7012
|
-
}).strict();
|
|
7013
|
-
EVENT_SCHEMAS = {
|
|
7014
|
-
lifecycle: lifecycleEventSchema,
|
|
7015
|
-
identify: identifyEventSchema,
|
|
7016
|
-
paywall: paywallEventSchema,
|
|
7017
|
-
transaction: transactionEventSchema,
|
|
7018
|
-
onboarding: onboardingEventSchema,
|
|
7019
|
-
notification: notificationEventSchema,
|
|
7020
|
-
custom: customEventSchema
|
|
7021
|
-
};
|
|
7022
|
-
}
|
|
7023
|
-
});
|
|
7024
|
-
|
|
7025
|
-
// src/core/events/eventFamilies.ts
|
|
7026
|
-
function isDeprecatedEvent(eventName) {
|
|
7027
|
-
return DEPRECATED_EVENT_NAMES.includes(eventName);
|
|
7028
|
-
}
|
|
7029
|
-
function isCanonicalFamily(name) {
|
|
7030
|
-
return CANONICAL_FAMILIES.includes(name);
|
|
7031
|
-
}
|
|
7032
|
-
function detectFamily(eventName) {
|
|
7033
|
-
if (isCanonicalFamily(eventName) && eventName !== "custom") {
|
|
7034
|
-
return eventName;
|
|
7035
|
-
}
|
|
7036
|
-
return "custom";
|
|
7037
|
-
}
|
|
7038
|
-
function validateEvent(eventName, properties) {
|
|
7039
|
-
const family = detectFamily(eventName);
|
|
7040
|
-
const schema = EVENT_SCHEMAS[family];
|
|
7041
|
-
const input = family === "custom" ? { name: eventName, ...properties ? { props: properties } : {} } : properties ?? {};
|
|
7042
|
-
const parsed = schema.safeParse(input);
|
|
7043
|
-
if (parsed.success) {
|
|
7044
|
-
return { ok: true, family, data: parsed.data };
|
|
7045
|
-
}
|
|
7046
|
-
return { ok: false, family, error: parsed.error };
|
|
7047
|
-
}
|
|
7048
|
-
function formatValidationError(error) {
|
|
7049
|
-
return error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
7050
|
-
}
|
|
7051
|
-
var CANONICAL_FAMILIES, DEPRECATED_EVENT_NAMES;
|
|
7052
|
-
var init_eventFamilies = __esm({
|
|
7053
|
-
"src/core/events/eventFamilies.ts"() {
|
|
7054
|
-
"use strict";
|
|
7055
|
-
init_schemas();
|
|
7056
|
-
CANONICAL_FAMILIES = [
|
|
7057
|
-
"lifecycle",
|
|
7058
|
-
"identify",
|
|
7059
|
-
"paywall",
|
|
7060
|
-
"transaction",
|
|
7061
|
-
"onboarding",
|
|
7062
|
-
"notification",
|
|
7063
|
-
"custom"
|
|
7064
|
-
];
|
|
7065
|
-
DEPRECATED_EVENT_NAMES = [
|
|
7066
|
-
"$paywall_purchased",
|
|
7067
|
-
"$paywall_product_selected",
|
|
7068
|
-
"$core_action",
|
|
7069
|
-
"$campaign_impression",
|
|
7070
|
-
"$app_open",
|
|
7071
|
-
"$app_background",
|
|
7072
|
-
"$app_foreground",
|
|
7073
|
-
"session.end"
|
|
7074
|
-
];
|
|
7075
|
-
}
|
|
7076
|
-
});
|
|
7077
|
-
|
|
7078
7126
|
// src/core/eventEnvelopeV2.ts
|
|
7079
7127
|
function buildV2Envelope(events, providerContext) {
|
|
7080
7128
|
const context = { ...providerContext };
|
|
@@ -7102,6 +7150,9 @@ function buildV2Envelope(events, providerContext) {
|
|
|
7102
7150
|
if (context.distinct_id === void 0 && events.length > 0) {
|
|
7103
7151
|
context.distinct_id = events[0].distinctId;
|
|
7104
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
|
+
}
|
|
7105
7156
|
return { context, events: v2Events };
|
|
7106
7157
|
}
|
|
7107
7158
|
function resolveProviderContext(provider) {
|
|
@@ -7155,12 +7206,18 @@ var init_eventEnvelopeV2 = __esm({
|
|
|
7155
7206
|
session_id: import_zod2.z.string().optional(),
|
|
7156
7207
|
device_id: import_zod2.z.string().optional(),
|
|
7157
7208
|
app_version: import_zod2.z.string().optional(),
|
|
7209
|
+
app_build: import_zod2.z.string().optional(),
|
|
7210
|
+
bundle_id: import_zod2.z.string().optional(),
|
|
7158
7211
|
sdk_version: import_zod2.z.string().optional(),
|
|
7159
7212
|
platform: import_zod2.z.string().optional(),
|
|
7160
7213
|
os_version: import_zod2.z.string().optional(),
|
|
7161
7214
|
device_model: import_zod2.z.string().optional(),
|
|
7162
7215
|
timezone: import_zod2.z.string().optional(),
|
|
7163
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(),
|
|
7164
7221
|
attribution: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional(),
|
|
7165
7222
|
ids: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional()
|
|
7166
7223
|
});
|
|
@@ -7173,12 +7230,21 @@ var init_eventEnvelopeV2 = __esm({
|
|
|
7173
7230
|
session_id: ["session_id", "sessionId"],
|
|
7174
7231
|
device_id: ["device_id", "deviceId"],
|
|
7175
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"],
|
|
7176
7238
|
sdk_version: ["sdk_version", "sdkVersion"],
|
|
7177
7239
|
platform: ["platform"],
|
|
7178
7240
|
os_version: ["os_version", "osVersion", "systemVersion"],
|
|
7179
7241
|
device_model: ["device_model", "deviceModel", "model"],
|
|
7180
7242
|
timezone: ["timezone", "timeZone"],
|
|
7181
7243
|
locale: ["locale"],
|
|
7244
|
+
carrier: ["carrier_ctx"],
|
|
7245
|
+
screen_width: ["screen_width"],
|
|
7246
|
+
screen_height: ["screen_height"],
|
|
7247
|
+
screen_density: ["screen_density"],
|
|
7182
7248
|
attribution: ["attribution"],
|
|
7183
7249
|
ids: ["ids"]
|
|
7184
7250
|
};
|
|
@@ -7305,8 +7371,8 @@ var init_EventBatcher = __esm({
|
|
|
7305
7371
|
for (const event of drained) {
|
|
7306
7372
|
try {
|
|
7307
7373
|
await this.postBatch([event], `event_critical:${event.eventName}`, "critical");
|
|
7308
|
-
} catch {
|
|
7309
|
-
|
|
7374
|
+
} catch (err) {
|
|
7375
|
+
console.error("[Paywallo:CRITICAL] critical event post failed", { eventName: event.eventName, error: err instanceof Error ? err.message : String(err) });
|
|
7310
7376
|
}
|
|
7311
7377
|
}
|
|
7312
7378
|
}
|
|
@@ -7319,8 +7385,8 @@ var init_EventBatcher = __esm({
|
|
|
7319
7385
|
}
|
|
7320
7386
|
try {
|
|
7321
7387
|
await this.postBatch(batch, `event_batch:${batch.length}`);
|
|
7322
|
-
} catch {
|
|
7323
|
-
|
|
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) });
|
|
7324
7390
|
}
|
|
7325
7391
|
}
|
|
7326
7392
|
async postBatch(events, label, priority = "normal") {
|
|
@@ -7335,6 +7401,7 @@ var init_EventBatcher = __esm({
|
|
|
7335
7401
|
const parsed = v2EnvelopeSchema.safeParse(envelope);
|
|
7336
7402
|
if (!parsed.success) {
|
|
7337
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 });
|
|
7338
7405
|
throw parsed.error;
|
|
7339
7406
|
}
|
|
7340
7407
|
await this.post(V2_BATCH_ENDPOINT, parsed.data, label, priority);
|
|
@@ -7917,7 +7984,8 @@ function setupNotifications(apiClient, config, environment, eventPipeline) {
|
|
|
7917
7984
|
httpClient: apiClient.getHttpClient(),
|
|
7918
7985
|
secureStorage: new SecureStorage(config.debug),
|
|
7919
7986
|
getDeviceId: () => identityManager.getDeviceId(),
|
|
7920
|
-
getDistinctId: () => identityManager.getDistinctId()
|
|
7987
|
+
getDistinctId: () => identityManager.getDistinctId(),
|
|
7988
|
+
environment: environment === "Sandbox" ? "sandbox" : "production"
|
|
7921
7989
|
});
|
|
7922
7990
|
notificationsManager.setupHandlers({ eventBatcher: eventPipeline });
|
|
7923
7991
|
notificationsManager.initialize({
|
|
@@ -8001,6 +8069,36 @@ var init_PaywalloState = __esm({
|
|
|
8001
8069
|
}
|
|
8002
8070
|
});
|
|
8003
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
|
+
|
|
8004
8102
|
// src/core/PaywalloInitializer.ts
|
|
8005
8103
|
async function initWithRetry(state, config) {
|
|
8006
8104
|
while (state.initAttempts <= MAX_INIT_RETRIES) {
|
|
@@ -8080,7 +8178,13 @@ async function resolveSessionFlags(state, keys, apiClient, distinctId) {
|
|
|
8080
8178
|
}
|
|
8081
8179
|
}
|
|
8082
8180
|
async function doInit(state, config) {
|
|
8083
|
-
|
|
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
|
+
}
|
|
8084
8188
|
const offlineQueueEnabled = config.offlineQueueEnabled !== false;
|
|
8085
8189
|
await Promise.all([
|
|
8086
8190
|
networkMonitor.initialize({ debug: config.debug }),
|
|
@@ -8168,15 +8272,31 @@ async function doInit(state, config) {
|
|
|
8168
8272
|
timezone = opts.timeZone || void 0;
|
|
8169
8273
|
} catch {
|
|
8170
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
|
+
}
|
|
8171
8285
|
return {
|
|
8172
8286
|
distinct_id: identityManager.getDistinctId() || void 0,
|
|
8173
8287
|
device_id: identityManager.getDeviceId() ?? void 0,
|
|
8174
8288
|
session_id: sessionManager.getSessionId() ?? void 0,
|
|
8175
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,
|
|
8176
8292
|
os_version: device?.systemVersion && device.systemVersion !== "0.0.0" ? device.systemVersion : void 0,
|
|
8177
8293
|
device_model: device?.modelId || device?.model || void 0,
|
|
8178
8294
|
timezone,
|
|
8179
8295
|
locale,
|
|
8296
|
+
carrier: device?.carrier || void 0,
|
|
8297
|
+
screen_width: screenWidth,
|
|
8298
|
+
screen_height: screenHeight,
|
|
8299
|
+
screen_density: screenDensity,
|
|
8180
8300
|
...attribution && Object.keys(attribution).length > 0 && { attribution },
|
|
8181
8301
|
...ids && Object.keys(ids).length > 0 && { ids }
|
|
8182
8302
|
};
|
|
@@ -8226,9 +8346,11 @@ async function doInit(state, config) {
|
|
|
8226
8346
|
setupAutoPreload(state, apiClient, config, distinctId);
|
|
8227
8347
|
startBatchPreload(apiClient, config.debug);
|
|
8228
8348
|
}
|
|
8349
|
+
var import_react_native25;
|
|
8229
8350
|
var init_PaywalloInitializer = __esm({
|
|
8230
8351
|
"src/core/PaywalloInitializer.ts"() {
|
|
8231
8352
|
"use strict";
|
|
8353
|
+
import_react_native25 = require("react-native");
|
|
8232
8354
|
init_identity();
|
|
8233
8355
|
init_AdvertisingIdManager();
|
|
8234
8356
|
init_DeepLinkAttributionCapture();
|
|
@@ -8248,6 +8370,7 @@ var init_PaywalloInitializer = __esm({
|
|
|
8248
8370
|
init_PaywalloState();
|
|
8249
8371
|
init_queue();
|
|
8250
8372
|
init_NativeStorage();
|
|
8373
|
+
init_detectEnvironment();
|
|
8251
8374
|
}
|
|
8252
8375
|
});
|
|
8253
8376
|
|
|
@@ -8652,7 +8775,7 @@ __export(OfferingService_exports, {
|
|
|
8652
8775
|
offeringService: () => offeringService
|
|
8653
8776
|
});
|
|
8654
8777
|
function resolveProductId(raw) {
|
|
8655
|
-
const isIos =
|
|
8778
|
+
const isIos = import_react_native26.Platform.OS === "ios";
|
|
8656
8779
|
const productId = isIos ? raw.apple_product_id : raw.google_product_id;
|
|
8657
8780
|
return productId ?? "";
|
|
8658
8781
|
}
|
|
@@ -8671,7 +8794,7 @@ function mapProduct2(raw) {
|
|
|
8671
8794
|
};
|
|
8672
8795
|
}
|
|
8673
8796
|
function pickPlatformProduct(products) {
|
|
8674
|
-
const isIos =
|
|
8797
|
+
const isIos = import_react_native26.Platform.OS === "ios";
|
|
8675
8798
|
const match = products.find((p) => isIos ? p.apple_product_id : p.google_product_id);
|
|
8676
8799
|
return match ?? products[0] ?? null;
|
|
8677
8800
|
}
|
|
@@ -8687,11 +8810,11 @@ function mapOffering(raw) {
|
|
|
8687
8810
|
product: mapProduct2(rawProduct)
|
|
8688
8811
|
};
|
|
8689
8812
|
}
|
|
8690
|
-
var
|
|
8813
|
+
var import_react_native26, OFFERINGS_CACHE_KEY, DEFAULT_TTL3, OfferingService, offeringService;
|
|
8691
8814
|
var init_OfferingService = __esm({
|
|
8692
8815
|
"src/domains/offering/OfferingService.ts"() {
|
|
8693
8816
|
"use strict";
|
|
8694
|
-
|
|
8817
|
+
import_react_native26 = require("react-native");
|
|
8695
8818
|
init_SecureStorage();
|
|
8696
8819
|
OFFERINGS_CACHE_KEY = "@paywallo:offerings_cache";
|
|
8697
8820
|
DEFAULT_TTL3 = 5 * 60 * 1e3;
|
|
@@ -8705,8 +8828,8 @@ var init_OfferingService = __esm({
|
|
|
8705
8828
|
this.config = config;
|
|
8706
8829
|
}
|
|
8707
8830
|
async getOfferings(ids) {
|
|
8708
|
-
const
|
|
8709
|
-
if (
|
|
8831
|
+
const cached2 = this.getMemCache();
|
|
8832
|
+
if (cached2 && !cached2.isStale) return this.filterByIds(cached2.data, ids);
|
|
8710
8833
|
if (!this.cache) {
|
|
8711
8834
|
try {
|
|
8712
8835
|
const stored = await secureStorage.get(OFFERINGS_CACHE_KEY);
|
|
@@ -9322,9 +9445,10 @@ function useSubscription() {
|
|
|
9322
9445
|
|
|
9323
9446
|
// src/PaywalloProvider.tsx
|
|
9324
9447
|
var import_react25 = require("react");
|
|
9325
|
-
var
|
|
9448
|
+
var import_react_native27 = require("react-native");
|
|
9326
9449
|
init_PaywalloClient();
|
|
9327
9450
|
init_SecureStorage();
|
|
9451
|
+
init_NativeStorage();
|
|
9328
9452
|
init_campaign();
|
|
9329
9453
|
init_paywall();
|
|
9330
9454
|
init_paywall();
|
|
@@ -9356,12 +9480,13 @@ async function detectOsVersion() {
|
|
|
9356
9480
|
}
|
|
9357
9481
|
|
|
9358
9482
|
// src/hooks/useAppAutoEvents.ts
|
|
9359
|
-
var FIRST_SEEN_KEY = "@paywallo:
|
|
9483
|
+
var FIRST_SEEN_KEY = "@paywallo:firstSeen_v226";
|
|
9360
9484
|
var DEBOUNCE_MS = 1e3;
|
|
9361
9485
|
function useAppAutoEvents(config) {
|
|
9362
9486
|
const { isInitialized, getAppVersion: getAppVersion2, getSessionId, emitLifecycle, storageGet, storageSet, debug } = config;
|
|
9363
9487
|
const didRunRef = (0, import_react20.useRef)(false);
|
|
9364
9488
|
(0, import_react20.useEffect)(() => {
|
|
9489
|
+
if (debug) console.log("[Paywallo EVENTS] useEffect fired", { isInitialized, didRunRefCurrent: didRunRef.current });
|
|
9365
9490
|
if (!isInitialized || didRunRef.current) return;
|
|
9366
9491
|
const timer = setTimeout(() => {
|
|
9367
9492
|
if (didRunRef.current) return;
|
|
@@ -9371,17 +9496,23 @@ function useAppAutoEvents(config) {
|
|
|
9371
9496
|
const appVersion = getAppVersion2();
|
|
9372
9497
|
const deviceType = await detectPlatform3();
|
|
9373
9498
|
const osVersion = await detectOsVersion();
|
|
9499
|
+
if (debug) console.log("[Paywallo EVENTS] computed device context", { deviceType, osVersion, appVersion, sessionId, hasSessionId: !!sessionId });
|
|
9374
9500
|
try {
|
|
9501
|
+
if (debug) console.log("[Paywallo EVENTS] checking install \u2014 about to read FIRST_SEEN_KEY");
|
|
9375
9502
|
const firstSeen = await storageGet(FIRST_SEEN_KEY);
|
|
9376
9503
|
if (!firstSeen) {
|
|
9377
9504
|
await emitLifecycle({ type: "install", device_type: deviceType, os_version: osVersion, app_version: appVersion, ...sessionId ? { session_id: sessionId } : {} }, "critical");
|
|
9378
9505
|
await storageSet(FIRST_SEEN_KEY, (/* @__PURE__ */ new Date()).toISOString());
|
|
9506
|
+
if (debug) console.log("[Paywallo EVENTS] install event emitted (first time)");
|
|
9379
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 });
|
|
9380
9510
|
}
|
|
9381
9511
|
} catch (err) {
|
|
9382
9512
|
if (debug) console.log("[Paywallo EVENTS] lifecycle install check failed", err);
|
|
9383
9513
|
}
|
|
9384
9514
|
try {
|
|
9515
|
+
if (debug) console.log("[Paywallo EVENTS] emitting cold_start");
|
|
9385
9516
|
await emitLifecycle({ type: "cold_start", app_version: appVersion, device_type: deviceType, os_version: osVersion, ...sessionId ? { session_id: sessionId } : {} }, "normal");
|
|
9386
9517
|
if (debug) console.log("[Paywallo EVENTS] lifecycle cold_start emitted");
|
|
9387
9518
|
} catch (err) {
|
|
@@ -9696,9 +9827,9 @@ function subscriptionInfoToSub(s) {
|
|
|
9696
9827
|
}
|
|
9697
9828
|
async function resolveOfflineFromCache(distinctId) {
|
|
9698
9829
|
try {
|
|
9699
|
-
const
|
|
9700
|
-
if (!
|
|
9701
|
-
const sub =
|
|
9830
|
+
const cached2 = await subscriptionCache.get(distinctId);
|
|
9831
|
+
if (!cached2) return false;
|
|
9832
|
+
const sub = cached2.data.subscription;
|
|
9702
9833
|
return isSubscriptionStillActive(
|
|
9703
9834
|
sub ? { status: sub.status, expiresAt: sub.expiresAt ?? null } : null
|
|
9704
9835
|
);
|
|
@@ -9768,8 +9899,8 @@ function useSubscriptionSync() {
|
|
|
9768
9899
|
if (!apiClient || !distinctId) return false;
|
|
9769
9900
|
const storeKitResult = await checkStoreKitActive(PaywalloClient.getConfig()?.debug);
|
|
9770
9901
|
if (storeKitResult !== null) return storeKitResult;
|
|
9771
|
-
const
|
|
9772
|
-
if (
|
|
9902
|
+
const cached2 = cacheRef.current;
|
|
9903
|
+
if (cached2 && cached2.expiresAt > Date.now()) return isActiveCacheEntry(cached2);
|
|
9773
9904
|
const persistedResult = await checkPersistedCache(distinctId, (sub) => setCacheAndState(sub));
|
|
9774
9905
|
if (persistedResult !== null) return persistedResult;
|
|
9775
9906
|
try {
|
|
@@ -9786,8 +9917,8 @@ function useSubscriptionSync() {
|
|
|
9786
9917
|
const apiClient = PaywalloClient.getApiClient();
|
|
9787
9918
|
const distinctId = PaywalloClient.getDistinctId();
|
|
9788
9919
|
if (!apiClient || !distinctId) return null;
|
|
9789
|
-
const
|
|
9790
|
-
if (
|
|
9920
|
+
const cached2 = cacheRef.current;
|
|
9921
|
+
if (cached2 && cached2.expiresAt > Date.now()) return cached2.data;
|
|
9791
9922
|
try {
|
|
9792
9923
|
return await fetchAndCache();
|
|
9793
9924
|
} catch {
|
|
@@ -9803,6 +9934,196 @@ function useSubscriptionSync() {
|
|
|
9803
9934
|
return { subscription, hasActiveSubscription, getSubscription, invalidateCache };
|
|
9804
9935
|
}
|
|
9805
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
|
+
|
|
9806
10127
|
// src/PaywalloProvider.tsx
|
|
9807
10128
|
init_localization();
|
|
9808
10129
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
@@ -9832,8 +10153,18 @@ function PaywalloProvider({ children, config }) {
|
|
|
9832
10153
|
for (const [k, v] of Object.entries(props)) if (v !== void 0) flat[k] = v;
|
|
9833
10154
|
return PaywalloClient.track("lifecycle", { properties: flat, priority });
|
|
9834
10155
|
},
|
|
9835
|
-
storageGet: (key) =>
|
|
9836
|
-
|
|
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
|
+
},
|
|
9837
10168
|
debug: config.debug
|
|
9838
10169
|
});
|
|
9839
10170
|
const triggerRepreload = (0, import_react25.useCallback)((placement) => {
|
|
@@ -9841,8 +10172,8 @@ function PaywalloProvider({ children, config }) {
|
|
|
9841
10172
|
});
|
|
9842
10173
|
}, []);
|
|
9843
10174
|
const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
|
|
9844
|
-
const SCREEN_HEIGHT = (0, import_react25.useMemo)(() =>
|
|
9845
|
-
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;
|
|
9846
10177
|
const handlePreloadedWebViewReady = (0, import_react25.useCallback)(() => {
|
|
9847
10178
|
if (PaywalloClient.getConfig()?.debug) {
|
|
9848
10179
|
console.log(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
|
|
@@ -9851,7 +10182,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
9851
10182
|
}, [preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
|
|
9852
10183
|
const handlePreloadedClose = (0, import_react25.useCallback)(() => {
|
|
9853
10184
|
const placement = preloadedWebView?.placement;
|
|
9854
|
-
|
|
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(() => {
|
|
9855
10186
|
baseHandlePreloadedClose();
|
|
9856
10187
|
if (placement) triggerRepreload(placement);
|
|
9857
10188
|
});
|
|
@@ -9880,6 +10211,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
9880
10211
|
PaywalloClient.init(configRef.current).then(async () => {
|
|
9881
10212
|
if (!mounted) return;
|
|
9882
10213
|
setDistinctId(PaywalloClient.getDistinctId());
|
|
10214
|
+
startSuperwallAutoBridge(configRef.current.debug);
|
|
9883
10215
|
setIsInitialized(true);
|
|
9884
10216
|
if (!autoPreloadTriggeredRef.current) {
|
|
9885
10217
|
autoPreloadTriggeredRef.current = true;
|
|
@@ -10100,7 +10432,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
10100
10432
|
(0, import_react25.useLayoutEffect)(() => {
|
|
10101
10433
|
if (showingPreloadedWebView) {
|
|
10102
10434
|
slideAnim.setValue(SCREEN_HEIGHT);
|
|
10103
|
-
|
|
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();
|
|
10104
10436
|
}
|
|
10105
10437
|
}, [showingPreloadedWebView, slideAnim, SCREEN_HEIGHT]);
|
|
10106
10438
|
const preloadStyle = [
|
|
@@ -10110,7 +10442,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
10110
10442
|
];
|
|
10111
10443
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(PaywalloContext.Provider, { value: contextValue, children: [
|
|
10112
10444
|
children,
|
|
10113
|
-
/* @__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)(
|
|
10114
10446
|
PaywallWebView,
|
|
10115
10447
|
{
|
|
10116
10448
|
paywallId: preloadedWebView.paywallId,
|
|
@@ -10141,7 +10473,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
10141
10473
|
) })
|
|
10142
10474
|
] });
|
|
10143
10475
|
}
|
|
10144
|
-
var styles3 =
|
|
10476
|
+
var styles3 = import_react_native27.StyleSheet.create({
|
|
10145
10477
|
preloadOverlay: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#ffffff" },
|
|
10146
10478
|
hidden: { zIndex: -1, opacity: 0, width: 1, height: 1, overflow: "hidden" },
|
|
10147
10479
|
visible: { zIndex: 9999, opacity: 1 }
|