@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 +52 -2
- package/dist/index.d.mts +116 -2
- package/dist/index.d.ts +116 -2
- package/dist/index.js +243 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +244 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StyleSheet, AppState, Dimensions, Modal, Pressable, TouchableOpacity, Text, ScrollView, View, ImageBackground, Image, Linking } from 'react-native';
|
|
1
|
+
import { StyleSheet, AppState, Dimensions, Platform, PixelRatio, Modal, Pressable, TouchableOpacity, Text, ScrollView, View, ImageBackground, Image, Linking } from 'react-native';
|
|
2
2
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
3
3
|
import { useState, useRef, useMemo, useEffect, useCallback } from 'react';
|
|
4
4
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
@@ -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 {
|
|
@@ -439,15 +648,29 @@ var Deferred = class {
|
|
|
439
648
|
try {
|
|
440
649
|
const { width, height } = Dimensions.get("screen");
|
|
441
650
|
const resolvedTimezone = options.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
442
|
-
const resolvedLanguage = options.language || "en";
|
|
651
|
+
const resolvedLanguage = options.language || (typeof Intl !== "undefined" && typeof Intl.DateTimeFormat === "function" ? Intl.DateTimeFormat().resolvedOptions().locale : void 0) || "en";
|
|
443
652
|
return await this.client.postPublic("/v1/api/deferred/claim-by-signals", {
|
|
444
653
|
appspace_id: options.appspaceId,
|
|
445
654
|
timezone: resolvedTimezone,
|
|
446
655
|
language: resolvedLanguage,
|
|
447
656
|
screen_width: options.screenWidth || width,
|
|
448
|
-
screen_height: options.screenHeight || height
|
|
657
|
+
screen_height: options.screenHeight || height,
|
|
658
|
+
// Separates devices reporting identical dp dimensions.
|
|
659
|
+
device_pixel_ratio: options.devicePixelRatio || PixelRatio.get(),
|
|
660
|
+
os_version: options.osVersion || String(Platform.Version)
|
|
449
661
|
});
|
|
450
662
|
} catch (err) {
|
|
663
|
+
const status = err?.statusCode ?? err?.status;
|
|
664
|
+
if (status === 404) {
|
|
665
|
+
debugWarn("Deferred claimBySignals: no match for this device.");
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
if (status === 403) {
|
|
669
|
+
console.warn(
|
|
670
|
+
`[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}`
|
|
671
|
+
);
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
451
674
|
debugWarn(`Deferred claimBySignals failed: ${err.message}`);
|
|
452
675
|
return null;
|
|
453
676
|
}
|
|
@@ -562,6 +785,7 @@ var _Tolinku = class _Tolinku {
|
|
|
562
785
|
setStorageNamespace(config.apiKey);
|
|
563
786
|
_Tolinku.client = new HttpClient(resolvedConfig);
|
|
564
787
|
_Tolinku.analyticsInstance = new Analytics(_Tolinku.client);
|
|
788
|
+
_Tolinku.ecommerceInstance = new Ecommerce(_Tolinku.client, () => _Tolinku._userId);
|
|
565
789
|
_Tolinku.referralsInstance = new Referrals(_Tolinku.client);
|
|
566
790
|
_Tolinku.deferredInstance = new Deferred(_Tolinku.client);
|
|
567
791
|
_Tolinku._initialized = true;
|
|
@@ -602,13 +826,23 @@ var _Tolinku = class _Tolinku {
|
|
|
602
826
|
return _Tolinku.analyticsInstance.track(eventType, mergedProps);
|
|
603
827
|
}
|
|
604
828
|
/**
|
|
605
|
-
* Immediately flush all queued analytics events to the server.
|
|
829
|
+
* Immediately flush all queued analytics and ecommerce events to the server.
|
|
606
830
|
*/
|
|
607
831
|
static async flush() {
|
|
608
832
|
if (!_Tolinku.analyticsInstance) {
|
|
609
833
|
throw new Error("Tolinku: SDK not initialized. Call Tolinku.init() first.");
|
|
610
834
|
}
|
|
611
|
-
|
|
835
|
+
await Promise.all([
|
|
836
|
+
_Tolinku.analyticsInstance.flush(),
|
|
837
|
+
_Tolinku.ecommerceInstance?.flush()
|
|
838
|
+
]);
|
|
839
|
+
}
|
|
840
|
+
/** Ecommerce: track purchases, carts, products, revenue */
|
|
841
|
+
static get ecommerce() {
|
|
842
|
+
if (!_Tolinku.ecommerceInstance) {
|
|
843
|
+
throw new Error("Tolinku: SDK not initialized. Call Tolinku.init() first.");
|
|
844
|
+
}
|
|
845
|
+
return _Tolinku.ecommerceInstance;
|
|
612
846
|
}
|
|
613
847
|
/** Referrals: create, complete, milestone, leaderboard, claimReward */
|
|
614
848
|
static get referrals() {
|
|
@@ -635,12 +869,16 @@ var _Tolinku = class _Tolinku {
|
|
|
635
869
|
if (_Tolinku.analyticsInstance) {
|
|
636
870
|
await _Tolinku.analyticsInstance.destroy();
|
|
637
871
|
}
|
|
872
|
+
if (_Tolinku.ecommerceInstance) {
|
|
873
|
+
await _Tolinku.ecommerceInstance.destroy();
|
|
874
|
+
}
|
|
638
875
|
if (_Tolinku.client) {
|
|
639
876
|
_Tolinku.client.abort();
|
|
640
877
|
}
|
|
641
878
|
resetStorageNamespace();
|
|
642
879
|
_Tolinku.client = null;
|
|
643
880
|
_Tolinku.analyticsInstance = null;
|
|
881
|
+
_Tolinku.ecommerceInstance = null;
|
|
644
882
|
_Tolinku.referralsInstance = null;
|
|
645
883
|
_Tolinku.deferredInstance = null;
|
|
646
884
|
_Tolinku._initialized = false;
|
|
@@ -651,6 +889,7 @@ var _Tolinku = class _Tolinku {
|
|
|
651
889
|
_Tolinku.VERSION = SDK_VERSION;
|
|
652
890
|
_Tolinku.client = null;
|
|
653
891
|
_Tolinku.analyticsInstance = null;
|
|
892
|
+
_Tolinku.ecommerceInstance = null;
|
|
654
893
|
_Tolinku.referralsInstance = null;
|
|
655
894
|
_Tolinku.deferredInstance = null;
|
|
656
895
|
_Tolinku._initialized = false;
|