@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/react.mjs CHANGED
@@ -387,6 +387,309 @@ function generateUUID() {
387
387
  return uuidv4();
388
388
  }
389
389
 
390
+ // src/utils/device-info.ts
391
+ function detectBrowser(ua) {
392
+ if (/Edg\/(\d+)/.test(ua)) return { name: "Edge", major: RegExp.$1 };
393
+ if (/OPR\/(\d+)/.test(ua)) return { name: "Opera", major: RegExp.$1 };
394
+ if (/Chrome\/(\d+)/.test(ua)) return { name: "Chrome", major: RegExp.$1 };
395
+ if (/Firefox\/(\d+)/.test(ua)) return { name: "Firefox", major: RegExp.$1 };
396
+ if (/Version\/(\d+).*Safari/.test(ua)) return { name: "Safari", major: RegExp.$1 };
397
+ return { name: "unknown", major: "" };
398
+ }
399
+ function detectOS(ua) {
400
+ if (/Windows NT ([\d.]+)/.test(ua)) return { name: "Windows", version: RegExp.$1 };
401
+ if (/Mac OS X ([\d_]+)/.test(ua)) return { name: "macOS", version: RegExp.$1.replace(/_/g, ".") };
402
+ if (/Android ([\d.]+)/.test(ua)) return { name: "Android", version: RegExp.$1 };
403
+ if (/iPhone OS ([\d_]+)/.test(ua)) return { name: "iOS", version: RegExp.$1.replace(/_/g, ".") };
404
+ if (/iPad.*OS ([\d_]+)/.test(ua)) return { name: "iOS", version: RegExp.$1.replace(/_/g, ".") };
405
+ if (/CrOS/.test(ua)) return { name: "Chrome OS", version: "" };
406
+ if (/Linux/.test(ua)) return { name: "Linux", version: "" };
407
+ return { name: "unknown", version: "" };
408
+ }
409
+ function detectDeviceType(ua) {
410
+ if (/Mobi|Android(?!.*Tablet)|iPhone|iPod/i.test(ua)) return "mobile";
411
+ if (/Tablet|iPad|Android.*Tablet/i.test(ua)) return "tablet";
412
+ return "desktop";
413
+ }
414
+ var _cache = null;
415
+ function getEssentialDeviceInfo() {
416
+ if (_cache) return _cache;
417
+ if (typeof window === "undefined" || typeof navigator === "undefined") {
418
+ return {
419
+ browser: "unknown",
420
+ os: "unknown",
421
+ device_type: null,
422
+ viewport: "0x0",
423
+ screen: "0x0",
424
+ pixel_ratio: null,
425
+ language: null,
426
+ timezone: null,
427
+ country_code: null
428
+ };
429
+ }
430
+ const ua = navigator.userAgent;
431
+ const browser = detectBrowser(ua);
432
+ const os = detectOS(ua);
433
+ const deviceType = detectDeviceType(ua);
434
+ let timezone = null;
435
+ try {
436
+ timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
437
+ } catch {
438
+ }
439
+ const language = navigator.language || null;
440
+ let country_code = null;
441
+ if (language) {
442
+ const parts = language.split("-");
443
+ if (parts.length > 1) {
444
+ country_code = parts[parts.length - 1].toUpperCase();
445
+ }
446
+ }
447
+ _cache = {
448
+ browser: browser.major ? `${browser.name} ${browser.major}` : browser.name,
449
+ os: os.version ? `${os.name} ${os.version}` : os.name,
450
+ device_type: deviceType,
451
+ viewport: `${window.innerWidth ?? 0}x${window.innerHeight ?? 0}`,
452
+ screen: `${window.screen?.width ?? 0}x${window.screen?.height ?? 0}`,
453
+ pixel_ratio: window.devicePixelRatio || null,
454
+ language,
455
+ timezone,
456
+ country_code
457
+ };
458
+ return _cache;
459
+ }
460
+
461
+ // src/utils/attribution.ts
462
+ var EMBEDDING_PLATFORM_PATTERNS = {
463
+ twitter: [/t\.co/i, /twitter\.com/i, /x\.com/i, /mobile\.twitter\.com/i, /mobile\.x\.com/i],
464
+ discord: [/discord\.com/i, /discordapp\.com/i, /discord\.gg/i],
465
+ telegram: [/telegram\.org/i, /telegram\.me/i, /t\.me/i, /web\.telegram\.org/i],
466
+ facebook: [/facebook\.com/i, /fb\.com/i, /m\.facebook\.com/i, /mobile\.facebook\.com/i, /meta\.com/i],
467
+ reddit: [/reddit\.com/i, /redd\.it/i, /old\.reddit\.com/i, /www\.reddit\.com/i],
468
+ linkedin: [/linkedin\.com/i, /lnkd\.in/i],
469
+ instagram: [/instagram\.com/i, /instagr\.am/i],
470
+ youtube: [/youtube\.com/i, /youtu\.be/i, /m\.youtube\.com/i],
471
+ twitch: [/twitch\.tv/i, /m\.twitch\.tv/i],
472
+ farcaster: [/warpcast\.com/i, /farcaster\.xyz/i, /miniapps\.farcaster\.xyz/i],
473
+ worldapp: [/world\.app/i, /worldcoin\.org/i],
474
+ tiktok: [/tiktok\.com/i, /vm\.tiktok\.com/i]
475
+ };
476
+ var AttributionManager = class _AttributionManager {
477
+ static instance;
478
+ cachedData = null;
479
+ STORAGE_KEY = "hyve_attribution";
480
+ SESSION_COUNT_KEY = "hyve_session_count";
481
+ static getInstance() {
482
+ if (!_AttributionManager.instance) {
483
+ _AttributionManager.instance = new _AttributionManager();
484
+ }
485
+ return _AttributionManager.instance;
486
+ }
487
+ captureAttribution() {
488
+ if (typeof window === "undefined") return this.getEmptyAttribution();
489
+ if (this.cachedData) return this.cachedData;
490
+ const urlParams = new URLSearchParams(window.location.search);
491
+ const referrerUrl = document.referrer;
492
+ const referrerDomain = this.extractDomain(referrerUrl);
493
+ const platformDetection = this.detectEmbeddingPlatform(urlParams, referrerUrl);
494
+ const attribution = {
495
+ utm_source: urlParams.get("utm_source"),
496
+ utm_medium: urlParams.get("utm_medium"),
497
+ utm_campaign: urlParams.get("utm_campaign"),
498
+ utm_term: urlParams.get("utm_term"),
499
+ utm_content: urlParams.get("utm_content"),
500
+ utm_id: urlParams.get("utm_id"),
501
+ utm_source_platform: urlParams.get("utm_source_platform"),
502
+ platform_id: platformDetection.platform,
503
+ is_embedded: platformDetection.isEmbedded,
504
+ referrer_url: referrerUrl || null,
505
+ referrer_domain: referrerDomain,
506
+ referrer_source: this.categorizeReferrer(referrerUrl),
507
+ is_first_party_referrer: this.isFirstPartyReferrer(referrerDomain),
508
+ traffic_type: this.classifyTrafficType(urlParams, referrerUrl),
509
+ first_visit_timestamp: Date.now(),
510
+ session_count: this.getSessionCount() + 1,
511
+ is_bot: this.detectBot(),
512
+ attribution_confidence: this.calculateConfidence(urlParams, referrerUrl)
513
+ };
514
+ this.cachedData = attribution;
515
+ this.storeAttribution(attribution);
516
+ return attribution;
517
+ }
518
+ getAttribution() {
519
+ if (this.cachedData) return this.cachedData;
520
+ if (typeof window === "undefined") return this.getEmptyAttribution();
521
+ const stored = sessionStorage.getItem(this.STORAGE_KEY);
522
+ if (stored) {
523
+ try {
524
+ const parsed = JSON.parse(stored);
525
+ if (this.isValidAttributionData(parsed)) {
526
+ this.cachedData = parsed;
527
+ return parsed;
528
+ }
529
+ } catch {
530
+ }
531
+ }
532
+ return this.captureAttribution();
533
+ }
534
+ classifyTrafficType(urlParams, referrerUrl) {
535
+ if (urlParams.get("utm_source")) return "campaign";
536
+ if (!referrerUrl) return "direct";
537
+ const domain = this.extractDomain(referrerUrl);
538
+ if (this.isSearchEngine(domain)) return "search";
539
+ if (this.isSocialPlatform(domain)) return "social";
540
+ if (this.isEmailClient(domain)) return "email";
541
+ if (this.isFirstPartyReferrer(domain)) return "direct";
542
+ return "referral";
543
+ }
544
+ isCdnUrl(url) {
545
+ if (!url) return false;
546
+ return [/cloudfront\.net/i, /cdn\.[\w-]+\.com/i, /\.cdn\./i, /fastly\.net/i, /akamai/i, /\.cloudflare\./i].some(
547
+ (p) => p.test(url)
548
+ );
549
+ }
550
+ detectEmbeddingPlatform(urlParams, referrerUrl) {
551
+ let isEmbedded = false;
552
+ try {
553
+ isEmbedded = window.self !== window.top;
554
+ } catch {
555
+ isEmbedded = true;
556
+ }
557
+ if (!isEmbedded) return { platform: "web", isEmbedded: false };
558
+ const platformParam = urlParams.get("platform")?.toLowerCase().trim();
559
+ if (platformParam && /^[a-z0-9_-]+$/.test(platformParam)) {
560
+ return { platform: platformParam, isEmbedded: true };
561
+ }
562
+ if (!referrerUrl || this.isCdnUrl(referrerUrl)) {
563
+ return { platform: "unknown", isEmbedded: true };
564
+ }
565
+ const normalizedRef = referrerUrl.toLowerCase();
566
+ for (const [platform, patterns] of Object.entries(EMBEDDING_PLATFORM_PATTERNS)) {
567
+ if (patterns.some((p) => p.test(normalizedRef))) {
568
+ return { platform, isEmbedded: true };
569
+ }
570
+ }
571
+ try {
572
+ if (window.location.ancestorOrigins?.length > 0) {
573
+ const parentOrigin = window.location.ancestorOrigins[0];
574
+ if (parentOrigin && !this.isCdnUrl(parentOrigin)) {
575
+ for (const [platform, patterns] of Object.entries(EMBEDDING_PLATFORM_PATTERNS)) {
576
+ if (patterns.some((p) => p.test(parentOrigin))) {
577
+ return { platform, isEmbedded: true };
578
+ }
579
+ }
580
+ }
581
+ }
582
+ } catch {
583
+ }
584
+ return { platform: "unknown", isEmbedded: true };
585
+ }
586
+ categorizeReferrer(referrerUrl) {
587
+ if (!referrerUrl) return "direct";
588
+ const r = referrerUrl.toLowerCase();
589
+ if (r.includes("checkout.stripe.com")) return "direct";
590
+ if (r.includes("t.co") || r.includes("x.com") || r.includes("twitter.com")) return "twitter";
591
+ if (r.includes("farcaster.xyz") || r.includes("warpcast.com")) return "farcaster";
592
+ if (r.includes("discord.com") || r.includes("discord.gg")) return "discord";
593
+ if (r.includes("t.me") || r.includes("telegram.me") || r.includes("telegram.org")) return "telegram";
594
+ if (r.includes("reddit.com") || r.includes("old.reddit.com")) return "reddit";
595
+ if (r.includes("facebook.com") || r.includes("fb.com") || r.includes("meta.com")) return "facebook";
596
+ if (r.includes("linkedin.com")) return "linkedin";
597
+ if (r.includes("youtube.com") || r.includes("youtu.be")) return "youtube";
598
+ if (r.includes("google.com")) return "google";
599
+ if (r.includes("bing.com")) return "bing";
600
+ if (r.includes("duckduckgo.com")) return "duckduckgo";
601
+ if (r.includes("yahoo.com")) return "yahoo";
602
+ if (r.includes("world.app")) return "worldapp";
603
+ if (r.includes("instagram.com")) return "instagram";
604
+ if (r.includes("tiktok.com")) return "tiktok";
605
+ return this.extractDomain(referrerUrl) || "unknown";
606
+ }
607
+ detectBot() {
608
+ if (typeof navigator === "undefined") return false;
609
+ const ua = navigator.userAgent.toLowerCase();
610
+ return ["bot", "crawler", "spider", "scraper", "headless", "phantom", "selenium", "puppeteer", "playwright", "googlebot", "bingbot", "slurp", "duckduckbot"].some(
611
+ (indicator) => ua.includes(indicator)
612
+ );
613
+ }
614
+ calculateConfidence(urlParams, referrerUrl) {
615
+ let confidence = 0.5;
616
+ if (urlParams.get("utm_source")) confidence += 0.3;
617
+ if (urlParams.get("utm_campaign")) confidence += 0.2;
618
+ if (urlParams.get("utm_medium")) confidence += 0.1;
619
+ if (referrerUrl && !this.isFirstPartyReferrer(this.extractDomain(referrerUrl))) confidence += 0.2;
620
+ if (this.detectBot()) confidence -= 0.5;
621
+ if (urlParams.get("utm_source") === "test" || urlParams.get("utm_campaign") === "test") confidence -= 0.2;
622
+ return Math.max(0, Math.min(1, confidence));
623
+ }
624
+ extractDomain(url) {
625
+ if (!url) return "";
626
+ try {
627
+ return new URL(url).hostname.toLowerCase();
628
+ } catch {
629
+ return "";
630
+ }
631
+ }
632
+ isFirstPartyReferrer(domain) {
633
+ return ["hyve.gg", "dev.hyve.gg", "localhost"].some((d) => domain.includes(d));
634
+ }
635
+ isSearchEngine(domain) {
636
+ return ["google.com", "bing.com", "duckduckgo.com", "yahoo.com", "yandex.com"].some((e) => domain.includes(e));
637
+ }
638
+ isSocialPlatform(domain) {
639
+ 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(
640
+ (p) => domain.includes(p)
641
+ );
642
+ }
643
+ isEmailClient(domain) {
644
+ return ["mail.google.com", "outlook.live.com", "mail.yahoo.com", "mail.aol.com"].some((c) => domain.includes(c));
645
+ }
646
+ getSessionCount() {
647
+ if (typeof window === "undefined") return 0;
648
+ try {
649
+ const stored = localStorage.getItem(this.SESSION_COUNT_KEY);
650
+ return stored ? parseInt(stored, 10) : 0;
651
+ } catch {
652
+ return 0;
653
+ }
654
+ }
655
+ storeAttribution(attribution) {
656
+ if (typeof window === "undefined") return;
657
+ try {
658
+ sessionStorage.setItem(this.STORAGE_KEY, JSON.stringify(attribution));
659
+ localStorage.setItem(this.SESSION_COUNT_KEY, attribution.session_count.toString());
660
+ } catch {
661
+ }
662
+ }
663
+ isValidAttributionData(data) {
664
+ return data !== null && typeof data === "object" && "traffic_type" in data && "attribution_confidence" in data && "platform_id" in data && "is_embedded" in data;
665
+ }
666
+ getEmptyAttribution() {
667
+ return {
668
+ utm_source: null,
669
+ utm_medium: null,
670
+ utm_campaign: null,
671
+ utm_term: null,
672
+ utm_content: null,
673
+ utm_id: null,
674
+ utm_source_platform: null,
675
+ platform_id: "web",
676
+ is_embedded: false,
677
+ referrer_url: null,
678
+ referrer_domain: null,
679
+ referrer_source: null,
680
+ is_first_party_referrer: false,
681
+ traffic_type: "direct",
682
+ first_visit_timestamp: 0,
683
+ session_count: 0,
684
+ is_bot: false,
685
+ attribution_confidence: 0
686
+ };
687
+ }
688
+ };
689
+ function getAttributionData() {
690
+ return AttributionManager.getInstance().getAttribution();
691
+ }
692
+
390
693
  // src/services/ads.ts
391
694
  var AdsService = class {
392
695
  config = {
@@ -1691,20 +1994,39 @@ var HyveClient = class {
1691
1994
  return false;
1692
1995
  }
1693
1996
  }
1694
- const toJsonString = (data) => {
1695
- if (!data) return null;
1696
- return typeof data === "string" ? data : JSON.stringify(data);
1997
+ let parsedEventDetails = {};
1998
+ if (eventDetails) {
1999
+ if (typeof eventDetails === "string") {
2000
+ try {
2001
+ parsedEventDetails = JSON.parse(eventDetails);
2002
+ } catch {
2003
+ }
2004
+ } else {
2005
+ parsedEventDetails = eventDetails;
2006
+ }
2007
+ }
2008
+ const attribution = getAttributionData();
2009
+ const enrichedEventDetails = {
2010
+ // Device info
2011
+ ...getEssentialDeviceInfo(),
2012
+ // Attribution data
2013
+ ...attribution,
2014
+ // Timestamp and session context
2015
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2016
+ session_id: this.sessionId,
2017
+ // Event-specific details can override any of the above
2018
+ ...parsedEventDetails
1697
2019
  };
1698
2020
  const telemetryEvent = {
1699
2021
  game_id: this.gameId,
1700
2022
  session_id: this.sessionId,
1701
- platform_id: platformId || null,
2023
+ platform_id: platformId || attribution.platform_id || null,
1702
2024
  event_location: eventLocation,
1703
2025
  event_category: eventCategory,
1704
2026
  event_action: eventAction,
1705
2027
  event_sub_category: eventSubCategory || null,
1706
2028
  event_sub_action: eventSubAction || null,
1707
- event_details: toJsonString(eventDetails)
2029
+ event_details: enrichedEventDetails
1708
2030
  };
1709
2031
  logger.debug("Sending telemetry event:", telemetryEvent);
1710
2032
  const telemetryUrl = `${this.apiBaseUrl}/api/v1/analytics/send`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyve-sdk/js",
3
- "version": "2.11.0",
3
+ "version": "2.11.1",
4
4
  "description": "Hyve SDK - TypeScript wrapper for Hyve game server integration",
5
5
  "private": false,
6
6
  "publishConfig": {