@trillboards/ads-sdk 2.1.1 → 2.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/CHANGELOG.md +10 -0
- package/README.md +9 -1
- package/dist/cli.js +2 -2
- package/dist/index.d.mts +45 -5
- package/dist/index.d.ts +45 -5
- package/dist/index.js +180 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +180 -14
- package/dist/index.mjs.map +1 -1
- package/dist/react-native.d.mts +2 -0
- package/dist/react-native.d.ts +2 -0
- package/dist/react-native.js +1 -0
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +1 -0
- package/dist/react-native.mjs.map +1 -1
- package/dist/react.d.mts +6 -0
- package/dist/react.d.ts +6 -0
- package/dist/react.js +180 -11
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +180 -11
- package/dist/react.mjs.map +1 -1
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +2 -2
- package/dist/server.mjs.map +1 -1
- package/dist/trillboards-lite.global.js +1 -1
- package/dist/trillboards-lite.global.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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
|
/**
|
|
@@ -932,7 +948,7 @@ var WaterfallEngine = class {
|
|
|
932
948
|
};
|
|
933
949
|
|
|
934
950
|
// src/player/ProgrammaticPlayer.ts
|
|
935
|
-
var
|
|
951
|
+
var _ProgrammaticPlayer = class _ProgrammaticPlayer {
|
|
936
952
|
constructor(events, timeoutMs = 12e3) {
|
|
937
953
|
// ── Public state ──────────────────────────────────────────
|
|
938
954
|
this.vastTagUrl = null;
|
|
@@ -1019,8 +1035,11 @@ var ProgrammaticPlayer = class {
|
|
|
1019
1035
|
this.telemetry.recordRequest();
|
|
1020
1036
|
const correlator = Date.now();
|
|
1021
1037
|
const finalUrl = vastUrl.includes("correlator=") ? vastUrl.replace(/correlator=[^&]*/, `correlator=${correlator}`) : `${vastUrl}${vastUrl.includes("?") ? "&" : "?"}correlator=${correlator}`;
|
|
1022
|
-
|
|
1023
|
-
|
|
1038
|
+
const imaReady = await this.ensureImaSdk();
|
|
1039
|
+
if (!imaReady) {
|
|
1040
|
+
const msg = "Google IMA SDK not loaded";
|
|
1041
|
+
this.events.emit("programmatic_error", { error: msg, code: void 0 });
|
|
1042
|
+
onError(msg);
|
|
1024
1043
|
this.telemetry.recordError();
|
|
1025
1044
|
this.waterfallEngine.recordFailure(sourceName);
|
|
1026
1045
|
return;
|
|
@@ -1037,6 +1056,50 @@ var ProgrammaticPlayer = class {
|
|
|
1037
1056
|
onError(err instanceof Error ? err.message : String(err));
|
|
1038
1057
|
}
|
|
1039
1058
|
}
|
|
1059
|
+
/**
|
|
1060
|
+
* Ensure Google IMA SDK is available. If already loaded, resolves
|
|
1061
|
+
* immediately. Otherwise injects the `<script>` tag and waits.
|
|
1062
|
+
* Matches the Lite SDK's `loadImaScript()` pattern.
|
|
1063
|
+
*/
|
|
1064
|
+
async ensureImaSdk() {
|
|
1065
|
+
if (typeof google !== "undefined" && google?.ima) return true;
|
|
1066
|
+
if (typeof document === "undefined") return false;
|
|
1067
|
+
const existing = document.querySelector(
|
|
1068
|
+
`script[src="${_ProgrammaticPlayer.IMA_SDK_URL}"]`
|
|
1069
|
+
);
|
|
1070
|
+
if (existing) {
|
|
1071
|
+
return new Promise((resolve) => {
|
|
1072
|
+
const start = Date.now();
|
|
1073
|
+
const check = () => {
|
|
1074
|
+
if (typeof google !== "undefined" && google?.ima) {
|
|
1075
|
+
resolve(true);
|
|
1076
|
+
} else if (Date.now() - start > 5e3) {
|
|
1077
|
+
resolve(false);
|
|
1078
|
+
} else {
|
|
1079
|
+
setTimeout(check, 100);
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
check();
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
if (!_ProgrammaticPlayer.imaLoadPromise) {
|
|
1086
|
+
_ProgrammaticPlayer.imaLoadPromise = new Promise((resolve) => {
|
|
1087
|
+
const script = document.createElement("script");
|
|
1088
|
+
script.src = _ProgrammaticPlayer.IMA_SDK_URL;
|
|
1089
|
+
script.async = true;
|
|
1090
|
+
script.onload = () => {
|
|
1091
|
+
_ProgrammaticPlayer.imaLoadPromise = null;
|
|
1092
|
+
resolve(true);
|
|
1093
|
+
};
|
|
1094
|
+
script.onerror = () => {
|
|
1095
|
+
_ProgrammaticPlayer.imaLoadPromise = null;
|
|
1096
|
+
resolve(false);
|
|
1097
|
+
};
|
|
1098
|
+
document.head.appendChild(script);
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
return _ProgrammaticPlayer.imaLoadPromise;
|
|
1102
|
+
}
|
|
1040
1103
|
// ── IMA request / playback lifecycle ──────────────────────
|
|
1041
1104
|
async requestAdsViaIMA(vastUrl, onComplete, onError) {
|
|
1042
1105
|
return new Promise((resolve) => {
|
|
@@ -1238,6 +1301,12 @@ var ProgrammaticPlayer = class {
|
|
|
1238
1301
|
this.telemetry.reset();
|
|
1239
1302
|
}
|
|
1240
1303
|
};
|
|
1304
|
+
// ── IMA SDK loader ──────────────────────────────────────────
|
|
1305
|
+
/** IMA script URL */
|
|
1306
|
+
_ProgrammaticPlayer.IMA_SDK_URL = "https://imasdk.googleapis.com/js/sdkloader/ima3.js";
|
|
1307
|
+
/** Singleton load promise so concurrent calls don't insert multiple scripts. */
|
|
1308
|
+
_ProgrammaticPlayer.imaLoadPromise = null;
|
|
1309
|
+
var ProgrammaticPlayer = _ProgrammaticPlayer;
|
|
1241
1310
|
|
|
1242
1311
|
// src/player/Player.ts
|
|
1243
1312
|
var MAX_AD_DURATION_SECONDS = 300;
|
|
@@ -1672,11 +1741,104 @@ var _TrillboardsAds = class _TrillboardsAds {
|
|
|
1672
1741
|
}
|
|
1673
1742
|
startHeartbeatTimer() {
|
|
1674
1743
|
if (this.state.heartbeatTimer) clearInterval(this.state.heartbeatTimer);
|
|
1744
|
+
const tick = () => {
|
|
1745
|
+
this.sendHeartbeatTick().catch(() => {
|
|
1746
|
+
});
|
|
1747
|
+
};
|
|
1748
|
+
tick();
|
|
1675
1749
|
this.state.heartbeatTimer = setInterval(() => {
|
|
1676
|
-
|
|
1750
|
+
tick();
|
|
1677
1751
|
}, this.config.heartbeatInterval);
|
|
1678
1752
|
}
|
|
1753
|
+
async sendHeartbeatTick() {
|
|
1754
|
+
const result = await this.api.sendHeartbeat(
|
|
1755
|
+
this.config.deviceId,
|
|
1756
|
+
this.state.screenId,
|
|
1757
|
+
this.buildHeartbeatPayload()
|
|
1758
|
+
);
|
|
1759
|
+
if (!result.ok || !result.data) return;
|
|
1760
|
+
if (result.data.ad_delivery_profile?.mode) {
|
|
1761
|
+
this.applyAdDeliveryProfile(result.data.ad_delivery_profile);
|
|
1762
|
+
}
|
|
1763
|
+
if (Array.isArray(result.data.commands) && result.data.commands.length > 0) {
|
|
1764
|
+
this.runHeartbeatCommands(result.data.commands);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
buildHeartbeatPayload() {
|
|
1768
|
+
const userAgent = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
1769
|
+
const platform = typeof navigator !== "undefined" ? navigator.platform : null;
|
|
1770
|
+
const connection = typeof navigator !== "undefined" ? navigator.connection : null;
|
|
1771
|
+
const chromiumMatch = userAgent.match(/(?:Chrome|Chromium)\/(\d+)/i);
|
|
1772
|
+
const chromiumMajor = chromiumMatch ? Number(chromiumMatch[1]) : void 0;
|
|
1773
|
+
const imaSupported = typeof window !== "undefined" ? Boolean(window?.google?.ima) : null;
|
|
1774
|
+
const telemetry = {
|
|
1775
|
+
powerState: typeof document !== "undefined" && document.hidden ? "standby" : "on",
|
|
1776
|
+
agentStatus: typeof document !== "undefined" && document.hidden ? "background" : "foreground",
|
|
1777
|
+
os: platform || void 0,
|
|
1778
|
+
agentVersion: SDK_VERSION,
|
|
1779
|
+
ima_integration: "html5_webview",
|
|
1780
|
+
ima_supported: imaSupported,
|
|
1781
|
+
webview_chromium_major: Number.isFinite(chromiumMajor) ? chromiumMajor : void 0
|
|
1782
|
+
};
|
|
1783
|
+
const payload = {
|
|
1784
|
+
telemetry,
|
|
1785
|
+
sdk: {
|
|
1786
|
+
version: SDK_VERSION,
|
|
1787
|
+
ima_supported: imaSupported,
|
|
1788
|
+
ima_integration: "html5_webview",
|
|
1789
|
+
webview_chromium_major: Number.isFinite(chromiumMajor) ? chromiumMajor : void 0
|
|
1790
|
+
},
|
|
1791
|
+
device: {
|
|
1792
|
+
os: platform || void 0,
|
|
1793
|
+
model: userAgent || void 0
|
|
1794
|
+
}
|
|
1795
|
+
};
|
|
1796
|
+
if (connection) {
|
|
1797
|
+
payload.network = {
|
|
1798
|
+
connectionType: connection.type || void 0,
|
|
1799
|
+
effectiveType: connection.effectiveType || void 0,
|
|
1800
|
+
downlinkMbps: typeof connection.downlink === "number" ? connection.downlink : void 0,
|
|
1801
|
+
bandwidthMbps: typeof connection.downlink === "number" ? connection.downlink : void 0,
|
|
1802
|
+
rtt: typeof connection.rtt === "number" ? connection.rtt : void 0,
|
|
1803
|
+
latencyRtt: typeof connection.rtt === "number" ? connection.rtt : void 0,
|
|
1804
|
+
saveData: typeof connection.saveData === "boolean" ? connection.saveData : void 0
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
return payload;
|
|
1808
|
+
}
|
|
1809
|
+
applyAdDeliveryProfile(profile) {
|
|
1810
|
+
if (!profile?.mode) return;
|
|
1811
|
+
const previousMode = this.state.adDeliveryProfile?.mode ?? null;
|
|
1812
|
+
this.state.adDeliveryProfile = profile;
|
|
1813
|
+
if (profile.mode === "vast_fallback" && this.state.programmaticPlaying) {
|
|
1814
|
+
this.programmaticPlayer.stop({ silent: true });
|
|
1815
|
+
this.state.programmaticPlaying = false;
|
|
1816
|
+
}
|
|
1817
|
+
if (previousMode !== profile.mode) {
|
|
1818
|
+
this.emitStateChanged();
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
runHeartbeatCommands(commands) {
|
|
1822
|
+
for (const command of commands) {
|
|
1823
|
+
const name = String(command?.command || "").toLowerCase();
|
|
1824
|
+
if (name === "refresh_ads") {
|
|
1825
|
+
this.refresh().catch(() => {
|
|
1826
|
+
});
|
|
1827
|
+
} else if (name === "restart") {
|
|
1828
|
+
if (typeof window !== "undefined" && typeof window.location?.reload === "function") {
|
|
1829
|
+
window.location.reload();
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
this.refresh().catch(() => {
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1679
1837
|
playNextAd() {
|
|
1838
|
+
if (this.state.adDeliveryProfile?.mode === "vast_fallback" && this.state.ads.length > 0) {
|
|
1839
|
+
this.playDirect();
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1680
1842
|
const mode = this.state.waterfallMode;
|
|
1681
1843
|
if (mode === "programmatic_only" || mode === "programmatic_then_direct") {
|
|
1682
1844
|
this.playProgrammatic();
|
|
@@ -1691,13 +1853,20 @@ var _TrillboardsAds = class _TrillboardsAds {
|
|
|
1691
1853
|
this.programmaticPlayer.play(
|
|
1692
1854
|
() => {
|
|
1693
1855
|
this.state.programmaticPlaying = false;
|
|
1856
|
+
this.resetProgrammaticBackoff();
|
|
1694
1857
|
this.emitStateChanged();
|
|
1695
1858
|
this.scheduleProgrammaticRetry();
|
|
1696
1859
|
},
|
|
1697
1860
|
(error) => {
|
|
1698
1861
|
this.state.programmaticPlaying = false;
|
|
1699
1862
|
this.state.programmaticLastError = error;
|
|
1700
|
-
|
|
1863
|
+
const normalizedError = String(error || "").toLowerCase();
|
|
1864
|
+
const isNoFillLike = normalizedError.includes("no fill") || normalizedError.includes("no ads vast response") || normalizedError.includes("waterfall_exhausted") || normalizedError === "throttled" || normalizedError === "busy";
|
|
1865
|
+
if (isNoFillLike) {
|
|
1866
|
+
this.state.programmaticRetryCount = 0;
|
|
1867
|
+
} else {
|
|
1868
|
+
this.state.programmaticRetryCount++;
|
|
1869
|
+
}
|
|
1701
1870
|
this.emitStateChanged();
|
|
1702
1871
|
if (this.state.waterfallMode === "programmatic_then_direct" && this.state.ads.length > 0) {
|
|
1703
1872
|
this.playDirect();
|