@tolinku/react-native-sdk 0.1.0 → 0.2.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.mjs CHANGED
@@ -347,6 +347,215 @@ var Analytics = class {
347
347
  }
348
348
  }
349
349
  };
350
+ var BATCH_SIZE2 = 10;
351
+ var FLUSH_INTERVAL_MS2 = 5e3;
352
+ var MAX_QUEUE_SIZE2 = 500;
353
+ var CART_ID_STORAGE_KEY = "tolinku_ecom_cart_id";
354
+ var Ecommerce = class {
355
+ constructor(client, getUserId) {
356
+ this.queue = [];
357
+ this.flushTimer = null;
358
+ this.appStateSubscription = null;
359
+ this.isFlushing = false;
360
+ this.memoryCartId = null;
361
+ this.handleAppStateChange = (state) => {
362
+ if (state === "background" || state === "inactive") {
363
+ this.flush().catch(() => {
364
+ });
365
+ }
366
+ };
367
+ this.client = client;
368
+ this.getUserId = getUserId;
369
+ this.appStateSubscription = AppState.addEventListener(
370
+ "change",
371
+ this.handleAppStateChange
372
+ );
373
+ }
374
+ // ─── Public methods (13 event types) ────────────────────
375
+ async viewItem(params) {
376
+ await this.enqueue({ event_type: "view_item", items: params.items });
377
+ }
378
+ async addToCart(params) {
379
+ const cartId = params.cart_id || await this.getOrCreateCartId();
380
+ await this.enqueue({ event_type: "add_to_cart", items: params.items, cart_id: cartId });
381
+ }
382
+ async removeFromCart(params) {
383
+ const cartId = params.cart_id || await this.getCartId();
384
+ await this.enqueue({ event_type: "remove_from_cart", items: params.items, cart_id: cartId });
385
+ }
386
+ async addToWishlist(params) {
387
+ await this.enqueue({ event_type: "add_to_wishlist", items: params.items });
388
+ }
389
+ async viewCart() {
390
+ const cartId = await this.getCartId();
391
+ await this.enqueue({ event_type: "view_cart", cart_id: cartId });
392
+ }
393
+ async addPaymentInfo(params) {
394
+ const cartId = params?.cart_id || await this.getCartId();
395
+ await this.enqueue({ event_type: "add_payment_info", cart_id: cartId });
396
+ }
397
+ async beginCheckout(params) {
398
+ const cartId = params.cart_id || await this.getCartId();
399
+ await this.enqueue({
400
+ event_type: "begin_checkout",
401
+ revenue: params.revenue,
402
+ currency: params.currency,
403
+ cart_id: cartId,
404
+ items: params.items
405
+ });
406
+ }
407
+ async purchase(params) {
408
+ const cartId = params.cart_id || await this.getCartId();
409
+ await this.enqueue({
410
+ event_type: "purchase",
411
+ transaction_id: params.transaction_id,
412
+ revenue: params.revenue,
413
+ currency: params.currency,
414
+ cart_id: cartId,
415
+ coupon_code: params.coupon_code,
416
+ discount: params.discount,
417
+ shipping: params.shipping,
418
+ tax: params.tax,
419
+ items: params.items
420
+ });
421
+ await this.clearCartId();
422
+ }
423
+ async refund(params) {
424
+ await this.enqueue({
425
+ event_type: "refund",
426
+ transaction_id: params.transaction_id,
427
+ revenue: params.revenue,
428
+ currency: params.currency,
429
+ items: params.items
430
+ });
431
+ }
432
+ async search(params) {
433
+ await this.enqueue({ event_type: "search", properties: { search_term: params.search_term } });
434
+ }
435
+ async share(params) {
436
+ const props = {};
437
+ if (params.item_id) props.item_id = params.item_id;
438
+ if (params.url) props.url = params.url;
439
+ if (params.method) props.method = params.method;
440
+ await this.enqueue({ event_type: "share", properties: props });
441
+ }
442
+ async rate(params) {
443
+ await this.enqueue({
444
+ event_type: "rate",
445
+ properties: {
446
+ item_id: params.item_id,
447
+ rating: String(params.rating),
448
+ ...params.max_rating != null ? { max_rating: String(params.max_rating) } : {}
449
+ }
450
+ });
451
+ }
452
+ async spendCredits(params) {
453
+ await this.enqueue({ event_type: "spend_credits", revenue: params.revenue, currency: params.currency });
454
+ }
455
+ // ─── Flush ─────────────────────────────────────────────
456
+ async flush() {
457
+ if (this.queue.length === 0 || this.isFlushing) return;
458
+ this.isFlushing = true;
459
+ const events = this.queue.splice(0);
460
+ this.cancelFlushTimer();
461
+ try {
462
+ debugLog(`Flushing ${events.length} ecommerce event(s)`);
463
+ const result = await this.client.post(
464
+ "/v1/api/analytics/ecommerce/batch",
465
+ { events }
466
+ );
467
+ if (result.errors && result.errors.length > 0) {
468
+ debugWarn(`Ecommerce batch partial failure: ${result.errors.join(", ")}`);
469
+ }
470
+ } catch (err) {
471
+ debugWarn(`Failed to flush ecommerce events: ${err.message}`);
472
+ const spaceLeft = MAX_QUEUE_SIZE2 - this.queue.length;
473
+ if (spaceLeft > 0) {
474
+ this.queue.unshift(...events.slice(0, spaceLeft));
475
+ }
476
+ } finally {
477
+ this.isFlushing = false;
478
+ }
479
+ }
480
+ async destroy() {
481
+ this.cancelFlushTimer();
482
+ this.appStateSubscription?.remove();
483
+ this.appStateSubscription = null;
484
+ try {
485
+ await this.flush();
486
+ } catch {
487
+ }
488
+ }
489
+ // ─── Private ───────────────────────────────────────────
490
+ async enqueue(event) {
491
+ const userId = this.getUserId();
492
+ if (userId) event.user_id = userId;
493
+ if (this.queue.length >= MAX_QUEUE_SIZE2) {
494
+ debugWarn(`Ecommerce queue full (${MAX_QUEUE_SIZE2}). Dropping oldest event.`);
495
+ this.queue.shift();
496
+ }
497
+ this.queue.push(event);
498
+ if (this.queue.length >= BATCH_SIZE2) {
499
+ await this.flush();
500
+ } else if (this.queue.length === 1) {
501
+ this.startFlushTimer();
502
+ }
503
+ }
504
+ startFlushTimer() {
505
+ this.cancelFlushTimer();
506
+ this.flushTimer = setTimeout(() => {
507
+ this.flush().catch((err) => {
508
+ debugWarn(`Ecommerce timer flush failed: ${err.message}`);
509
+ });
510
+ }, FLUSH_INTERVAL_MS2);
511
+ }
512
+ cancelFlushTimer() {
513
+ if (this.flushTimer !== null) {
514
+ clearTimeout(this.flushTimer);
515
+ this.flushTimer = null;
516
+ }
517
+ }
518
+ // ─── Cart ID lifecycle (AsyncStorage + memory fallback) ─
519
+ async getOrCreateCartId() {
520
+ const existing = await this.getCartId();
521
+ if (existing) return existing;
522
+ const cartId = this.generateId();
523
+ await this.setCartId(cartId);
524
+ return cartId;
525
+ }
526
+ async getCartId() {
527
+ try {
528
+ const stored = await AsyncStorage.getItem(CART_ID_STORAGE_KEY);
529
+ if (stored) return stored;
530
+ } catch {
531
+ }
532
+ return this.memoryCartId || void 0;
533
+ }
534
+ async setCartId(cartId) {
535
+ this.memoryCartId = cartId;
536
+ try {
537
+ await AsyncStorage.setItem(CART_ID_STORAGE_KEY, cartId);
538
+ } catch {
539
+ }
540
+ }
541
+ async clearCartId() {
542
+ this.memoryCartId = null;
543
+ try {
544
+ await AsyncStorage.removeItem(CART_ID_STORAGE_KEY);
545
+ } catch {
546
+ }
547
+ }
548
+ generateId() {
549
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
550
+ return crypto.randomUUID();
551
+ }
552
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
553
+ const r = Math.random() * 16 | 0;
554
+ const v = c === "x" ? r : r & 3 | 8;
555
+ return v.toString(16);
556
+ });
557
+ }
558
+ };
350
559
 
351
560
  // src/referrals.ts
352
561
  var Referrals = class {
@@ -562,6 +771,7 @@ var _Tolinku = class _Tolinku {
562
771
  setStorageNamespace(config.apiKey);
563
772
  _Tolinku.client = new HttpClient(resolvedConfig);
564
773
  _Tolinku.analyticsInstance = new Analytics(_Tolinku.client);
774
+ _Tolinku.ecommerceInstance = new Ecommerce(_Tolinku.client, () => _Tolinku._userId);
565
775
  _Tolinku.referralsInstance = new Referrals(_Tolinku.client);
566
776
  _Tolinku.deferredInstance = new Deferred(_Tolinku.client);
567
777
  _Tolinku._initialized = true;
@@ -602,13 +812,23 @@ var _Tolinku = class _Tolinku {
602
812
  return _Tolinku.analyticsInstance.track(eventType, mergedProps);
603
813
  }
604
814
  /**
605
- * Immediately flush all queued analytics events to the server.
815
+ * Immediately flush all queued analytics and ecommerce events to the server.
606
816
  */
607
817
  static async flush() {
608
818
  if (!_Tolinku.analyticsInstance) {
609
819
  throw new Error("Tolinku: SDK not initialized. Call Tolinku.init() first.");
610
820
  }
611
- return _Tolinku.analyticsInstance.flush();
821
+ await Promise.all([
822
+ _Tolinku.analyticsInstance.flush(),
823
+ _Tolinku.ecommerceInstance?.flush()
824
+ ]);
825
+ }
826
+ /** Ecommerce: track purchases, carts, products, revenue */
827
+ static get ecommerce() {
828
+ if (!_Tolinku.ecommerceInstance) {
829
+ throw new Error("Tolinku: SDK not initialized. Call Tolinku.init() first.");
830
+ }
831
+ return _Tolinku.ecommerceInstance;
612
832
  }
613
833
  /** Referrals: create, complete, milestone, leaderboard, claimReward */
614
834
  static get referrals() {
@@ -635,12 +855,16 @@ var _Tolinku = class _Tolinku {
635
855
  if (_Tolinku.analyticsInstance) {
636
856
  await _Tolinku.analyticsInstance.destroy();
637
857
  }
858
+ if (_Tolinku.ecommerceInstance) {
859
+ await _Tolinku.ecommerceInstance.destroy();
860
+ }
638
861
  if (_Tolinku.client) {
639
862
  _Tolinku.client.abort();
640
863
  }
641
864
  resetStorageNamespace();
642
865
  _Tolinku.client = null;
643
866
  _Tolinku.analyticsInstance = null;
867
+ _Tolinku.ecommerceInstance = null;
644
868
  _Tolinku.referralsInstance = null;
645
869
  _Tolinku.deferredInstance = null;
646
870
  _Tolinku._initialized = false;
@@ -651,6 +875,7 @@ var _Tolinku = class _Tolinku {
651
875
  _Tolinku.VERSION = SDK_VERSION;
652
876
  _Tolinku.client = null;
653
877
  _Tolinku.analyticsInstance = null;
878
+ _Tolinku.ecommerceInstance = null;
654
879
  _Tolinku.referralsInstance = null;
655
880
  _Tolinku.deferredInstance = null;
656
881
  _Tolinku._initialized = false;