@hyve-sdk/js 2.11.0 → 2.11.1

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
@@ -33,8 +33,8 @@ interface TelemetryEvent {
33
33
  event_action: string;
34
34
  /** Sub-action for detailed tracking (optional) */
35
35
  event_sub_action?: string | null;
36
- /** Event details as JSON string or object (optional) */
37
- event_details?: Record<string, any> | string | null;
36
+ /** Event details as object (optional) */
37
+ event_details?: Record<string, unknown> | null;
38
38
  /** Mapping details as JSON string or object (optional) */
39
39
  mapping_details?: Record<string, any> | string | null;
40
40
  }
package/dist/index.d.ts CHANGED
@@ -33,8 +33,8 @@ interface TelemetryEvent {
33
33
  event_action: string;
34
34
  /** Sub-action for detailed tracking (optional) */
35
35
  event_sub_action?: string | null;
36
- /** Event details as JSON string or object (optional) */
37
- event_details?: Record<string, any> | string | null;
36
+ /** Event details as object (optional) */
37
+ event_details?: Record<string, unknown> | null;
38
38
  /** Mapping details as JSON string or object (optional) */
39
39
  mapping_details?: Record<string, any> | string | null;
40
40
  }
package/dist/index.js CHANGED
@@ -448,6 +448,309 @@ function generateUUID() {
448
448
  return (0, import_uuid.v4)();
449
449
  }
450
450
 
451
+ // src/utils/device-info.ts
452
+ function detectBrowser(ua) {
453
+ if (/Edg\/(\d+)/.test(ua)) return { name: "Edge", major: RegExp.$1 };
454
+ if (/OPR\/(\d+)/.test(ua)) return { name: "Opera", major: RegExp.$1 };
455
+ if (/Chrome\/(\d+)/.test(ua)) return { name: "Chrome", major: RegExp.$1 };
456
+ if (/Firefox\/(\d+)/.test(ua)) return { name: "Firefox", major: RegExp.$1 };
457
+ if (/Version\/(\d+).*Safari/.test(ua)) return { name: "Safari", major: RegExp.$1 };
458
+ return { name: "unknown", major: "" };
459
+ }
460
+ function detectOS(ua) {
461
+ if (/Windows NT ([\d.]+)/.test(ua)) return { name: "Windows", version: RegExp.$1 };
462
+ if (/Mac OS X ([\d_]+)/.test(ua)) return { name: "macOS", version: RegExp.$1.replace(/_/g, ".") };
463
+ if (/Android ([\d.]+)/.test(ua)) return { name: "Android", version: RegExp.$1 };
464
+ if (/iPhone OS ([\d_]+)/.test(ua)) return { name: "iOS", version: RegExp.$1.replace(/_/g, ".") };
465
+ if (/iPad.*OS ([\d_]+)/.test(ua)) return { name: "iOS", version: RegExp.$1.replace(/_/g, ".") };
466
+ if (/CrOS/.test(ua)) return { name: "Chrome OS", version: "" };
467
+ if (/Linux/.test(ua)) return { name: "Linux", version: "" };
468
+ return { name: "unknown", version: "" };
469
+ }
470
+ function detectDeviceType(ua) {
471
+ if (/Mobi|Android(?!.*Tablet)|iPhone|iPod/i.test(ua)) return "mobile";
472
+ if (/Tablet|iPad|Android.*Tablet/i.test(ua)) return "tablet";
473
+ return "desktop";
474
+ }
475
+ var _cache = null;
476
+ function getEssentialDeviceInfo() {
477
+ if (_cache) return _cache;
478
+ if (typeof window === "undefined" || typeof navigator === "undefined") {
479
+ return {
480
+ browser: "unknown",
481
+ os: "unknown",
482
+ device_type: null,
483
+ viewport: "0x0",
484
+ screen: "0x0",
485
+ pixel_ratio: null,
486
+ language: null,
487
+ timezone: null,
488
+ country_code: null
489
+ };
490
+ }
491
+ const ua = navigator.userAgent;
492
+ const browser = detectBrowser(ua);
493
+ const os = detectOS(ua);
494
+ const deviceType = detectDeviceType(ua);
495
+ let timezone = null;
496
+ try {
497
+ timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
498
+ } catch {
499
+ }
500
+ const language = navigator.language || null;
501
+ let country_code = null;
502
+ if (language) {
503
+ const parts = language.split("-");
504
+ if (parts.length > 1) {
505
+ country_code = parts[parts.length - 1].toUpperCase();
506
+ }
507
+ }
508
+ _cache = {
509
+ browser: browser.major ? `${browser.name} ${browser.major}` : browser.name,
510
+ os: os.version ? `${os.name} ${os.version}` : os.name,
511
+ device_type: deviceType,
512
+ viewport: `${window.innerWidth ?? 0}x${window.innerHeight ?? 0}`,
513
+ screen: `${window.screen?.width ?? 0}x${window.screen?.height ?? 0}`,
514
+ pixel_ratio: window.devicePixelRatio || null,
515
+ language,
516
+ timezone,
517
+ country_code
518
+ };
519
+ return _cache;
520
+ }
521
+
522
+ // src/utils/attribution.ts
523
+ var EMBEDDING_PLATFORM_PATTERNS = {
524
+ twitter: [/t\.co/i, /twitter\.com/i, /x\.com/i, /mobile\.twitter\.com/i, /mobile\.x\.com/i],
525
+ discord: [/discord\.com/i, /discordapp\.com/i, /discord\.gg/i],
526
+ telegram: [/telegram\.org/i, /telegram\.me/i, /t\.me/i, /web\.telegram\.org/i],
527
+ facebook: [/facebook\.com/i, /fb\.com/i, /m\.facebook\.com/i, /mobile\.facebook\.com/i, /meta\.com/i],
528
+ reddit: [/reddit\.com/i, /redd\.it/i, /old\.reddit\.com/i, /www\.reddit\.com/i],
529
+ linkedin: [/linkedin\.com/i, /lnkd\.in/i],
530
+ instagram: [/instagram\.com/i, /instagr\.am/i],
531
+ youtube: [/youtube\.com/i, /youtu\.be/i, /m\.youtube\.com/i],
532
+ twitch: [/twitch\.tv/i, /m\.twitch\.tv/i],
533
+ farcaster: [/warpcast\.com/i, /farcaster\.xyz/i, /miniapps\.farcaster\.xyz/i],
534
+ worldapp: [/world\.app/i, /worldcoin\.org/i],
535
+ tiktok: [/tiktok\.com/i, /vm\.tiktok\.com/i]
536
+ };
537
+ var AttributionManager = class _AttributionManager {
538
+ static instance;
539
+ cachedData = null;
540
+ STORAGE_KEY = "hyve_attribution";
541
+ SESSION_COUNT_KEY = "hyve_session_count";
542
+ static getInstance() {
543
+ if (!_AttributionManager.instance) {
544
+ _AttributionManager.instance = new _AttributionManager();
545
+ }
546
+ return _AttributionManager.instance;
547
+ }
548
+ captureAttribution() {
549
+ if (typeof window === "undefined") return this.getEmptyAttribution();
550
+ if (this.cachedData) return this.cachedData;
551
+ const urlParams = new URLSearchParams(window.location.search);
552
+ const referrerUrl = document.referrer;
553
+ const referrerDomain = this.extractDomain(referrerUrl);
554
+ const platformDetection = this.detectEmbeddingPlatform(urlParams, referrerUrl);
555
+ const attribution = {
556
+ utm_source: urlParams.get("utm_source"),
557
+ utm_medium: urlParams.get("utm_medium"),
558
+ utm_campaign: urlParams.get("utm_campaign"),
559
+ utm_term: urlParams.get("utm_term"),
560
+ utm_content: urlParams.get("utm_content"),
561
+ utm_id: urlParams.get("utm_id"),
562
+ utm_source_platform: urlParams.get("utm_source_platform"),
563
+ platform_id: platformDetection.platform,
564
+ is_embedded: platformDetection.isEmbedded,
565
+ referrer_url: referrerUrl || null,
566
+ referrer_domain: referrerDomain,
567
+ referrer_source: this.categorizeReferrer(referrerUrl),
568
+ is_first_party_referrer: this.isFirstPartyReferrer(referrerDomain),
569
+ traffic_type: this.classifyTrafficType(urlParams, referrerUrl),
570
+ first_visit_timestamp: Date.now(),
571
+ session_count: this.getSessionCount() + 1,
572
+ is_bot: this.detectBot(),
573
+ attribution_confidence: this.calculateConfidence(urlParams, referrerUrl)
574
+ };
575
+ this.cachedData = attribution;
576
+ this.storeAttribution(attribution);
577
+ return attribution;
578
+ }
579
+ getAttribution() {
580
+ if (this.cachedData) return this.cachedData;
581
+ if (typeof window === "undefined") return this.getEmptyAttribution();
582
+ const stored = sessionStorage.getItem(this.STORAGE_KEY);
583
+ if (stored) {
584
+ try {
585
+ const parsed = JSON.parse(stored);
586
+ if (this.isValidAttributionData(parsed)) {
587
+ this.cachedData = parsed;
588
+ return parsed;
589
+ }
590
+ } catch {
591
+ }
592
+ }
593
+ return this.captureAttribution();
594
+ }
595
+ classifyTrafficType(urlParams, referrerUrl) {
596
+ if (urlParams.get("utm_source")) return "campaign";
597
+ if (!referrerUrl) return "direct";
598
+ const domain = this.extractDomain(referrerUrl);
599
+ if (this.isSearchEngine(domain)) return "search";
600
+ if (this.isSocialPlatform(domain)) return "social";
601
+ if (this.isEmailClient(domain)) return "email";
602
+ if (this.isFirstPartyReferrer(domain)) return "direct";
603
+ return "referral";
604
+ }
605
+ isCdnUrl(url) {
606
+ if (!url) return false;
607
+ return [/cloudfront\.net/i, /cdn\.[\w-]+\.com/i, /\.cdn\./i, /fastly\.net/i, /akamai/i, /\.cloudflare\./i].some(
608
+ (p) => p.test(url)
609
+ );
610
+ }
611
+ detectEmbeddingPlatform(urlParams, referrerUrl) {
612
+ let isEmbedded = false;
613
+ try {
614
+ isEmbedded = window.self !== window.top;
615
+ } catch {
616
+ isEmbedded = true;
617
+ }
618
+ if (!isEmbedded) return { platform: "web", isEmbedded: false };
619
+ const platformParam = urlParams.get("platform")?.toLowerCase().trim();
620
+ if (platformParam && /^[a-z0-9_-]+$/.test(platformParam)) {
621
+ return { platform: platformParam, isEmbedded: true };
622
+ }
623
+ if (!referrerUrl || this.isCdnUrl(referrerUrl)) {
624
+ return { platform: "unknown", isEmbedded: true };
625
+ }
626
+ const normalizedRef = referrerUrl.toLowerCase();
627
+ for (const [platform, patterns] of Object.entries(EMBEDDING_PLATFORM_PATTERNS)) {
628
+ if (patterns.some((p) => p.test(normalizedRef))) {
629
+ return { platform, isEmbedded: true };
630
+ }
631
+ }
632
+ try {
633
+ if (window.location.ancestorOrigins?.length > 0) {
634
+ const parentOrigin = window.location.ancestorOrigins[0];
635
+ if (parentOrigin && !this.isCdnUrl(parentOrigin)) {
636
+ for (const [platform, patterns] of Object.entries(EMBEDDING_PLATFORM_PATTERNS)) {
637
+ if (patterns.some((p) => p.test(parentOrigin))) {
638
+ return { platform, isEmbedded: true };
639
+ }
640
+ }
641
+ }
642
+ }
643
+ } catch {
644
+ }
645
+ return { platform: "unknown", isEmbedded: true };
646
+ }
647
+ categorizeReferrer(referrerUrl) {
648
+ if (!referrerUrl) return "direct";
649
+ const r = referrerUrl.toLowerCase();
650
+ if (r.includes("checkout.stripe.com")) return "direct";
651
+ if (r.includes("t.co") || r.includes("x.com") || r.includes("twitter.com")) return "twitter";
652
+ if (r.includes("farcaster.xyz") || r.includes("warpcast.com")) return "farcaster";
653
+ if (r.includes("discord.com") || r.includes("discord.gg")) return "discord";
654
+ if (r.includes("t.me") || r.includes("telegram.me") || r.includes("telegram.org")) return "telegram";
655
+ if (r.includes("reddit.com") || r.includes("old.reddit.com")) return "reddit";
656
+ if (r.includes("facebook.com") || r.includes("fb.com") || r.includes("meta.com")) return "facebook";
657
+ if (r.includes("linkedin.com")) return "linkedin";
658
+ if (r.includes("youtube.com") || r.includes("youtu.be")) return "youtube";
659
+ if (r.includes("google.com")) return "google";
660
+ if (r.includes("bing.com")) return "bing";
661
+ if (r.includes("duckduckgo.com")) return "duckduckgo";
662
+ if (r.includes("yahoo.com")) return "yahoo";
663
+ if (r.includes("world.app")) return "worldapp";
664
+ if (r.includes("instagram.com")) return "instagram";
665
+ if (r.includes("tiktok.com")) return "tiktok";
666
+ return this.extractDomain(referrerUrl) || "unknown";
667
+ }
668
+ detectBot() {
669
+ if (typeof navigator === "undefined") return false;
670
+ const ua = navigator.userAgent.toLowerCase();
671
+ return ["bot", "crawler", "spider", "scraper", "headless", "phantom", "selenium", "puppeteer", "playwright", "googlebot", "bingbot", "slurp", "duckduckbot"].some(
672
+ (indicator) => ua.includes(indicator)
673
+ );
674
+ }
675
+ calculateConfidence(urlParams, referrerUrl) {
676
+ let confidence = 0.5;
677
+ if (urlParams.get("utm_source")) confidence += 0.3;
678
+ if (urlParams.get("utm_campaign")) confidence += 0.2;
679
+ if (urlParams.get("utm_medium")) confidence += 0.1;
680
+ if (referrerUrl && !this.isFirstPartyReferrer(this.extractDomain(referrerUrl))) confidence += 0.2;
681
+ if (this.detectBot()) confidence -= 0.5;
682
+ if (urlParams.get("utm_source") === "test" || urlParams.get("utm_campaign") === "test") confidence -= 0.2;
683
+ return Math.max(0, Math.min(1, confidence));
684
+ }
685
+ extractDomain(url) {
686
+ if (!url) return "";
687
+ try {
688
+ return new URL(url).hostname.toLowerCase();
689
+ } catch {
690
+ return "";
691
+ }
692
+ }
693
+ isFirstPartyReferrer(domain) {
694
+ return ["hyve.gg", "dev.hyve.gg", "localhost"].some((d) => domain.includes(d));
695
+ }
696
+ isSearchEngine(domain) {
697
+ return ["google.com", "bing.com", "duckduckgo.com", "yahoo.com", "yandex.com"].some((e) => domain.includes(e));
698
+ }
699
+ isSocialPlatform(domain) {
700
+ return ["twitter.com", "x.com", "facebook.com", "meta.com", "linkedin.com", "reddit.com", "instagram.com", "tiktok.com", "youtube.com", "discord.com", "telegram.org", "farcaster.xyz", "warpcast.com", "t.me", "world.app"].some(
701
+ (p) => domain.includes(p)
702
+ );
703
+ }
704
+ isEmailClient(domain) {
705
+ return ["mail.google.com", "outlook.live.com", "mail.yahoo.com", "mail.aol.com"].some((c) => domain.includes(c));
706
+ }
707
+ getSessionCount() {
708
+ if (typeof window === "undefined") return 0;
709
+ try {
710
+ const stored = localStorage.getItem(this.SESSION_COUNT_KEY);
711
+ return stored ? parseInt(stored, 10) : 0;
712
+ } catch {
713
+ return 0;
714
+ }
715
+ }
716
+ storeAttribution(attribution) {
717
+ if (typeof window === "undefined") return;
718
+ try {
719
+ sessionStorage.setItem(this.STORAGE_KEY, JSON.stringify(attribution));
720
+ localStorage.setItem(this.SESSION_COUNT_KEY, attribution.session_count.toString());
721
+ } catch {
722
+ }
723
+ }
724
+ isValidAttributionData(data) {
725
+ return data !== null && typeof data === "object" && "traffic_type" in data && "attribution_confidence" in data && "platform_id" in data && "is_embedded" in data;
726
+ }
727
+ getEmptyAttribution() {
728
+ return {
729
+ utm_source: null,
730
+ utm_medium: null,
731
+ utm_campaign: null,
732
+ utm_term: null,
733
+ utm_content: null,
734
+ utm_id: null,
735
+ utm_source_platform: null,
736
+ platform_id: "web",
737
+ is_embedded: false,
738
+ referrer_url: null,
739
+ referrer_domain: null,
740
+ referrer_source: null,
741
+ is_first_party_referrer: false,
742
+ traffic_type: "direct",
743
+ first_visit_timestamp: 0,
744
+ session_count: 0,
745
+ is_bot: false,
746
+ attribution_confidence: 0
747
+ };
748
+ }
749
+ };
750
+ function getAttributionData() {
751
+ return AttributionManager.getInstance().getAttribution();
752
+ }
753
+
451
754
  // src/services/ads.ts
452
755
  var AdsService = class {
453
756
  config = {
@@ -1758,20 +2061,39 @@ var HyveClient = class {
1758
2061
  return false;
1759
2062
  }
1760
2063
  }
1761
- const toJsonString = (data) => {
1762
- if (!data) return null;
1763
- return typeof data === "string" ? data : JSON.stringify(data);
2064
+ let parsedEventDetails = {};
2065
+ if (eventDetails) {
2066
+ if (typeof eventDetails === "string") {
2067
+ try {
2068
+ parsedEventDetails = JSON.parse(eventDetails);
2069
+ } catch {
2070
+ }
2071
+ } else {
2072
+ parsedEventDetails = eventDetails;
2073
+ }
2074
+ }
2075
+ const attribution = getAttributionData();
2076
+ const enrichedEventDetails = {
2077
+ // Device info
2078
+ ...getEssentialDeviceInfo(),
2079
+ // Attribution data
2080
+ ...attribution,
2081
+ // Timestamp and session context
2082
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2083
+ session_id: this.sessionId,
2084
+ // Event-specific details can override any of the above
2085
+ ...parsedEventDetails
1764
2086
  };
1765
2087
  const telemetryEvent = {
1766
2088
  game_id: this.gameId,
1767
2089
  session_id: this.sessionId,
1768
- platform_id: platformId || null,
2090
+ platform_id: platformId || attribution.platform_id || null,
1769
2091
  event_location: eventLocation,
1770
2092
  event_category: eventCategory,
1771
2093
  event_action: eventAction,
1772
2094
  event_sub_category: eventSubCategory || null,
1773
2095
  event_sub_action: eventSubAction || null,
1774
- event_details: toJsonString(eventDetails)
2096
+ event_details: enrichedEventDetails
1775
2097
  };
1776
2098
  logger.debug("Sending telemetry event:", telemetryEvent);
1777
2099
  const telemetryUrl = `${this.apiBaseUrl}/api/v1/analytics/send`;