@tolinku/react-native-sdk 0.1.0 → 0.3.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/README.md CHANGED
@@ -105,6 +105,32 @@ await Tolinku.referrals.claimReward(referral_code);
105
105
  const { leaderboard } = await Tolinku.referrals.leaderboard(10);
106
106
  ```
107
107
 
108
+ ### Ecommerce
109
+
110
+ Track purchases, cart activity, and product events with built-in revenue analytics. Available on paid plans.
111
+
112
+ ```typescript
113
+ Tolinku.setUserId('user_123');
114
+
115
+ // Track a product view
116
+ await Tolinku.ecommerce.viewItem({
117
+ items: [{ item_id: 'sku_1', item_name: 'T-Shirt', price: 24.99 }]
118
+ });
119
+
120
+ // Track a purchase
121
+ await Tolinku.ecommerce.purchase({
122
+ transaction_id: 'order_456',
123
+ revenue: 49.99,
124
+ currency: 'USD',
125
+ items: [{ item_id: 'sku_1', item_name: 'T-Shirt', price: 24.99, quantity: 2 }]
126
+ });
127
+
128
+ // Flush ecommerce events
129
+ await Tolinku.ecommerce.flush();
130
+ ```
131
+
132
+ The SDK supports 13 event types covering the full shopping journey. Cart IDs are managed automatically via `AsyncStorage` and cleared after purchase. Events auto-flush when the app enters the background.
133
+
108
134
  ### Deferred Deep Links
109
135
 
110
136
  Recover deep link context for users who installed your app after clicking a link. Deferred deep linking lets you route users to specific content even when the app was not installed at the time of the click.
@@ -116,12 +142,17 @@ if (link) {
116
142
  console.log(link.deep_link_path); // e.g. "/merchant/xyz"
117
143
  }
118
144
 
119
- // Claim by device signal matching (auto-detects timezone, language, screen size)
145
+ // Claim by device signal matching. Timezone, language, screen size, pixel ratio and
146
+ // OS version are detected for you.
120
147
  const link = await Tolinku.deferred.claimBySignals({
121
- appspaceId: 'your_appspace_id',
148
+ appspaceId: '64f0a1b2c3d4e5f60718',
122
149
  });
123
150
  ```
124
151
 
152
+ `appspaceId` is your Appspace ID, not your subdomain or slug. Copy it from the dashboard
153
+ under **Integrate** or **Settings**. It looks like `64f0a1b2c3d4e5f60718`.
154
+
155
+
125
156
  ### In-App Messages (React Native Component)
126
157
 
127
158
  Display server-configured in-app messages using the `<TolinkuMessages>` component. Drop it anywhere in your React Native component tree to automatically fetch and render messages as a modal overlay. Messages are created and managed from the Tolinku dashboard without shipping app updates.
@@ -201,6 +232,25 @@ await Tolinku.destroy();
201
232
  | `claimReward(code)` | Claim a referral reward |
202
233
  | `leaderboard(limit?)` | Fetch the referral leaderboard |
203
234
 
235
+ ### `Tolinku.ecommerce`
236
+
237
+ | Method | Description |
238
+ |--------|-------------|
239
+ | `viewItem(params)` | Track a product view |
240
+ | `addToCart(params)` | Track item added to cart |
241
+ | `removeFromCart(params)` | Track item removed from cart |
242
+ | `addToWishlist(params)` | Track item added to wishlist |
243
+ | `viewCart()` | Track cart view |
244
+ | `addPaymentInfo()` | Track payment info entered |
245
+ | `beginCheckout(params?)` | Track checkout started |
246
+ | `purchase(params)` | Track a purchase |
247
+ | `refund(params)` | Track a refund |
248
+ | `search(params)` | Track a product search |
249
+ | `share(params)` | Track a product share |
250
+ | `rate(params)` | Track a product rating |
251
+ | `spendCredits(params)` | Track loyalty credits spent |
252
+ | `flush()` | Send all queued ecommerce events |
253
+
204
254
  ### `Tolinku.deferred`
205
255
 
206
256
  | Method | Description |
package/dist/index.d.mts CHANGED
@@ -111,6 +111,10 @@ interface ClaimBySignalsOptions {
111
111
  language?: string;
112
112
  screenWidth?: number;
113
113
  screenHeight?: number;
114
+ /** Defaults to `PixelRatio.get()`. Override only if you report a custom screen size. */
115
+ devicePixelRatio?: number;
116
+ /** Defaults to `Platform.Version`. Compared on the major component only. */
117
+ osVersion?: string;
114
118
  }
115
119
  /** In-app message from the API */
116
120
  interface Message {
@@ -146,6 +150,78 @@ interface ShowMessageOptions {
146
150
  onDismiss?: (messageId: string) => void;
147
151
  onButtonPress?: (action: string, messageId: string) => void;
148
152
  }
153
+ interface EcommerceItem {
154
+ item_id: string;
155
+ item_name?: string;
156
+ item_category?: string;
157
+ item_brand?: string;
158
+ item_variant?: string;
159
+ item_list_name?: string;
160
+ item_list_id?: string;
161
+ item_image_url?: string;
162
+ price?: number;
163
+ quantity?: number;
164
+ currency?: string;
165
+ coupon_code?: string;
166
+ discount?: number;
167
+ }
168
+ interface PurchaseParams {
169
+ transaction_id: string;
170
+ revenue: number;
171
+ currency: string;
172
+ items?: EcommerceItem[];
173
+ cart_id?: string;
174
+ coupon_code?: string;
175
+ discount?: number;
176
+ shipping?: number;
177
+ tax?: number;
178
+ }
179
+ interface AddToCartParams {
180
+ items: EcommerceItem[];
181
+ cart_id?: string;
182
+ }
183
+ interface RemoveFromCartParams {
184
+ items: EcommerceItem[];
185
+ cart_id?: string;
186
+ }
187
+ interface AddToWishlistParams {
188
+ items: EcommerceItem[];
189
+ }
190
+ interface BeginCheckoutParams {
191
+ revenue?: number;
192
+ currency?: string;
193
+ cart_id?: string;
194
+ items?: EcommerceItem[];
195
+ }
196
+ interface RefundParams {
197
+ transaction_id: string;
198
+ revenue: number;
199
+ currency?: string;
200
+ items?: EcommerceItem[];
201
+ }
202
+ interface ViewItemParams {
203
+ items: EcommerceItem[];
204
+ }
205
+ interface SearchParams {
206
+ search_term: string;
207
+ }
208
+ interface AddPaymentInfoParams {
209
+ cart_id?: string;
210
+ }
211
+ interface ShareParams {
212
+ item_id?: string;
213
+ url?: string;
214
+ method?: string;
215
+ }
216
+ interface RateParams {
217
+ item_id: string;
218
+ rating: number;
219
+ max_rating?: number;
220
+ }
221
+ interface SpendCreditsParams {
222
+ revenue: number;
223
+ currency: string;
224
+ }
149
225
 
150
226
  declare class HttpClient {
151
227
  private baseUrl;
@@ -185,6 +261,41 @@ declare class TolinkuError extends Error {
185
261
  constructor(message: string, status: number, code?: string);
186
262
  }
187
263
 
264
+ declare class Ecommerce {
265
+ private client;
266
+ private queue;
267
+ private flushTimer;
268
+ private appStateSubscription;
269
+ private isFlushing;
270
+ private getUserId;
271
+ private memoryCartId;
272
+ constructor(client: HttpClient, getUserId: () => string | null);
273
+ viewItem(params: ViewItemParams): Promise<void>;
274
+ addToCart(params: AddToCartParams): Promise<void>;
275
+ removeFromCart(params: RemoveFromCartParams): Promise<void>;
276
+ addToWishlist(params: AddToWishlistParams): Promise<void>;
277
+ viewCart(): Promise<void>;
278
+ addPaymentInfo(params?: AddPaymentInfoParams): Promise<void>;
279
+ beginCheckout(params: BeginCheckoutParams): Promise<void>;
280
+ purchase(params: PurchaseParams): Promise<void>;
281
+ refund(params: RefundParams): Promise<void>;
282
+ search(params: SearchParams): Promise<void>;
283
+ share(params: ShareParams): Promise<void>;
284
+ rate(params: RateParams): Promise<void>;
285
+ spendCredits(params: SpendCreditsParams): Promise<void>;
286
+ flush(): Promise<void>;
287
+ destroy(): Promise<void>;
288
+ private enqueue;
289
+ private handleAppStateChange;
290
+ private startFlushTimer;
291
+ private cancelFlushTimer;
292
+ private getOrCreateCartId;
293
+ private getCartId;
294
+ private setCartId;
295
+ private clearCartId;
296
+ private generateId;
297
+ }
298
+
188
299
  declare class Referrals {
189
300
  private client;
190
301
  constructor(client: HttpClient);
@@ -230,6 +341,7 @@ declare class Tolinku {
230
341
  static readonly VERSION = "0.1.0";
231
342
  private static client;
232
343
  private static analyticsInstance;
344
+ private static ecommerceInstance;
233
345
  private static referralsInstance;
234
346
  private static deferredInstance;
235
347
  private static _initialized;
@@ -259,9 +371,11 @@ declare class Tolinku {
259
371
  */
260
372
  static track(eventType: string, properties?: TrackProperties): Promise<void>;
261
373
  /**
262
- * Immediately flush all queued analytics events to the server.
374
+ * Immediately flush all queued analytics and ecommerce events to the server.
263
375
  */
264
376
  static flush(): Promise<void>;
377
+ /** Ecommerce: track purchases, carts, products, revenue */
378
+ static get ecommerce(): Ecommerce;
265
379
  /** Referrals: create, complete, milestone, leaderboard, claimReward */
266
380
  static get referrals(): Referrals;
267
381
  /** Deferred deep links: claimByToken, claimBySignals */
@@ -303,4 +417,4 @@ declare function TolinkuMessages({ trigger, triggerValue, onDismiss, onButtonPre
303
417
  */
304
418
  declare function isSafeUrl(url: string): boolean;
305
419
 
306
- export { type ClaimBySignalsOptions, type CompleteReferralOptions, type CompleteReferralResult, type CreateReferralOptions, type CreateReferralResult, type DeferredLink, type LeaderboardEntry, type Message, type MessageComponent, type MessageContent, type MilestoneOptions, type MilestoneResult, type ReferralInfo, type ResolvedTolinkuConfig, type ShowMessageOptions, Tolinku, type TolinkuConfig, TolinkuError, TolinkuMessages, type TrackProperties, isSafeUrl };
420
+ export { type AddPaymentInfoParams, type AddToCartParams, type AddToWishlistParams, type BeginCheckoutParams, type ClaimBySignalsOptions, type CompleteReferralOptions, type CompleteReferralResult, type CreateReferralOptions, type CreateReferralResult, type DeferredLink, type EcommerceItem, type LeaderboardEntry, type Message, type MessageComponent, type MessageContent, type MilestoneOptions, type MilestoneResult, type PurchaseParams, type RateParams, type ReferralInfo, type RefundParams, type RemoveFromCartParams, type ResolvedTolinkuConfig, type SearchParams, type ShareParams, type ShowMessageOptions, type SpendCreditsParams, Tolinku, type TolinkuConfig, TolinkuError, TolinkuMessages, type TrackProperties, type ViewItemParams, isSafeUrl };
package/dist/index.d.ts CHANGED
@@ -111,6 +111,10 @@ interface ClaimBySignalsOptions {
111
111
  language?: string;
112
112
  screenWidth?: number;
113
113
  screenHeight?: number;
114
+ /** Defaults to `PixelRatio.get()`. Override only if you report a custom screen size. */
115
+ devicePixelRatio?: number;
116
+ /** Defaults to `Platform.Version`. Compared on the major component only. */
117
+ osVersion?: string;
114
118
  }
115
119
  /** In-app message from the API */
116
120
  interface Message {
@@ -146,6 +150,78 @@ interface ShowMessageOptions {
146
150
  onDismiss?: (messageId: string) => void;
147
151
  onButtonPress?: (action: string, messageId: string) => void;
148
152
  }
153
+ interface EcommerceItem {
154
+ item_id: string;
155
+ item_name?: string;
156
+ item_category?: string;
157
+ item_brand?: string;
158
+ item_variant?: string;
159
+ item_list_name?: string;
160
+ item_list_id?: string;
161
+ item_image_url?: string;
162
+ price?: number;
163
+ quantity?: number;
164
+ currency?: string;
165
+ coupon_code?: string;
166
+ discount?: number;
167
+ }
168
+ interface PurchaseParams {
169
+ transaction_id: string;
170
+ revenue: number;
171
+ currency: string;
172
+ items?: EcommerceItem[];
173
+ cart_id?: string;
174
+ coupon_code?: string;
175
+ discount?: number;
176
+ shipping?: number;
177
+ tax?: number;
178
+ }
179
+ interface AddToCartParams {
180
+ items: EcommerceItem[];
181
+ cart_id?: string;
182
+ }
183
+ interface RemoveFromCartParams {
184
+ items: EcommerceItem[];
185
+ cart_id?: string;
186
+ }
187
+ interface AddToWishlistParams {
188
+ items: EcommerceItem[];
189
+ }
190
+ interface BeginCheckoutParams {
191
+ revenue?: number;
192
+ currency?: string;
193
+ cart_id?: string;
194
+ items?: EcommerceItem[];
195
+ }
196
+ interface RefundParams {
197
+ transaction_id: string;
198
+ revenue: number;
199
+ currency?: string;
200
+ items?: EcommerceItem[];
201
+ }
202
+ interface ViewItemParams {
203
+ items: EcommerceItem[];
204
+ }
205
+ interface SearchParams {
206
+ search_term: string;
207
+ }
208
+ interface AddPaymentInfoParams {
209
+ cart_id?: string;
210
+ }
211
+ interface ShareParams {
212
+ item_id?: string;
213
+ url?: string;
214
+ method?: string;
215
+ }
216
+ interface RateParams {
217
+ item_id: string;
218
+ rating: number;
219
+ max_rating?: number;
220
+ }
221
+ interface SpendCreditsParams {
222
+ revenue: number;
223
+ currency: string;
224
+ }
149
225
 
150
226
  declare class HttpClient {
151
227
  private baseUrl;
@@ -185,6 +261,41 @@ declare class TolinkuError extends Error {
185
261
  constructor(message: string, status: number, code?: string);
186
262
  }
187
263
 
264
+ declare class Ecommerce {
265
+ private client;
266
+ private queue;
267
+ private flushTimer;
268
+ private appStateSubscription;
269
+ private isFlushing;
270
+ private getUserId;
271
+ private memoryCartId;
272
+ constructor(client: HttpClient, getUserId: () => string | null);
273
+ viewItem(params: ViewItemParams): Promise<void>;
274
+ addToCart(params: AddToCartParams): Promise<void>;
275
+ removeFromCart(params: RemoveFromCartParams): Promise<void>;
276
+ addToWishlist(params: AddToWishlistParams): Promise<void>;
277
+ viewCart(): Promise<void>;
278
+ addPaymentInfo(params?: AddPaymentInfoParams): Promise<void>;
279
+ beginCheckout(params: BeginCheckoutParams): Promise<void>;
280
+ purchase(params: PurchaseParams): Promise<void>;
281
+ refund(params: RefundParams): Promise<void>;
282
+ search(params: SearchParams): Promise<void>;
283
+ share(params: ShareParams): Promise<void>;
284
+ rate(params: RateParams): Promise<void>;
285
+ spendCredits(params: SpendCreditsParams): Promise<void>;
286
+ flush(): Promise<void>;
287
+ destroy(): Promise<void>;
288
+ private enqueue;
289
+ private handleAppStateChange;
290
+ private startFlushTimer;
291
+ private cancelFlushTimer;
292
+ private getOrCreateCartId;
293
+ private getCartId;
294
+ private setCartId;
295
+ private clearCartId;
296
+ private generateId;
297
+ }
298
+
188
299
  declare class Referrals {
189
300
  private client;
190
301
  constructor(client: HttpClient);
@@ -230,6 +341,7 @@ declare class Tolinku {
230
341
  static readonly VERSION = "0.1.0";
231
342
  private static client;
232
343
  private static analyticsInstance;
344
+ private static ecommerceInstance;
233
345
  private static referralsInstance;
234
346
  private static deferredInstance;
235
347
  private static _initialized;
@@ -259,9 +371,11 @@ declare class Tolinku {
259
371
  */
260
372
  static track(eventType: string, properties?: TrackProperties): Promise<void>;
261
373
  /**
262
- * Immediately flush all queued analytics events to the server.
374
+ * Immediately flush all queued analytics and ecommerce events to the server.
263
375
  */
264
376
  static flush(): Promise<void>;
377
+ /** Ecommerce: track purchases, carts, products, revenue */
378
+ static get ecommerce(): Ecommerce;
265
379
  /** Referrals: create, complete, milestone, leaderboard, claimReward */
266
380
  static get referrals(): Referrals;
267
381
  /** Deferred deep links: claimByToken, claimBySignals */
@@ -303,4 +417,4 @@ declare function TolinkuMessages({ trigger, triggerValue, onDismiss, onButtonPre
303
417
  */
304
418
  declare function isSafeUrl(url: string): boolean;
305
419
 
306
- export { type ClaimBySignalsOptions, type CompleteReferralOptions, type CompleteReferralResult, type CreateReferralOptions, type CreateReferralResult, type DeferredLink, type LeaderboardEntry, type Message, type MessageComponent, type MessageContent, type MilestoneOptions, type MilestoneResult, type ReferralInfo, type ResolvedTolinkuConfig, type ShowMessageOptions, Tolinku, type TolinkuConfig, TolinkuError, TolinkuMessages, type TrackProperties, isSafeUrl };
420
+ export { type AddPaymentInfoParams, type AddToCartParams, type AddToWishlistParams, type BeginCheckoutParams, type ClaimBySignalsOptions, type CompleteReferralOptions, type CompleteReferralResult, type CreateReferralOptions, type CreateReferralResult, type DeferredLink, type EcommerceItem, type LeaderboardEntry, type Message, type MessageComponent, type MessageContent, type MilestoneOptions, type MilestoneResult, type PurchaseParams, type RateParams, type ReferralInfo, type RefundParams, type RemoveFromCartParams, type ResolvedTolinkuConfig, type SearchParams, type ShareParams, type ShowMessageOptions, type SpendCreditsParams, Tolinku, type TolinkuConfig, TolinkuError, TolinkuMessages, type TrackProperties, type ViewItemParams, isSafeUrl };
package/dist/index.js CHANGED
@@ -353,6 +353,215 @@ var Analytics = class {
353
353
  }
354
354
  }
355
355
  };
356
+ var BATCH_SIZE2 = 10;
357
+ var FLUSH_INTERVAL_MS2 = 5e3;
358
+ var MAX_QUEUE_SIZE2 = 500;
359
+ var CART_ID_STORAGE_KEY = "tolinku_ecom_cart_id";
360
+ var Ecommerce = class {
361
+ constructor(client, getUserId) {
362
+ this.queue = [];
363
+ this.flushTimer = null;
364
+ this.appStateSubscription = null;
365
+ this.isFlushing = false;
366
+ this.memoryCartId = null;
367
+ this.handleAppStateChange = (state) => {
368
+ if (state === "background" || state === "inactive") {
369
+ this.flush().catch(() => {
370
+ });
371
+ }
372
+ };
373
+ this.client = client;
374
+ this.getUserId = getUserId;
375
+ this.appStateSubscription = reactNative.AppState.addEventListener(
376
+ "change",
377
+ this.handleAppStateChange
378
+ );
379
+ }
380
+ // ─── Public methods (13 event types) ────────────────────
381
+ async viewItem(params) {
382
+ await this.enqueue({ event_type: "view_item", items: params.items });
383
+ }
384
+ async addToCart(params) {
385
+ const cartId = params.cart_id || await this.getOrCreateCartId();
386
+ await this.enqueue({ event_type: "add_to_cart", items: params.items, cart_id: cartId });
387
+ }
388
+ async removeFromCart(params) {
389
+ const cartId = params.cart_id || await this.getCartId();
390
+ await this.enqueue({ event_type: "remove_from_cart", items: params.items, cart_id: cartId });
391
+ }
392
+ async addToWishlist(params) {
393
+ await this.enqueue({ event_type: "add_to_wishlist", items: params.items });
394
+ }
395
+ async viewCart() {
396
+ const cartId = await this.getCartId();
397
+ await this.enqueue({ event_type: "view_cart", cart_id: cartId });
398
+ }
399
+ async addPaymentInfo(params) {
400
+ const cartId = params?.cart_id || await this.getCartId();
401
+ await this.enqueue({ event_type: "add_payment_info", cart_id: cartId });
402
+ }
403
+ async beginCheckout(params) {
404
+ const cartId = params.cart_id || await this.getCartId();
405
+ await this.enqueue({
406
+ event_type: "begin_checkout",
407
+ revenue: params.revenue,
408
+ currency: params.currency,
409
+ cart_id: cartId,
410
+ items: params.items
411
+ });
412
+ }
413
+ async purchase(params) {
414
+ const cartId = params.cart_id || await this.getCartId();
415
+ await this.enqueue({
416
+ event_type: "purchase",
417
+ transaction_id: params.transaction_id,
418
+ revenue: params.revenue,
419
+ currency: params.currency,
420
+ cart_id: cartId,
421
+ coupon_code: params.coupon_code,
422
+ discount: params.discount,
423
+ shipping: params.shipping,
424
+ tax: params.tax,
425
+ items: params.items
426
+ });
427
+ await this.clearCartId();
428
+ }
429
+ async refund(params) {
430
+ await this.enqueue({
431
+ event_type: "refund",
432
+ transaction_id: params.transaction_id,
433
+ revenue: params.revenue,
434
+ currency: params.currency,
435
+ items: params.items
436
+ });
437
+ }
438
+ async search(params) {
439
+ await this.enqueue({ event_type: "search", properties: { search_term: params.search_term } });
440
+ }
441
+ async share(params) {
442
+ const props = {};
443
+ if (params.item_id) props.item_id = params.item_id;
444
+ if (params.url) props.url = params.url;
445
+ if (params.method) props.method = params.method;
446
+ await this.enqueue({ event_type: "share", properties: props });
447
+ }
448
+ async rate(params) {
449
+ await this.enqueue({
450
+ event_type: "rate",
451
+ properties: {
452
+ item_id: params.item_id,
453
+ rating: String(params.rating),
454
+ ...params.max_rating != null ? { max_rating: String(params.max_rating) } : {}
455
+ }
456
+ });
457
+ }
458
+ async spendCredits(params) {
459
+ await this.enqueue({ event_type: "spend_credits", revenue: params.revenue, currency: params.currency });
460
+ }
461
+ // ─── Flush ─────────────────────────────────────────────
462
+ async flush() {
463
+ if (this.queue.length === 0 || this.isFlushing) return;
464
+ this.isFlushing = true;
465
+ const events = this.queue.splice(0);
466
+ this.cancelFlushTimer();
467
+ try {
468
+ debugLog(`Flushing ${events.length} ecommerce event(s)`);
469
+ const result = await this.client.post(
470
+ "/v1/api/analytics/ecommerce/batch",
471
+ { events }
472
+ );
473
+ if (result.errors && result.errors.length > 0) {
474
+ debugWarn(`Ecommerce batch partial failure: ${result.errors.join(", ")}`);
475
+ }
476
+ } catch (err) {
477
+ debugWarn(`Failed to flush ecommerce events: ${err.message}`);
478
+ const spaceLeft = MAX_QUEUE_SIZE2 - this.queue.length;
479
+ if (spaceLeft > 0) {
480
+ this.queue.unshift(...events.slice(0, spaceLeft));
481
+ }
482
+ } finally {
483
+ this.isFlushing = false;
484
+ }
485
+ }
486
+ async destroy() {
487
+ this.cancelFlushTimer();
488
+ this.appStateSubscription?.remove();
489
+ this.appStateSubscription = null;
490
+ try {
491
+ await this.flush();
492
+ } catch {
493
+ }
494
+ }
495
+ // ─── Private ───────────────────────────────────────────
496
+ async enqueue(event) {
497
+ const userId = this.getUserId();
498
+ if (userId) event.user_id = userId;
499
+ if (this.queue.length >= MAX_QUEUE_SIZE2) {
500
+ debugWarn(`Ecommerce queue full (${MAX_QUEUE_SIZE2}). Dropping oldest event.`);
501
+ this.queue.shift();
502
+ }
503
+ this.queue.push(event);
504
+ if (this.queue.length >= BATCH_SIZE2) {
505
+ await this.flush();
506
+ } else if (this.queue.length === 1) {
507
+ this.startFlushTimer();
508
+ }
509
+ }
510
+ startFlushTimer() {
511
+ this.cancelFlushTimer();
512
+ this.flushTimer = setTimeout(() => {
513
+ this.flush().catch((err) => {
514
+ debugWarn(`Ecommerce timer flush failed: ${err.message}`);
515
+ });
516
+ }, FLUSH_INTERVAL_MS2);
517
+ }
518
+ cancelFlushTimer() {
519
+ if (this.flushTimer !== null) {
520
+ clearTimeout(this.flushTimer);
521
+ this.flushTimer = null;
522
+ }
523
+ }
524
+ // ─── Cart ID lifecycle (AsyncStorage + memory fallback) ─
525
+ async getOrCreateCartId() {
526
+ const existing = await this.getCartId();
527
+ if (existing) return existing;
528
+ const cartId = this.generateId();
529
+ await this.setCartId(cartId);
530
+ return cartId;
531
+ }
532
+ async getCartId() {
533
+ try {
534
+ const stored = await AsyncStorage__default.default.getItem(CART_ID_STORAGE_KEY);
535
+ if (stored) return stored;
536
+ } catch {
537
+ }
538
+ return this.memoryCartId || void 0;
539
+ }
540
+ async setCartId(cartId) {
541
+ this.memoryCartId = cartId;
542
+ try {
543
+ await AsyncStorage__default.default.setItem(CART_ID_STORAGE_KEY, cartId);
544
+ } catch {
545
+ }
546
+ }
547
+ async clearCartId() {
548
+ this.memoryCartId = null;
549
+ try {
550
+ await AsyncStorage__default.default.removeItem(CART_ID_STORAGE_KEY);
551
+ } catch {
552
+ }
553
+ }
554
+ generateId() {
555
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
556
+ return crypto.randomUUID();
557
+ }
558
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
559
+ const r = Math.random() * 16 | 0;
560
+ const v = c === "x" ? r : r & 3 | 8;
561
+ return v.toString(16);
562
+ });
563
+ }
564
+ };
356
565
 
357
566
  // src/referrals.ts
358
567
  var Referrals = class {
@@ -445,15 +654,29 @@ var Deferred = class {
445
654
  try {
446
655
  const { width, height } = reactNative.Dimensions.get("screen");
447
656
  const resolvedTimezone = options.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
448
- const resolvedLanguage = options.language || "en";
657
+ const resolvedLanguage = options.language || (typeof Intl !== "undefined" && typeof Intl.DateTimeFormat === "function" ? Intl.DateTimeFormat().resolvedOptions().locale : void 0) || "en";
449
658
  return await this.client.postPublic("/v1/api/deferred/claim-by-signals", {
450
659
  appspace_id: options.appspaceId,
451
660
  timezone: resolvedTimezone,
452
661
  language: resolvedLanguage,
453
662
  screen_width: options.screenWidth || width,
454
- screen_height: options.screenHeight || height
663
+ screen_height: options.screenHeight || height,
664
+ // Separates devices reporting identical dp dimensions.
665
+ device_pixel_ratio: options.devicePixelRatio || reactNative.PixelRatio.get(),
666
+ os_version: options.osVersion || String(reactNative.Platform.Version)
455
667
  });
456
668
  } catch (err) {
669
+ const status = err?.statusCode ?? err?.status;
670
+ if (status === 404) {
671
+ debugWarn("Deferred claimBySignals: no match for this device.");
672
+ return null;
673
+ }
674
+ if (status === 403) {
675
+ console.warn(
676
+ `[Tolinku] claimBySignals failed with HTTP 403. Check that appspaceId is your Appspace ID (copy it from the dashboard under Settings), not your subdomain or slug. ${err.message}`
677
+ );
678
+ return null;
679
+ }
457
680
  debugWarn(`Deferred claimBySignals failed: ${err.message}`);
458
681
  return null;
459
682
  }
@@ -568,6 +791,7 @@ var _Tolinku = class _Tolinku {
568
791
  setStorageNamespace(config.apiKey);
569
792
  _Tolinku.client = new HttpClient(resolvedConfig);
570
793
  _Tolinku.analyticsInstance = new Analytics(_Tolinku.client);
794
+ _Tolinku.ecommerceInstance = new Ecommerce(_Tolinku.client, () => _Tolinku._userId);
571
795
  _Tolinku.referralsInstance = new Referrals(_Tolinku.client);
572
796
  _Tolinku.deferredInstance = new Deferred(_Tolinku.client);
573
797
  _Tolinku._initialized = true;
@@ -608,13 +832,23 @@ var _Tolinku = class _Tolinku {
608
832
  return _Tolinku.analyticsInstance.track(eventType, mergedProps);
609
833
  }
610
834
  /**
611
- * Immediately flush all queued analytics events to the server.
835
+ * Immediately flush all queued analytics and ecommerce events to the server.
612
836
  */
613
837
  static async flush() {
614
838
  if (!_Tolinku.analyticsInstance) {
615
839
  throw new Error("Tolinku: SDK not initialized. Call Tolinku.init() first.");
616
840
  }
617
- return _Tolinku.analyticsInstance.flush();
841
+ await Promise.all([
842
+ _Tolinku.analyticsInstance.flush(),
843
+ _Tolinku.ecommerceInstance?.flush()
844
+ ]);
845
+ }
846
+ /** Ecommerce: track purchases, carts, products, revenue */
847
+ static get ecommerce() {
848
+ if (!_Tolinku.ecommerceInstance) {
849
+ throw new Error("Tolinku: SDK not initialized. Call Tolinku.init() first.");
850
+ }
851
+ return _Tolinku.ecommerceInstance;
618
852
  }
619
853
  /** Referrals: create, complete, milestone, leaderboard, claimReward */
620
854
  static get referrals() {
@@ -641,12 +875,16 @@ var _Tolinku = class _Tolinku {
641
875
  if (_Tolinku.analyticsInstance) {
642
876
  await _Tolinku.analyticsInstance.destroy();
643
877
  }
878
+ if (_Tolinku.ecommerceInstance) {
879
+ await _Tolinku.ecommerceInstance.destroy();
880
+ }
644
881
  if (_Tolinku.client) {
645
882
  _Tolinku.client.abort();
646
883
  }
647
884
  resetStorageNamespace();
648
885
  _Tolinku.client = null;
649
886
  _Tolinku.analyticsInstance = null;
887
+ _Tolinku.ecommerceInstance = null;
650
888
  _Tolinku.referralsInstance = null;
651
889
  _Tolinku.deferredInstance = null;
652
890
  _Tolinku._initialized = false;
@@ -657,6 +895,7 @@ var _Tolinku = class _Tolinku {
657
895
  _Tolinku.VERSION = SDK_VERSION;
658
896
  _Tolinku.client = null;
659
897
  _Tolinku.analyticsInstance = null;
898
+ _Tolinku.ecommerceInstance = null;
660
899
  _Tolinku.referralsInstance = null;
661
900
  _Tolinku.deferredInstance = null;
662
901
  _Tolinku._initialized = false;