@trillboards/ads-sdk 2.1.1 → 2.2.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
@@ -2,6 +2,9 @@ import { createContext, useState, useEffect, useMemo, useContext, useCallback, u
2
2
  import { jsx } from 'react/jsx-runtime';
3
3
 
4
4
  // src/react/TrillboardsProvider.tsx
5
+
6
+ // src/core/config.ts
7
+ var SDK_VERSION = "2.2.0";
5
8
  var DEFAULT_CONFIG = {
6
9
  API_BASE: "https://api.trillboards.com/v1/partner",
7
10
  CDN_BASE: "https://cdn.trillboards.com",
@@ -56,6 +59,7 @@ var DEFAULT_PUBLIC_STATE = {
56
59
  programmaticPlaying: false,
57
60
  prefetchedReady: false,
58
61
  waterfallMode: "programmatic_only",
62
+ adDeliveryProfileMode: null,
59
63
  screenId: null,
60
64
  deviceId: null
61
65
  };
@@ -83,6 +87,7 @@ function createInitialState() {
83
87
  programmaticRetryActive: false,
84
88
  programmaticRetryCount: 0,
85
89
  programmaticLastError: null,
90
+ adDeliveryProfile: null,
86
91
  initialized: false,
87
92
  screenOrientation: null,
88
93
  screenDimensions: null,
@@ -100,6 +105,7 @@ function getPublicState(internal) {
100
105
  programmaticPlaying: internal.programmaticPlaying,
101
106
  prefetchedReady: internal.prefetchedReady ?? false,
102
107
  waterfallMode: internal.waterfallMode,
108
+ adDeliveryProfileMode: internal.adDeliveryProfile?.mode ?? null,
103
109
  screenId: internal.screenId,
104
110
  deviceId: internal.deviceId
105
111
  };
@@ -360,10 +366,9 @@ var ApiClient = class {
360
366
  }
361
367
  }
362
368
  /**
363
- * Ping the heartbeat endpoint to signal this device is alive.
364
- * Returns `true` on 2xx, `false` on any error.
369
+ * Ping heartbeat endpoint and return delivery profile + queued commands.
365
370
  */
366
- async sendHeartbeat(deviceId, screenId) {
371
+ async sendHeartbeat(deviceId, screenId, payload = {}) {
367
372
  logger.debug("Sending heartbeat", { deviceId });
368
373
  try {
369
374
  const response = await fetch(`${this.apiBase}/device/${deviceId}/heartbeat`, {
@@ -373,13 +378,24 @@ var ApiClient = class {
373
378
  device_id: deviceId,
374
379
  screen_id: screenId,
375
380
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
376
- status: "active"
381
+ status: "active",
382
+ ...payload
377
383
  }),
378
384
  signal: AbortSignal.timeout(5e3)
379
385
  });
380
- return response.ok;
386
+ if (!response.ok) {
387
+ return { ok: false, data: null };
388
+ }
389
+ let data = null;
390
+ try {
391
+ const json = await response.json();
392
+ data = json?.data ?? null;
393
+ } catch {
394
+ data = null;
395
+ }
396
+ return { ok: true, data };
381
397
  } catch {
382
- return false;
398
+ return { ok: false, data: null };
383
399
  }
384
400
  }
385
401
  /**
@@ -1672,11 +1688,104 @@ var _TrillboardsAds = class _TrillboardsAds {
1672
1688
  }
1673
1689
  startHeartbeatTimer() {
1674
1690
  if (this.state.heartbeatTimer) clearInterval(this.state.heartbeatTimer);
1691
+ const tick = () => {
1692
+ this.sendHeartbeatTick().catch(() => {
1693
+ });
1694
+ };
1695
+ tick();
1675
1696
  this.state.heartbeatTimer = setInterval(() => {
1676
- this.api.sendHeartbeat(this.config.deviceId, this.state.screenId);
1697
+ tick();
1677
1698
  }, this.config.heartbeatInterval);
1678
1699
  }
1700
+ async sendHeartbeatTick() {
1701
+ const result = await this.api.sendHeartbeat(
1702
+ this.config.deviceId,
1703
+ this.state.screenId,
1704
+ this.buildHeartbeatPayload()
1705
+ );
1706
+ if (!result.ok || !result.data) return;
1707
+ if (result.data.ad_delivery_profile?.mode) {
1708
+ this.applyAdDeliveryProfile(result.data.ad_delivery_profile);
1709
+ }
1710
+ if (Array.isArray(result.data.commands) && result.data.commands.length > 0) {
1711
+ this.runHeartbeatCommands(result.data.commands);
1712
+ }
1713
+ }
1714
+ buildHeartbeatPayload() {
1715
+ const userAgent = typeof navigator !== "undefined" ? navigator.userAgent : "";
1716
+ const platform = typeof navigator !== "undefined" ? navigator.platform : null;
1717
+ const connection = typeof navigator !== "undefined" ? navigator.connection : null;
1718
+ const chromiumMatch = userAgent.match(/(?:Chrome|Chromium)\/(\d+)/i);
1719
+ const chromiumMajor = chromiumMatch ? Number(chromiumMatch[1]) : void 0;
1720
+ const imaSupported = typeof window !== "undefined" ? Boolean(window?.google?.ima) : null;
1721
+ const telemetry = {
1722
+ powerState: typeof document !== "undefined" && document.hidden ? "standby" : "on",
1723
+ agentStatus: typeof document !== "undefined" && document.hidden ? "background" : "foreground",
1724
+ os: platform || void 0,
1725
+ agentVersion: SDK_VERSION,
1726
+ ima_integration: "html5_webview",
1727
+ ima_supported: imaSupported,
1728
+ webview_chromium_major: Number.isFinite(chromiumMajor) ? chromiumMajor : void 0
1729
+ };
1730
+ const payload = {
1731
+ telemetry,
1732
+ sdk: {
1733
+ version: SDK_VERSION,
1734
+ ima_supported: imaSupported,
1735
+ ima_integration: "html5_webview",
1736
+ webview_chromium_major: Number.isFinite(chromiumMajor) ? chromiumMajor : void 0
1737
+ },
1738
+ device: {
1739
+ os: platform || void 0,
1740
+ model: userAgent || void 0
1741
+ }
1742
+ };
1743
+ if (connection) {
1744
+ payload.network = {
1745
+ connectionType: connection.type || void 0,
1746
+ effectiveType: connection.effectiveType || void 0,
1747
+ downlinkMbps: typeof connection.downlink === "number" ? connection.downlink : void 0,
1748
+ bandwidthMbps: typeof connection.downlink === "number" ? connection.downlink : void 0,
1749
+ rtt: typeof connection.rtt === "number" ? connection.rtt : void 0,
1750
+ latencyRtt: typeof connection.rtt === "number" ? connection.rtt : void 0,
1751
+ saveData: typeof connection.saveData === "boolean" ? connection.saveData : void 0
1752
+ };
1753
+ }
1754
+ return payload;
1755
+ }
1756
+ applyAdDeliveryProfile(profile) {
1757
+ if (!profile?.mode) return;
1758
+ const previousMode = this.state.adDeliveryProfile?.mode ?? null;
1759
+ this.state.adDeliveryProfile = profile;
1760
+ if (profile.mode === "vast_fallback" && this.state.programmaticPlaying) {
1761
+ this.programmaticPlayer.stop({ silent: true });
1762
+ this.state.programmaticPlaying = false;
1763
+ }
1764
+ if (previousMode !== profile.mode) {
1765
+ this.emitStateChanged();
1766
+ }
1767
+ }
1768
+ runHeartbeatCommands(commands) {
1769
+ for (const command of commands) {
1770
+ const name = String(command?.command || "").toLowerCase();
1771
+ if (name === "refresh_ads") {
1772
+ this.refresh().catch(() => {
1773
+ });
1774
+ } else if (name === "restart") {
1775
+ if (typeof window !== "undefined" && typeof window.location?.reload === "function") {
1776
+ window.location.reload();
1777
+ return;
1778
+ }
1779
+ this.refresh().catch(() => {
1780
+ });
1781
+ }
1782
+ }
1783
+ }
1679
1784
  playNextAd() {
1785
+ if (this.state.adDeliveryProfile?.mode === "vast_fallback" && this.state.ads.length > 0) {
1786
+ this.playDirect();
1787
+ return;
1788
+ }
1680
1789
  const mode = this.state.waterfallMode;
1681
1790
  if (mode === "programmatic_only" || mode === "programmatic_then_direct") {
1682
1791
  this.playProgrammatic();
@@ -1691,13 +1800,20 @@ var _TrillboardsAds = class _TrillboardsAds {
1691
1800
  this.programmaticPlayer.play(
1692
1801
  () => {
1693
1802
  this.state.programmaticPlaying = false;
1803
+ this.resetProgrammaticBackoff();
1694
1804
  this.emitStateChanged();
1695
1805
  this.scheduleProgrammaticRetry();
1696
1806
  },
1697
1807
  (error) => {
1698
1808
  this.state.programmaticPlaying = false;
1699
1809
  this.state.programmaticLastError = error;
1700
- this.state.programmaticRetryCount++;
1810
+ const normalizedError = String(error || "").toLowerCase();
1811
+ const isNoFillLike = normalizedError.includes("no fill") || normalizedError.includes("no ads vast response") || normalizedError.includes("waterfall_exhausted") || normalizedError === "throttled" || normalizedError === "busy";
1812
+ if (isNoFillLike) {
1813
+ this.state.programmaticRetryCount = 0;
1814
+ } else {
1815
+ this.state.programmaticRetryCount++;
1816
+ }
1701
1817
  this.emitStateChanged();
1702
1818
  if (this.state.waterfallMode === "programmatic_then_direct" && this.state.ads.length > 0) {
1703
1819
  this.playDirect();