@virex-tech/paywallo-sdk 1.5.3 → 2.0.0

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/dist/index.d.mts CHANGED
@@ -118,6 +118,13 @@ interface CampaignResponse {
118
118
  campaignId: string;
119
119
  placement: string;
120
120
  variantKey: string;
121
+ /**
122
+ * UUID estável de `paywall_variants` (sub-fase 2.14). Server sempre
123
+ * envia; SDK usa pra popular `variant_id` em eventos `$paywall_*`.
124
+ * Opcional no tipo pra retrocompat com mock responses / SDK 1.x que
125
+ * possam não ter esse campo — consumer code deve tratar como string.
126
+ */
127
+ variantId?: string;
121
128
  paywall: {
122
129
  id: string;
123
130
  placement: string;
@@ -137,6 +144,8 @@ interface CampaignResult {
137
144
  restored: boolean;
138
145
  campaignId?: string;
139
146
  variantKey?: string;
147
+ /** UUID de `paywall_variants` (sub-fase 2.14). */
148
+ variantId?: string;
140
149
  error?: Error;
141
150
  skippedReason?: "subscriber";
142
151
  }
@@ -192,7 +201,14 @@ declare class PaywalloError extends Error {
192
201
 
193
202
  declare class PurchaseError extends PaywalloError {
194
203
  readonly userCancelled: boolean;
195
- constructor(code: string, message: string, userCancelled?: boolean);
204
+ /**
205
+ * Populated when this error stems from an HTTP response (e.g. server
206
+ * validation rejected the receipt). Lets callers distinguish a permanent
207
+ * 4xx (drop + finishTransaction) from a transport-level failure that
208
+ * should be retried via the offline queue.
209
+ */
210
+ readonly httpStatus?: number;
211
+ constructor(code: string, message: string, userCancelled?: boolean, httpStatus?: number);
196
212
  }
197
213
  declare const PURCHASE_ERROR_CODES: {
198
214
  readonly NOT_INITIALIZED: "PURCHASE_NOT_INITIALIZED";
@@ -302,6 +318,13 @@ interface IdentifyOptions {
302
318
  interface TrackEventOptions {
303
319
  properties?: UserProperties;
304
320
  timestamp?: number;
321
+ /**
322
+ * Flush priority. `"critical"` flushes the event as a batch-of-1 on the
323
+ * next microtask (use for install/identify/transaction/subscription/refund
324
+ * events). `"normal"` (default) batches on a 25-event / 10-second rolling
325
+ * window.
326
+ */
327
+ priority?: "critical" | "normal";
305
328
  }
306
329
  interface FlagVariant {
307
330
  variant: string | null;
@@ -332,8 +355,10 @@ interface PaywallModalProps {
332
355
  preloadedProducts?: Map<string, Product>;
333
356
  variantKey?: string;
334
357
  campaignId?: string;
358
+ /** UUID de `paywall_variants` (sub-fase 2.14). */
359
+ variantId?: string;
335
360
  }
336
- declare function PaywallModal({ placement, visible, onResult, paywallConfig: preloadedConfig, preloadedProducts, variantKey, campaignId, }: PaywallModalProps): React__default.ReactElement | null;
361
+ declare function PaywallModal({ placement, visible, onResult, paywallConfig: preloadedConfig, preloadedProducts, variantKey, campaignId, variantId, }: PaywallModalProps): React__default.ReactElement | null;
337
362
 
338
363
  interface PaywallContextValue {
339
364
  nodes: Record<string, PaywallNode>;
@@ -420,6 +445,11 @@ interface HttpClientConfig {
420
445
  timeout: number;
421
446
  retry: RetryConfig;
422
447
  debug: boolean;
448
+ /**
449
+ * Headers injected into every outgoing request (e.g. `x-sdk-version`,
450
+ * `x-sdk-platform`). Per-request headers take precedence on conflict.
451
+ */
452
+ globalHeaders: Record<string, string>;
423
453
  }
424
454
  interface RequestOptions {
425
455
  method: "GET" | "POST" | "PUT" | "DELETE";
@@ -438,6 +468,12 @@ interface HttpResponse<T = unknown> {
438
468
  declare class HttpClient {
439
469
  private config;
440
470
  constructor(baseUrl: string, config?: Partial<Omit<HttpClientConfig, "baseUrl">>);
471
+ /**
472
+ * Merge additional headers into every subsequent request. Call sites
473
+ * (e.g. `ApiClient`) use this to update `x-sdk-environment` when the
474
+ * environment flips from Sandbox to Production at runtime.
475
+ */
476
+ setGlobalHeaders(headers: Record<string, string>): void;
441
477
  get<T>(path: string, options?: Partial<RequestOptions>): Promise<HttpResponse<T>>;
442
478
  post<T>(path: string, body?: unknown, options?: Partial<RequestOptions>): Promise<HttpResponse<T>>;
443
479
  request<T>(path: string, options: RequestOptions): Promise<HttpResponse<T>>;
@@ -457,6 +493,54 @@ declare class HttpClient {
457
493
  getConfig(): Readonly<HttpClientConfig>;
458
494
  }
459
495
 
496
+ /**
497
+ * Shared context resolved once per batch and hoisted out of every individual
498
+ * event in the V2 ingest envelope. The provider is called at flush time so
499
+ * each field reflects the most recent SDK state.
500
+ *
501
+ * The shape mirrors the server-side `/api/v2/ingest/batch` contract (Phase
502
+ * 3.1). Fields declared here are deduplicated across the batch — they exist
503
+ * once on the envelope instead of `N` times on each `events[]` entry.
504
+ */
505
+ interface EventContext {
506
+ distinct_id?: string;
507
+ session_id?: string;
508
+ device_id?: string;
509
+ app_version?: string;
510
+ sdk_version?: string;
511
+ platform?: string;
512
+ os_version?: string;
513
+ device_model?: string;
514
+ timezone?: string;
515
+ locale?: string;
516
+ attribution?: Record<string, unknown>;
517
+ ids?: Record<string, unknown>;
518
+ }
519
+
520
+ /**
521
+ * Event priority controls flush semantics:
522
+ *
523
+ * - `"critical"` → flushed as a batch-of-1 on the next microtask (via
524
+ * `queueMicrotask`). Used for events where loss is unacceptable:
525
+ * `transaction`, `identify`, `lifecycle {type: "install"}`,
526
+ * `subscription_*`, `refund`, `checkout_started`.
527
+ * - `"normal"` → buffered and flushed on a rolling window (whichever fires
528
+ * first): `BATCH_MAX_SIZE` events OR `BATCH_FLUSH_MS`. Used for events
529
+ * where batching amortizes network cost: `paywall`, non-install
530
+ * `lifecycle` (foreground/background/session), `onboarding`,
531
+ * `notification`, `custom`.
532
+ *
533
+ * Both priorities post to `/api/v2/ingest/batch` (Phase 3.1 envelope) with
534
+ * automatic fallback to `/api/v1/events/batch` when the V2 endpoint is
535
+ * absent (old server) or rejects the request. `OfflineQueue` handles
536
+ * network-failure durability for both queues via the `PostFn` injected by
537
+ * `ApiClient.postWithQueue`.
538
+ */
539
+ type EventPriority = "critical" | "normal";
540
+ interface TrackOptions {
541
+ priority?: EventPriority;
542
+ }
543
+
460
544
  declare class ApiClient {
461
545
  private baseUrl;
462
546
  private webUrl;
@@ -467,7 +551,17 @@ declare class ApiClient {
467
551
  private httpClient;
468
552
  private readonly cache;
469
553
  private readonly batcher;
554
+ private eventContextProvider;
555
+ private onError;
470
556
  constructor(appKey: string, apiUrl: string, debug?: boolean, environment?: Environment, offlineQueueEnabled?: boolean, timeout?: number);
557
+ /**
558
+ * Inject a context provider so the EventBatcher can hoist global fields
559
+ * (sdk/platform/version/locale/attribution/ids) out of each event into
560
+ * the V2 envelope. Allows PaywalloClient to wire in identity/session
561
+ * state lazily without the batcher depending on those singletons.
562
+ */
563
+ setEventContextProvider(provider: () => EventContext | null): void;
564
+ private resolveEventContext;
471
565
  getHttpClient(): HttpClient;
472
566
  getBaseUrl(): string;
473
567
  getWebUrl(): string;
@@ -476,22 +570,22 @@ declare class ApiClient {
476
570
  getQueueSize(): number;
477
571
  setofflineQueueEnabled(enabled: boolean): void;
478
572
  isofflineQueueEnabled(): boolean;
573
+ setOnError(callback: ((error: unknown) => void) | null): void;
479
574
  setEnvironment(environment: Environment): void;
480
575
  setDebug(debug: boolean): void;
576
+ /**
577
+ * Build the baseline SDK telemetry headers that every outgoing request
578
+ * should carry. Keep this tiny — it runs once per ApiClient instance.
579
+ */
580
+ private buildSdkHeaders;
481
581
  clearQueue(): Promise<void>;
482
582
  get<T>(path: string): Promise<HttpResponse<T>>;
483
583
  post<T>(path: string, body: unknown, skipRetry?: boolean): Promise<HttpResponse<T>>;
484
584
  reportError(errorType: string, message: string, context?: Record<string, unknown>): Promise<void>;
485
- trackEvent(eventName: string, distinctId: string, properties?: UserProperties, timestamp?: number): Promise<void>;
585
+ trackEvent(eventName: string, distinctId: string, properties?: UserProperties, timestamp?: number, options?: TrackOptions): Promise<void>;
486
586
  flushEventBatch(): Promise<void>;
487
587
  dispose(): void;
488
588
  identify(distinctId: string, properties?: UserProperties, email?: string, deviceId?: string): Promise<void>;
489
- startSession(distinctId: string, sessionId: string, platform?: string, appVersion?: string, deviceModel?: string, osVersion?: string): Promise<{
490
- success: boolean;
491
- }>;
492
- endSession(sessionId: string, durationSeconds: number): Promise<{
493
- success: boolean;
494
- }>;
495
589
  getVariant(flagKey: string, distinctId: string): Promise<FlagVariant>;
496
590
  getPaywall(placement: string): Promise<PaywallConfig | null>;
497
591
  getEmergencyPaywall(): Promise<EmergencyPaywallResponse>;
@@ -502,14 +596,64 @@ declare class ApiClient {
502
596
  evaluateFlags(keys: string[], distinctId: string): Promise<Record<string, string | null>>;
503
597
  getConditionalFlag(flagKey: string, context: ConditionalFlagContext): Promise<ConditionalFlagResult>;
504
598
  getVariantFromCache(flagKey: string, distinctId: string): Promise<FlagVariant | null>;
505
- private postWithQueue;
506
- private log;
599
+ private flagDeps;
600
+ }
601
+
602
+ /**
603
+ * Event payload emitted by the native module whenever StoreKit 2
604
+ * (`Transaction.updates`) or Google Play Billing (`onPurchasesUpdated` +
605
+ * refund diff) detects a renewal, refund, or cancellation.
606
+ */
607
+ interface NativeTransactionUpdate {
608
+ type: "renewed" | "refunded" | "canceled";
609
+ transactionId: string;
610
+ productId: string;
611
+ amount?: number;
612
+ currency?: string;
613
+ }
614
+ type TransactionUpdateListener = (update: NativeTransactionUpdate) => void;
615
+ interface TransactionUpdateSubscription {
616
+ remove(): void;
617
+ }
618
+ declare function isAvailable(): boolean;
619
+ declare const nativeStoreKit: {
620
+ isAvailable: typeof isAvailable;
621
+ getProducts(productIds: string[]): Promise<Product[]>;
622
+ purchase(productId: string): Promise<Purchase | null>;
623
+ finishTransaction(transactionId: string): Promise<void>;
624
+ getActiveTransactions(): Promise<Purchase[]>;
625
+ /**
626
+ * Subscribes to native transaction update events (renewals, refunds,
627
+ * cancellations) from StoreKit 2 (`Transaction.updates`) on iOS or
628
+ * `PurchasesUpdatedListener` + refund diff on Android.
629
+ *
630
+ * Returns a subscription with a `remove()` method. The caller is
631
+ * responsible for cleanup (typically on `Paywallo.reset()`).
632
+ *
633
+ * Returns `null` when the native module isn't available (non-mobile env,
634
+ * native module not linked) — callers should treat null as a no-op.
635
+ */
636
+ subscribeToTransactionUpdates(listener: TransactionUpdateListener): TransactionUpdateSubscription | null;
637
+ };
638
+
639
+ interface ValidateOptions {
640
+ paywallPlacement?: string;
641
+ variantKey?: string;
642
+ paywallId?: string;
643
+ variantId?: string;
507
644
  }
508
645
 
509
646
  declare class IAPService {
510
647
  private productsCache;
511
648
  private apiClient;
512
649
  private purchaseInFlight;
650
+ /**
651
+ * Idempotency guard against double-finish. Populated with transaction IDs
652
+ * that have already been forwarded to `NativeStoreKit.finishTransaction`.
653
+ */
654
+ private finishedTransactions;
655
+ private validator;
656
+ private emitter;
513
657
  constructor(apiClient?: ApiClient);
514
658
  private get debug();
515
659
  private getDeviceCountry;
@@ -518,26 +662,16 @@ declare class IAPService {
518
662
  loadProducts(productIds: string[]): Promise<Product[]>;
519
663
  getProduct(productId: string): Product | undefined;
520
664
  getCachedProducts(): Map<string, Product>;
521
- private validateWithRetry;
522
- private validateWithServer;
523
665
  private finishTransaction;
524
- purchase(productId: string, options?: {
525
- paywallPlacement?: string;
526
- variantKey?: string;
527
- }): Promise<PurchaseResult>;
666
+ startTransactionListener(): void;
667
+ stopTransactionListener(): void;
668
+ emitTransactionUpdate(update: NativeTransactionUpdate): Promise<void>;
669
+ purchase(productId: string, options?: ValidateOptions): Promise<PurchaseResult>;
528
670
  restore(): Promise<Purchase[]>;
671
+ reset(): void;
529
672
  }
530
673
  declare function getIAPService(): IAPService;
531
674
 
532
- declare function isAvailable(): boolean;
533
- declare const nativeStoreKit: {
534
- isAvailable: typeof isAvailable;
535
- getProducts(productIds: string[]): Promise<Product[]>;
536
- purchase(productId: string): Promise<Purchase | null>;
537
- finishTransaction(transactionId: string): Promise<void>;
538
- getActiveTransactions(): Promise<Purchase[]>;
539
- };
540
-
541
675
  type PurchaseState = "idle" | "purchasing" | "restoring" | "error";
542
676
  interface IAPProduct {
543
677
  productId: string;
@@ -594,6 +728,7 @@ declare class IdentityManager {
594
728
  private debug;
595
729
  private initialized;
596
730
  private initPromise;
731
+ private distinctId;
597
732
  initialize(apiClient: ApiClient, debug?: boolean): Promise<string>;
598
733
  private _doInitialize;
599
734
  identify(options?: IdentifyOptions | UserProperties): Promise<void>;
@@ -610,6 +745,16 @@ declare class IdentityManager {
610
745
  }
611
746
  declare const identityManager: IdentityManager;
612
747
 
748
+ /**
749
+ * Guard usado pelo SessionManager.startSession() — aguarda distinctId ficar
750
+ * disponível antes de criar a sessão. Raiz dos 55% users com total_sessions=0
751
+ * era session.startSession rodando em paralelo com identity.initialize() e
752
+ * batendo em distinctId vazio. Agora sessão só inicia depois da identity
753
+ * estar pronta; este guard é uma defesa extra caso alguém chame startSession
754
+ * manualmente antes do init terminar.
755
+ */
756
+ type DistinctIdProvider = () => string;
757
+
613
758
  declare class IdentityError extends PaywalloError {
614
759
  constructor(code: string, message: string);
615
760
  }
@@ -663,6 +808,231 @@ declare class NetworkMonitorClass {
663
808
  }
664
809
  declare const networkMonitor: NetworkMonitorClass;
665
810
 
811
+ declare class SecureStorage {
812
+ constructor(_debug?: boolean);
813
+ get(key: string): Promise<string | null>;
814
+ set(key: string, value: string): Promise<boolean>;
815
+ remove(key: string): Promise<boolean>;
816
+ hasKeychainSupport(): boolean;
817
+ private getFromKeychain;
818
+ private setToKeychain;
819
+ private removeFromKeychain;
820
+ }
821
+
822
+ type PushPlatform = "ios" | "android" | "web";
823
+ type PushPermissionStatus = "granted" | "denied" | "provisional" | "notDetermined";
824
+ /**
825
+ * Canonical push event types — MUST match server `PushEventType` enum 1:1
826
+ * (see paywallo-server/src/domain/notifications/types/index.ts).
827
+ * Server Zod (`pushNotificationEventSchema`) rejects anything outside this set.
828
+ */
829
+ type NotificationEventType = "notification_sent" | "notification_delivered" | "notification_displayed" | "notification_clicked" | "notification_dismissed" | "notification_failed" | "notification_converted";
830
+ /**
831
+ * Payload sent to `POST /api/v2/push-tokens`. Server schema matches 1:1:
832
+ * `{ token, platform, distinct_id, app_version, sdk_version }`.
833
+ *
834
+ * Optional client-side extras (deviceId/locale/timezone) are kept for context
835
+ * but the server-critical fields are the five snake_case keys.
836
+ */
837
+ interface DeviceTokenRegistration {
838
+ token: string;
839
+ platform: PushPlatform;
840
+ distinct_id: string;
841
+ app_version: string;
842
+ sdk_version: string;
843
+ deviceId?: string;
844
+ locale?: string;
845
+ timezone?: string;
846
+ }
847
+ interface NotificationPayload {
848
+ notificationId: string;
849
+ campaignId: string;
850
+ variantKey?: string;
851
+ title: string;
852
+ body: string;
853
+ imageUrl?: string;
854
+ deepLink?: string;
855
+ data?: Record<string, unknown>;
856
+ }
857
+ type NotificationListener = (event: {
858
+ type: NotificationEventType;
859
+ notification: NotificationPayload;
860
+ messageId: string;
861
+ }) => void;
862
+
863
+ interface FcmRemoteMessage {
864
+ messageId?: string;
865
+ data?: Record<string, string>;
866
+ notification?: {
867
+ title?: string;
868
+ body?: string;
869
+ };
870
+ sentTime?: number;
871
+ }
872
+ /**
873
+ * Minimal interface satisfied by NativePushBridge (and any custom bridge).
874
+ * Use this type instead of concrete classes to keep code bridge-agnostic.
875
+ */
876
+ interface IPushBridge {
877
+ getToken(): Promise<string | null>;
878
+ getAPNSToken(): Promise<string | null>;
879
+ requestPermission(options?: {
880
+ provisional?: boolean;
881
+ }): Promise<number>;
882
+ hasPermission(): Promise<number>;
883
+ onTokenRefresh(cb: (token: string) => void): () => void;
884
+ onMessage(cb: (message: FcmRemoteMessage) => Promise<void>): () => void;
885
+ setBackgroundMessageHandler(cb: (message: FcmRemoteMessage) => Promise<void>): void;
886
+ getInitialNotification(): Promise<FcmRemoteMessage | null>;
887
+ }
888
+
889
+ /**
890
+ * Minimal surface the facade needs from the SDK's unified event pipeline.
891
+ * `PaywalloClient` implements it (forwarding to `ApiClient.trackEvent`
892
+ * which ends up in the `EventBatcher`).
893
+ */
894
+ interface EventBatcherLike {
895
+ trackEvent(eventName: string, distinctId: string, properties?: Record<string, unknown>, timestamp?: number): Promise<void>;
896
+ getDistinctId(): string;
897
+ }
898
+
899
+ interface PrePromptOptions {
900
+ title: string;
901
+ body: string;
902
+ acceptLabel?: string;
903
+ rejectLabel?: string;
904
+ provisional?: boolean;
905
+ }
906
+ interface PrePromptHandle {
907
+ title: string;
908
+ body: string;
909
+ acceptLabel?: string;
910
+ rejectLabel?: string;
911
+ /** Consumer invokes after user taps accept in custom UI. */
912
+ accept(): Promise<PushPermissionStatus>;
913
+ /** Consumer invokes after user taps reject in custom UI. */
914
+ reject(): PushPermissionStatus;
915
+ }
916
+ interface RNPlatform {
917
+ OS: string;
918
+ Version?: number | string;
919
+ }
920
+
921
+ type NotificationsEnvironment = "sandbox" | "production";
922
+ interface NotificationsConfig {
923
+ debug?: boolean;
924
+ environment?: NotificationsEnvironment;
925
+ apiBaseUrl?: string;
926
+ }
927
+
928
+ interface HandlersConfig {
929
+ eventBatcher: EventBatcherLike;
930
+ }
931
+ interface NotificationsManagerConfig {
932
+ appKey: string;
933
+ apiBaseUrl?: string;
934
+ debug?: boolean;
935
+ environment?: NotificationsConfig["environment"];
936
+ }
937
+ interface NotificationsManagerDeps {
938
+ httpClient?: HttpClient;
939
+ secureStorage?: SecureStorage;
940
+ /** Generic bridge — takes precedence over fcmBridge if both are provided. */
941
+ bridge?: IPushBridge;
942
+ /** Kept for backwards compatibility — use bridge instead. */
943
+ fcmBridge?: IPushBridge;
944
+ getDeviceId?: () => string | null;
945
+ getDistinctId?: () => string;
946
+ platform?: RNPlatform;
947
+ }
948
+ declare class NotificationsManager {
949
+ private isInitialized;
950
+ private permissionStatus;
951
+ private currentToken;
952
+ private debug;
953
+ private httpClient;
954
+ private secureStorage;
955
+ private bridge;
956
+ private getDeviceId;
957
+ private getDistinctId;
958
+ private appKey;
959
+ private apiBaseUrl;
960
+ private tokenRefreshCleanup;
961
+ private foregroundCleanup;
962
+ private pendingHandlersConfig;
963
+ private tracker;
964
+ private permissionManager;
965
+ private receivedCallbacks;
966
+ private openedCallbacks;
967
+ private dismissedCallbacks;
968
+ constructor(deps?: NotificationsManagerDeps);
969
+ injectDeps(deps: {
970
+ httpClient: HttpClient;
971
+ secureStorage: SecureStorage;
972
+ getDeviceId: () => string | null;
973
+ getDistinctId?: () => string;
974
+ }): void;
975
+ initialize(config: NotificationsManagerConfig): Promise<void>;
976
+ private applyConfig;
977
+ private refreshPermissionStatus;
978
+ private tokenDeps;
979
+ private doRegisterToken;
980
+ private subscribeTokenRefresh;
981
+ /** Deletes token on server + locally (explicit user opt-out). */
982
+ optOut(): Promise<void>;
983
+ /** Clears token locally without server call (used on sign-out/reset). */
984
+ invalidateLocalToken(): Promise<void>;
985
+ getPermissionStatus(): Promise<PushPermissionStatus>;
986
+ requestPermission(options?: {
987
+ provisional?: boolean;
988
+ }): Promise<PushPermissionStatus>;
989
+ /** Requests OS permission and, on success, captures + registers the push token. */
990
+ requestPushPermission(options?: {
991
+ provisional?: boolean;
992
+ }): Promise<PushPermissionStatus>;
993
+ private captureAndRegisterToken;
994
+ requestPermissionWithPrePrompt(opts: PrePromptOptions): PrePromptHandle;
995
+ isReady(): boolean;
996
+ markInitialized(): void;
997
+ getCurrentToken(): string | null;
998
+ setupHandlers(config: HandlersConfig): void;
999
+ onReceived(cb: (payload: NotificationPayload) => void): void;
1000
+ onOpened(cb: (payload: NotificationPayload) => void): void;
1001
+ onDismissed(cb: (payload: NotificationPayload) => void): void;
1002
+ getInitialNotification(): Promise<NotificationPayload | null>;
1003
+ flushEvents(): Promise<void>;
1004
+ registerBackgroundHandler(cb: (message: FcmRemoteMessage) => Promise<void>): void;
1005
+ destroy(): void;
1006
+ }
1007
+
1008
+ declare const notificationsManager: NotificationsManager;
1009
+
1010
+ declare class NativePushBridge implements IPushBridge {
1011
+ getToken(): Promise<string | null>;
1012
+ getAPNSToken(): Promise<string | null>;
1013
+ requestPermission(options?: {
1014
+ provisional?: boolean;
1015
+ }): Promise<number>;
1016
+ hasPermission(): Promise<number>;
1017
+ onTokenRefresh(cb: (token: string) => void): () => void;
1018
+ onMessage(cb: (message: FcmRemoteMessage) => Promise<void>): () => void;
1019
+ setBackgroundMessageHandler(_cb: (message: FcmRemoteMessage) => Promise<void>): void;
1020
+ getInitialNotification(): Promise<FcmRemoteMessage | null>;
1021
+ }
1022
+
1023
+ declare const NOTIFICATIONS_ERROR_CODES: {
1024
+ readonly TOKEN_UNAVAILABLE: "TOKEN_UNAVAILABLE";
1025
+ readonly PERMISSION_DENIED: "PERMISSION_DENIED";
1026
+ readonly REGISTRATION_FAILED: "REGISTRATION_FAILED";
1027
+ readonly NOT_INITIALIZED: "NOT_INITIALIZED";
1028
+ readonly ABORTED: "ABORTED";
1029
+ };
1030
+ type NotificationsErrorCode = (typeof NOTIFICATIONS_ERROR_CODES)[keyof typeof NOTIFICATIONS_ERROR_CODES];
1031
+ declare class NotificationsError extends PaywalloError {
1032
+ readonly details?: unknown;
1033
+ constructor(code: NotificationsErrorCode, message: string, details?: unknown);
1034
+ }
1035
+
666
1036
  interface SessionState {
667
1037
  sessionId: string | null;
668
1038
  startedAt: Date | null;
@@ -688,7 +1058,8 @@ declare class SessionManager {
688
1058
  private emergencyPaywallHandler;
689
1059
  private emergencyPaywallShownInSession;
690
1060
  private secureStorage;
691
- initialize(apiClient: ApiClient, config?: SessionConfig, debug?: boolean): Promise<void>;
1061
+ private distinctIdProvider;
1062
+ initialize(apiClient: ApiClient, config?: SessionConfig, debug?: boolean, distinctIdProvider?: DistinctIdProvider | null): Promise<void>;
692
1063
  startSession(): Promise<string>;
693
1064
  endSession(): Promise<void>;
694
1065
  getSessionId(): string | null;
@@ -697,11 +1068,13 @@ declare class SessionManager {
697
1068
  getState(): SessionState;
698
1069
  destroy(): void;
699
1070
  registerEmergencyPaywallHandler(handler: EmergencyPaywallHandler | null): void;
1071
+ private readSessionKey;
700
1072
  private restoreSession;
701
1073
  private setupAppStateListener;
702
1074
  private handleAppStateChange;
703
1075
  private handleForeground;
704
1076
  private handleBackground;
1077
+ private flushEventsWithTimeout;
705
1078
  private checkEmergencyPaywall;
706
1079
  private trackSessionStart;
707
1080
  private trackSessionEnd;
@@ -741,6 +1114,8 @@ interface PaywalloInitConfig extends PaywalloConfig {
741
1114
  sessionFlags?: string[];
742
1115
  subscriptionCacheTTL?: number;
743
1116
  autoPreloadCampaign?: string;
1117
+ /** Called when the SDK silently swallows an API error. Useful for logging/monitoring in production. */
1118
+ onError?: (error: unknown) => void;
744
1119
  }
745
1120
  interface IPaywalloClient {
746
1121
  init(config: PaywalloInitConfig): Promise<void>;
@@ -767,6 +1142,9 @@ interface IPaywalloClient {
767
1142
  getPreloadedCampaign(placement: string): CampaignResponse | null;
768
1143
  reset(): Promise<void>;
769
1144
  fullReset(): Promise<void>;
1145
+ requestPushPermission(options?: {
1146
+ provisional?: boolean;
1147
+ }): Promise<PushPermissionStatus>;
770
1148
  getConfig(): PaywalloConfig | null;
771
1149
  getDistinctId(): string;
772
1150
  getDeviceId(): string | null;
@@ -804,6 +1182,7 @@ interface IPaywalloClient {
804
1182
 
805
1183
  declare const PaywalloClient: IPaywalloClient;
806
1184
 
1185
+ type QueuePriority = "critical" | "normal";
807
1186
  interface QueueItem {
808
1187
  id: string;
809
1188
  method: "POST" | "PUT";
@@ -813,23 +1192,87 @@ interface QueueItem {
813
1192
  timestamp: number;
814
1193
  attempts: number;
815
1194
  maxAttempts: number;
1195
+ /**
1196
+ * Priority for this queue item. `"critical"` items are journaled to disk
1197
+ * immediately and trigger an out-of-band flush via listeners; `"normal"`
1198
+ * items are still journaled pre-network but coalesce into batch snapshots.
1199
+ * Optional so pre-existing serialized items (SDK ≤0.10) still load.
1200
+ */
1201
+ priority?: QueuePriority;
1202
+ /**
1203
+ * Epoch ms timestamp before which this item should NOT be retried.
1204
+ * Computed via exponential backoff after each failed attempt.
1205
+ * Absent on first attempt (item eligible immediately).
1206
+ */
1207
+ nextRetryAt?: number;
816
1208
  }
817
1209
  interface OfflineQueueConfig {
818
1210
  maxItems: number;
819
1211
  maxAge: number;
820
1212
  maxAttempts: number;
821
1213
  storageKey: string;
1214
+ /**
1215
+ * Storage key for the pre-write journal. New items are written here first
1216
+ * (append-only) BEFORE any network attempt so a kernel panic between
1217
+ * `enqueue` and the main snapshot save still lets us recover the item on
1218
+ * the next cold start.
1219
+ */
1220
+ journalKey: string;
1221
+ /**
1222
+ * Storage key for the dead-letter queue. Items that exhaust `maxAttempts`
1223
+ * are moved here instead of being silently dropped, enabling diagnostics
1224
+ * and manual replay.
1225
+ */
1226
+ dlqKey: string;
1227
+ /**
1228
+ * Maximum number of items retained in the DLQ. Oldest items are evicted
1229
+ * when the limit is reached (FIFO).
1230
+ */
1231
+ dlqMaxItems: number;
1232
+ /**
1233
+ * Base delay (ms) for exponential backoff on retry.
1234
+ * Delay = min(baseRetryDelayMs * 2^(attempts-1), maxRetryDelayMs).
1235
+ */
1236
+ baseRetryDelayMs: number;
1237
+ /**
1238
+ * Maximum delay cap (ms) for exponential backoff.
1239
+ */
1240
+ maxRetryDelayMs: number;
822
1241
  debug: boolean;
823
1242
  }
824
- type QueueEventType = "item:added" | "item:removed" | "item:success" | "item:failed" | "item:evicted" | "processing:start" | "processing:end";
1243
+ type QueueEventType = "item:added" | "item:removed" | "item:success" | "item:failed" | "item:evicted" | "item:expired" | "processing:start" | "processing:end" | "recovery:complete" | "flush:requested";
825
1244
  interface QueueEvent {
826
1245
  type: QueueEventType;
827
1246
  item?: QueueItem;
828
1247
  error?: Error;
829
1248
  queueSize: number;
1249
+ /**
1250
+ * Populated on `item:expired` and `recovery:complete` events to surface
1251
+ * counts without forcing listeners to snapshot the queue.
1252
+ */
1253
+ count?: number;
1254
+ /**
1255
+ * Populated on `item:expired` so observability can report WHY items were
1256
+ * dropped (currently always `"max_age"`). Never a silent drop.
1257
+ */
1258
+ reason?: "max_age";
830
1259
  }
831
1260
  type QueueListener = (event: QueueEvent) => void;
832
1261
 
1262
+ /**
1263
+ * Durable offline queue with a pre-write journal.
1264
+ *
1265
+ * Durability contract:
1266
+ * 1. `enqueue()` writes the new item to a dedicated journal key in storage
1267
+ * BEFORE any network attempt and before the item is added to the
1268
+ * in-memory queue.
1269
+ * 2. The main snapshot is written after the in-memory mutation.
1270
+ * 3. On boot, `initialize()` merges main+journal (dedup by id) and persists
1271
+ * the union.
1272
+ *
1273
+ * Persistence logic is delegated to `OfflineQueueStorage`.
1274
+ * Retention: items older than `maxAge` (default 7 days) are dropped.
1275
+ */
833
1276
  declare class OfflineQueueClass {
834
1277
  private config;
835
1278
  private queue;
@@ -838,12 +1281,10 @@ declare class OfflineQueueClass {
838
1281
  private processing;
839
1282
  private processingLock;
840
1283
  private cleanupTimer;
1284
+ private storage;
841
1285
  initialize(config?: Partial<OfflineQueueConfig>): Promise<void>;
842
- private loadFromStorage;
843
- private saveToStorage;
844
- private cleanupExpired;
845
- enqueue(method: "POST" | "PUT", url: string, body: unknown, headers?: Record<string, string>, id?: string): Promise<QueueItem>;
846
- private findDuplicateIndex;
1286
+ enqueue(method: "POST" | "PUT", url: string, body: unknown, headers?: Record<string, string>, id?: string, priority?: QueuePriority): Promise<QueueItem>;
1287
+ private mergeDuplicate;
847
1288
  dequeue(): Promise<QueueItem | null>;
848
1289
  removeItem(id: string): Promise<boolean>;
849
1290
  processQueue(processor: (item: QueueItem) => Promise<boolean | {
@@ -856,6 +1297,9 @@ declare class OfflineQueueClass {
856
1297
  failed: number;
857
1298
  }>;
858
1299
  private handleItemFailure;
1300
+ private moveToDlq;
1301
+ getDlq(): Promise<QueueItem[]>;
1302
+ clearDlq(): Promise<void>;
859
1303
  private sleep;
860
1304
  getQueue(): ReadonlyArray<QueueItem>;
861
1305
  getSize(): number;
@@ -875,11 +1319,18 @@ declare const offlineQueue: OfflineQueueClass;
875
1319
  interface QueueProcessorConfig {
876
1320
  debounceMs: number;
877
1321
  debug: boolean;
1322
+ /**
1323
+ * Lazy getter for headers that must be fresh at flush time (e.g. `X-App-Key`).
1324
+ * Called just before each batch/item HTTP request — not at enqueue time.
1325
+ * Avoids stale headers when the app key changes after the item was queued.
1326
+ */
1327
+ getFreshHeaders?: () => Record<string, string>;
878
1328
  }
879
1329
  declare class QueueProcessorClass {
880
1330
  private config;
881
1331
  private httpClient;
882
1332
  private networkUnsubscribe;
1333
+ private queueUnsubscribe;
883
1334
  private debounceTimeout;
884
1335
  private retryIntervalId;
885
1336
  private initialized;
@@ -903,15 +1354,85 @@ declare class QueueProcessorClass {
903
1354
  }
904
1355
  declare const queueProcessor: QueueProcessorClass;
905
1356
 
906
- declare class AnalyticsError extends PaywalloError {
1357
+ /**
1358
+ * Minimal surface the OnboardingManager needs from the unified event pipeline.
1359
+ * `PaywalloClient` satisfies this interface (forwarding to
1360
+ * `ApiClient.trackEvent` → `EventBatcher` → `/api/v1/events/batch`).
1361
+ */
1362
+ interface OnboardingEventPipeline {
1363
+ trackEvent(eventName: string, distinctId: string, properties?: Record<string, unknown>, timestamp?: number): Promise<void>;
1364
+ getDistinctId(): string;
1365
+ }
1366
+ interface OnboardingDepsConfig {
1367
+ pipeline: OnboardingEventPipeline;
1368
+ debug?: boolean;
1369
+ }
1370
+ declare class OnboardingManager {
1371
+ private pipeline;
1372
+ private debug;
1373
+ private lastStep;
1374
+ private finished;
1375
+ /**
1376
+ * Injects runtime dependencies. Called by PaywalloClient during init.
1377
+ */
1378
+ injectDeps(config: OnboardingDepsConfig): void;
1379
+ /**
1380
+ * Tracks an onboarding step view.
1381
+ * Saves the step name internally for implicit drop detection on the backend.
1382
+ *
1383
+ * @param stepName - Unique name for this onboarding step.
1384
+ */
1385
+ step(stepName: string): Promise<void>;
1386
+ /**
1387
+ * Tracks successful completion of the onboarding flow.
1388
+ * Marks the flow as finished.
1389
+ */
1390
+ complete(): Promise<void>;
1391
+ /**
1392
+ * Tracks an explicit drop (abandonment) at the given step.
1393
+ * Marks the flow as finished.
1394
+ *
1395
+ * @param stepName - The step name where the user dropped off.
1396
+ */
1397
+ drop(stepName: string): Promise<void>;
1398
+ /** Returns the last step name seen, or null if no step was tracked yet. */
1399
+ getLastStep(): string | null;
1400
+ /** Returns whether the flow has been marked as finished (complete or drop). */
1401
+ isFinished(): boolean;
1402
+ private emit;
1403
+ private assertConfig;
1404
+ private validateStepName;
1405
+ }
1406
+ declare const onboardingManager: OnboardingManager;
1407
+
1408
+ declare class OnboardingError extends PaywalloError {
907
1409
  constructor(code: string, message: string);
908
1410
  }
909
- declare const ANALYTICS_ERROR_CODES: {
910
- readonly NOT_INITIALIZED: "ANALYTICS_NOT_INITIALIZED";
911
- readonly INVALID_STEP_INDEX: "ANALYTICS_INVALID_STEP_INDEX";
912
- readonly INVALID_ACTION_NAME: "ANALYTICS_INVALID_ACTION_NAME";
1411
+ declare const ONBOARDING_ERROR_CODES: {
1412
+ readonly NOT_INITIALIZED: "ONBOARDING_NOT_INITIALIZED";
1413
+ readonly INVALID_STEP_NAME: "ONBOARDING_INVALID_STEP_NAME";
913
1414
  };
914
1415
 
1416
+ /** Payload sent with onboarding.step events. */
1417
+ interface OnboardingStepPayload {
1418
+ step_name: string;
1419
+ }
1420
+ /** Payload sent with onboarding.drop events. */
1421
+ interface OnboardingDropPayload {
1422
+ step_name: string;
1423
+ }
1424
+
1425
+ interface UseOnboardingResult {
1426
+ step: (stepName: string) => Promise<void>;
1427
+ complete: () => Promise<void>;
1428
+ drop: (stepName: string) => Promise<void>;
1429
+ }
1430
+ /**
1431
+ * React hook that exposes bound onboarding tracking methods.
1432
+ * Wraps `onboardingManager` with stable `useCallback` references.
1433
+ */
1434
+ declare function useOnboarding(): UseOnboardingResult;
1435
+
915
1436
  declare class CampaignError extends PaywalloError {
916
1437
  constructor(code: string, message: string);
917
1438
  }
@@ -1089,4 +1610,4 @@ declare function hasVariables(text: string): boolean;
1089
1610
 
1090
1611
  declare const Paywallo: IPaywalloClient;
1091
1612
 
1092
- export { ANALYTICS_ERROR_CODES, AnalyticsError, ApiClient, type ApiClientConfig, type BlockType, type BoxSpacing, CAMPAIGN_ERROR_CODES, type CachedSubscription, CampaignError, type CampaignPresentOptions, type CampaignResponse, type CampaignResult, type ConditionalFlagContext, type ConditionalFlagResult, PURCHASE_ERROR_CODES as ERROR_CODES, type EmergencyPaywallResponse, type Environment, type FlagVariant, HttpClient, type HttpClientConfig, type HttpResponse, type IAPProduct, type IAPPurchase, IAPService, IDENTITY_ERROR_CODES, type IPaywalloClient, type IdentifyOptions, IdentityError, IdentityManager, type IdentityState, type LocalizedText, MetaBridge, type NetworkListener, type NetworkMonitorConfig, type NetworkState, type OfflineQueueConfig, PAYWALL_ERROR_CODES, PURCHASE_ERROR_CODES, type PaywallBlockConfig, type PaywallConfig, PaywallContext, PaywallError, type PaywallErrorStrings$1 as PaywallErrorStrings, PaywallModal, type PaywallNode, type PaywallPresentOptions, type PaywallResult, type PaywallState, Paywallo, PaywalloClient, type PaywalloConfig, PaywalloContext, PaywalloError, type PaywalloInitConfig, PaywalloProvider, type PaywalloProviderProps, type PreloadCampaignResult, type PreloadResult, type Product, type ProductPriceInfo, type Purchase, type PurchaseControllerConfig, PurchaseError, type PurchaseResult, type PurchaseState, type QueueEvent, type QueueItem, type QueueListener, type QueueProcessorConfig, type RequestOptions, type RestoreResult, type RetryConfig, SESSION_ERROR_CODES, type SessionConfig, SessionError, SessionManager, type SessionState, type Subscription, SubscriptionCache, type SubscriptionInfo, type SubscriptionManagerConfig, type SubscriptionPlatform, type SubscriptionStatus$1 as SubscriptionStatus, type SubscriptionStatusResponse$1 as SubscriptionStatusResponse, type TapBehavior, type TrackEventOptions, type UserProperties, type ValidatePurchaseResponse, type VariableContext, createPurchaseError, Paywallo as default, detectDeviceLanguage, getCurrentLanguage, getDefaultLanguage, getIAPService, getLocalizedString, hasVariables, identityManager, initLocalization, metaBridge, nativeStoreKit, networkMonitor, offlineQueue, parseVariables, queueProcessor, resolveText, sessionManager, setCurrentLanguage, setDefaultLanguage, subscriptionManager, usePaywallContext, usePaywallo, useProducts, usePurchase, useSubscription };
1613
+ export { ApiClient, type ApiClientConfig, type BlockType, type BoxSpacing, CAMPAIGN_ERROR_CODES, type CachedSubscription, CampaignError, type CampaignPresentOptions, type CampaignResponse, type CampaignResult, type ConditionalFlagContext, type ConditionalFlagResult, type DeviceTokenRegistration, PURCHASE_ERROR_CODES as ERROR_CODES, type EmergencyPaywallResponse, type Environment, type FlagVariant, HttpClient, type HttpClientConfig, type HttpResponse, type IAPProduct, type IAPPurchase, IAPService, IDENTITY_ERROR_CODES, type IPaywalloClient, type IPushBridge, type IdentifyOptions, IdentityError, IdentityManager, type IdentityState, type LocalizedText, MetaBridge, NOTIFICATIONS_ERROR_CODES, NativePushBridge, type NetworkListener, type NetworkMonitorConfig, type NetworkState, type NotificationEventType, type NotificationListener, type NotificationPayload, NotificationsError, NotificationsManager, ONBOARDING_ERROR_CODES, type OfflineQueueConfig, type OnboardingDropPayload, OnboardingError, OnboardingManager, type OnboardingStepPayload, PAYWALL_ERROR_CODES, PURCHASE_ERROR_CODES, type PaywallBlockConfig, type PaywallConfig, PaywallContext, PaywallError, type PaywallErrorStrings$1 as PaywallErrorStrings, PaywallModal, type PaywallNode, type PaywallPresentOptions, type PaywallResult, type PaywallState, Paywallo, PaywalloClient, type PaywalloConfig, PaywalloContext, PaywalloError, type PaywalloInitConfig, PaywalloProvider, type PaywalloProviderProps, type PrePromptHandle, type PrePromptOptions, type PreloadCampaignResult, type PreloadResult, type Product, type ProductPriceInfo, type Purchase, type PurchaseControllerConfig, PurchaseError, type PurchaseResult, type PurchaseState, type PushPermissionStatus, type PushPlatform, type QueueEvent, type QueueItem, type QueueListener, type QueueProcessorConfig, type RequestOptions, type RestoreResult, type RetryConfig, SESSION_ERROR_CODES, type SessionConfig, SessionError, SessionManager, type SessionState, type Subscription, SubscriptionCache, type SubscriptionInfo, type SubscriptionManagerConfig, type SubscriptionPlatform, type SubscriptionStatus$1 as SubscriptionStatus, type SubscriptionStatusResponse$1 as SubscriptionStatusResponse, type TapBehavior, type TrackEventOptions, type UserProperties, type ValidatePurchaseResponse, type VariableContext, createPurchaseError, Paywallo as default, detectDeviceLanguage, getCurrentLanguage, getDefaultLanguage, getIAPService, getLocalizedString, hasVariables, identityManager, initLocalization, metaBridge, nativeStoreKit, networkMonitor, notificationsManager, offlineQueue, onboardingManager, parseVariables, queueProcessor, resolveText, sessionManager, setCurrentLanguage, setDefaultLanguage, subscriptionManager, useOnboarding, usePaywallContext, usePaywallo, useProducts, usePurchase, useSubscription };