@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/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/core/config.ts
|
|
2
|
-
var SDK_VERSION = "2.
|
|
2
|
+
var SDK_VERSION = "2.2.0";
|
|
3
3
|
var DEFAULT_CONFIG = {
|
|
4
4
|
API_BASE: "https://api.trillboards.com/v1/partner",
|
|
5
5
|
CDN_BASE: "https://cdn.trillboards.com",
|
|
@@ -58,6 +58,7 @@ var DEFAULT_PUBLIC_STATE = {
|
|
|
58
58
|
programmaticPlaying: false,
|
|
59
59
|
prefetchedReady: false,
|
|
60
60
|
waterfallMode: "programmatic_only",
|
|
61
|
+
adDeliveryProfileMode: null,
|
|
61
62
|
screenId: null,
|
|
62
63
|
deviceId: null
|
|
63
64
|
};
|
|
@@ -85,6 +86,7 @@ function createInitialState() {
|
|
|
85
86
|
programmaticRetryActive: false,
|
|
86
87
|
programmaticRetryCount: 0,
|
|
87
88
|
programmaticLastError: null,
|
|
89
|
+
adDeliveryProfile: null,
|
|
88
90
|
initialized: false,
|
|
89
91
|
screenOrientation: null,
|
|
90
92
|
screenDimensions: null,
|
|
@@ -102,6 +104,7 @@ function getPublicState(internal) {
|
|
|
102
104
|
programmaticPlaying: internal.programmaticPlaying,
|
|
103
105
|
prefetchedReady: internal.prefetchedReady ?? false,
|
|
104
106
|
waterfallMode: internal.waterfallMode,
|
|
107
|
+
adDeliveryProfileMode: internal.adDeliveryProfile?.mode ?? null,
|
|
105
108
|
screenId: internal.screenId,
|
|
106
109
|
deviceId: internal.deviceId
|
|
107
110
|
};
|
|
@@ -362,10 +365,9 @@ var ApiClient = class {
|
|
|
362
365
|
}
|
|
363
366
|
}
|
|
364
367
|
/**
|
|
365
|
-
* Ping
|
|
366
|
-
* Returns `true` on 2xx, `false` on any error.
|
|
368
|
+
* Ping heartbeat endpoint and return delivery profile + queued commands.
|
|
367
369
|
*/
|
|
368
|
-
async sendHeartbeat(deviceId, screenId) {
|
|
370
|
+
async sendHeartbeat(deviceId, screenId, payload = {}) {
|
|
369
371
|
logger.debug("Sending heartbeat", { deviceId });
|
|
370
372
|
try {
|
|
371
373
|
const response = await fetch(`${this.apiBase}/device/${deviceId}/heartbeat`, {
|
|
@@ -375,13 +377,24 @@ var ApiClient = class {
|
|
|
375
377
|
device_id: deviceId,
|
|
376
378
|
screen_id: screenId,
|
|
377
379
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
378
|
-
status: "active"
|
|
380
|
+
status: "active",
|
|
381
|
+
...payload
|
|
379
382
|
}),
|
|
380
383
|
signal: AbortSignal.timeout(5e3)
|
|
381
384
|
});
|
|
382
|
-
|
|
385
|
+
if (!response.ok) {
|
|
386
|
+
return { ok: false, data: null };
|
|
387
|
+
}
|
|
388
|
+
let data = null;
|
|
389
|
+
try {
|
|
390
|
+
const json = await response.json();
|
|
391
|
+
data = json?.data ?? null;
|
|
392
|
+
} catch {
|
|
393
|
+
data = null;
|
|
394
|
+
}
|
|
395
|
+
return { ok: true, data };
|
|
383
396
|
} catch {
|
|
384
|
-
return false;
|
|
397
|
+
return { ok: false, data: null };
|
|
385
398
|
}
|
|
386
399
|
}
|
|
387
400
|
/**
|
|
@@ -934,7 +947,7 @@ var WaterfallEngine = class {
|
|
|
934
947
|
};
|
|
935
948
|
|
|
936
949
|
// src/player/ProgrammaticPlayer.ts
|
|
937
|
-
var
|
|
950
|
+
var _ProgrammaticPlayer = class _ProgrammaticPlayer {
|
|
938
951
|
constructor(events, timeoutMs = 12e3) {
|
|
939
952
|
// ── Public state ──────────────────────────────────────────
|
|
940
953
|
this.vastTagUrl = null;
|
|
@@ -1021,8 +1034,11 @@ var ProgrammaticPlayer = class {
|
|
|
1021
1034
|
this.telemetry.recordRequest();
|
|
1022
1035
|
const correlator = Date.now();
|
|
1023
1036
|
const finalUrl = vastUrl.includes("correlator=") ? vastUrl.replace(/correlator=[^&]*/, `correlator=${correlator}`) : `${vastUrl}${vastUrl.includes("?") ? "&" : "?"}correlator=${correlator}`;
|
|
1024
|
-
|
|
1025
|
-
|
|
1037
|
+
const imaReady = await this.ensureImaSdk();
|
|
1038
|
+
if (!imaReady) {
|
|
1039
|
+
const msg = "Google IMA SDK not loaded";
|
|
1040
|
+
this.events.emit("programmatic_error", { error: msg, code: void 0 });
|
|
1041
|
+
onError(msg);
|
|
1026
1042
|
this.telemetry.recordError();
|
|
1027
1043
|
this.waterfallEngine.recordFailure(sourceName);
|
|
1028
1044
|
return;
|
|
@@ -1039,6 +1055,50 @@ var ProgrammaticPlayer = class {
|
|
|
1039
1055
|
onError(err instanceof Error ? err.message : String(err));
|
|
1040
1056
|
}
|
|
1041
1057
|
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Ensure Google IMA SDK is available. If already loaded, resolves
|
|
1060
|
+
* immediately. Otherwise injects the `<script>` tag and waits.
|
|
1061
|
+
* Matches the Lite SDK's `loadImaScript()` pattern.
|
|
1062
|
+
*/
|
|
1063
|
+
async ensureImaSdk() {
|
|
1064
|
+
if (typeof google !== "undefined" && google?.ima) return true;
|
|
1065
|
+
if (typeof document === "undefined") return false;
|
|
1066
|
+
const existing = document.querySelector(
|
|
1067
|
+
`script[src="${_ProgrammaticPlayer.IMA_SDK_URL}"]`
|
|
1068
|
+
);
|
|
1069
|
+
if (existing) {
|
|
1070
|
+
return new Promise((resolve) => {
|
|
1071
|
+
const start = Date.now();
|
|
1072
|
+
const check = () => {
|
|
1073
|
+
if (typeof google !== "undefined" && google?.ima) {
|
|
1074
|
+
resolve(true);
|
|
1075
|
+
} else if (Date.now() - start > 5e3) {
|
|
1076
|
+
resolve(false);
|
|
1077
|
+
} else {
|
|
1078
|
+
setTimeout(check, 100);
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
check();
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
if (!_ProgrammaticPlayer.imaLoadPromise) {
|
|
1085
|
+
_ProgrammaticPlayer.imaLoadPromise = new Promise((resolve) => {
|
|
1086
|
+
const script = document.createElement("script");
|
|
1087
|
+
script.src = _ProgrammaticPlayer.IMA_SDK_URL;
|
|
1088
|
+
script.async = true;
|
|
1089
|
+
script.onload = () => {
|
|
1090
|
+
_ProgrammaticPlayer.imaLoadPromise = null;
|
|
1091
|
+
resolve(true);
|
|
1092
|
+
};
|
|
1093
|
+
script.onerror = () => {
|
|
1094
|
+
_ProgrammaticPlayer.imaLoadPromise = null;
|
|
1095
|
+
resolve(false);
|
|
1096
|
+
};
|
|
1097
|
+
document.head.appendChild(script);
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
return _ProgrammaticPlayer.imaLoadPromise;
|
|
1101
|
+
}
|
|
1042
1102
|
// ── IMA request / playback lifecycle ──────────────────────
|
|
1043
1103
|
async requestAdsViaIMA(vastUrl, onComplete, onError) {
|
|
1044
1104
|
return new Promise((resolve) => {
|
|
@@ -1240,6 +1300,12 @@ var ProgrammaticPlayer = class {
|
|
|
1240
1300
|
this.telemetry.reset();
|
|
1241
1301
|
}
|
|
1242
1302
|
};
|
|
1303
|
+
// ── IMA SDK loader ──────────────────────────────────────────
|
|
1304
|
+
/** IMA script URL */
|
|
1305
|
+
_ProgrammaticPlayer.IMA_SDK_URL = "https://imasdk.googleapis.com/js/sdkloader/ima3.js";
|
|
1306
|
+
/** Singleton load promise so concurrent calls don't insert multiple scripts. */
|
|
1307
|
+
_ProgrammaticPlayer.imaLoadPromise = null;
|
|
1308
|
+
var ProgrammaticPlayer = _ProgrammaticPlayer;
|
|
1243
1309
|
|
|
1244
1310
|
// src/player/Player.ts
|
|
1245
1311
|
var MAX_AD_DURATION_SECONDS = 300;
|
|
@@ -1674,11 +1740,104 @@ var _TrillboardsAds = class _TrillboardsAds {
|
|
|
1674
1740
|
}
|
|
1675
1741
|
startHeartbeatTimer() {
|
|
1676
1742
|
if (this.state.heartbeatTimer) clearInterval(this.state.heartbeatTimer);
|
|
1743
|
+
const tick = () => {
|
|
1744
|
+
this.sendHeartbeatTick().catch(() => {
|
|
1745
|
+
});
|
|
1746
|
+
};
|
|
1747
|
+
tick();
|
|
1677
1748
|
this.state.heartbeatTimer = setInterval(() => {
|
|
1678
|
-
|
|
1749
|
+
tick();
|
|
1679
1750
|
}, this.config.heartbeatInterval);
|
|
1680
1751
|
}
|
|
1752
|
+
async sendHeartbeatTick() {
|
|
1753
|
+
const result = await this.api.sendHeartbeat(
|
|
1754
|
+
this.config.deviceId,
|
|
1755
|
+
this.state.screenId,
|
|
1756
|
+
this.buildHeartbeatPayload()
|
|
1757
|
+
);
|
|
1758
|
+
if (!result.ok || !result.data) return;
|
|
1759
|
+
if (result.data.ad_delivery_profile?.mode) {
|
|
1760
|
+
this.applyAdDeliveryProfile(result.data.ad_delivery_profile);
|
|
1761
|
+
}
|
|
1762
|
+
if (Array.isArray(result.data.commands) && result.data.commands.length > 0) {
|
|
1763
|
+
this.runHeartbeatCommands(result.data.commands);
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
buildHeartbeatPayload() {
|
|
1767
|
+
const userAgent = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
1768
|
+
const platform = typeof navigator !== "undefined" ? navigator.platform : null;
|
|
1769
|
+
const connection = typeof navigator !== "undefined" ? navigator.connection : null;
|
|
1770
|
+
const chromiumMatch = userAgent.match(/(?:Chrome|Chromium)\/(\d+)/i);
|
|
1771
|
+
const chromiumMajor = chromiumMatch ? Number(chromiumMatch[1]) : void 0;
|
|
1772
|
+
const imaSupported = typeof window !== "undefined" ? Boolean(window?.google?.ima) : null;
|
|
1773
|
+
const telemetry = {
|
|
1774
|
+
powerState: typeof document !== "undefined" && document.hidden ? "standby" : "on",
|
|
1775
|
+
agentStatus: typeof document !== "undefined" && document.hidden ? "background" : "foreground",
|
|
1776
|
+
os: platform || void 0,
|
|
1777
|
+
agentVersion: SDK_VERSION,
|
|
1778
|
+
ima_integration: "html5_webview",
|
|
1779
|
+
ima_supported: imaSupported,
|
|
1780
|
+
webview_chromium_major: Number.isFinite(chromiumMajor) ? chromiumMajor : void 0
|
|
1781
|
+
};
|
|
1782
|
+
const payload = {
|
|
1783
|
+
telemetry,
|
|
1784
|
+
sdk: {
|
|
1785
|
+
version: SDK_VERSION,
|
|
1786
|
+
ima_supported: imaSupported,
|
|
1787
|
+
ima_integration: "html5_webview",
|
|
1788
|
+
webview_chromium_major: Number.isFinite(chromiumMajor) ? chromiumMajor : void 0
|
|
1789
|
+
},
|
|
1790
|
+
device: {
|
|
1791
|
+
os: platform || void 0,
|
|
1792
|
+
model: userAgent || void 0
|
|
1793
|
+
}
|
|
1794
|
+
};
|
|
1795
|
+
if (connection) {
|
|
1796
|
+
payload.network = {
|
|
1797
|
+
connectionType: connection.type || void 0,
|
|
1798
|
+
effectiveType: connection.effectiveType || void 0,
|
|
1799
|
+
downlinkMbps: typeof connection.downlink === "number" ? connection.downlink : void 0,
|
|
1800
|
+
bandwidthMbps: typeof connection.downlink === "number" ? connection.downlink : void 0,
|
|
1801
|
+
rtt: typeof connection.rtt === "number" ? connection.rtt : void 0,
|
|
1802
|
+
latencyRtt: typeof connection.rtt === "number" ? connection.rtt : void 0,
|
|
1803
|
+
saveData: typeof connection.saveData === "boolean" ? connection.saveData : void 0
|
|
1804
|
+
};
|
|
1805
|
+
}
|
|
1806
|
+
return payload;
|
|
1807
|
+
}
|
|
1808
|
+
applyAdDeliveryProfile(profile) {
|
|
1809
|
+
if (!profile?.mode) return;
|
|
1810
|
+
const previousMode = this.state.adDeliveryProfile?.mode ?? null;
|
|
1811
|
+
this.state.adDeliveryProfile = profile;
|
|
1812
|
+
if (profile.mode === "vast_fallback" && this.state.programmaticPlaying) {
|
|
1813
|
+
this.programmaticPlayer.stop({ silent: true });
|
|
1814
|
+
this.state.programmaticPlaying = false;
|
|
1815
|
+
}
|
|
1816
|
+
if (previousMode !== profile.mode) {
|
|
1817
|
+
this.emitStateChanged();
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
runHeartbeatCommands(commands) {
|
|
1821
|
+
for (const command of commands) {
|
|
1822
|
+
const name = String(command?.command || "").toLowerCase();
|
|
1823
|
+
if (name === "refresh_ads") {
|
|
1824
|
+
this.refresh().catch(() => {
|
|
1825
|
+
});
|
|
1826
|
+
} else if (name === "restart") {
|
|
1827
|
+
if (typeof window !== "undefined" && typeof window.location?.reload === "function") {
|
|
1828
|
+
window.location.reload();
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
this.refresh().catch(() => {
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1681
1836
|
playNextAd() {
|
|
1837
|
+
if (this.state.adDeliveryProfile?.mode === "vast_fallback" && this.state.ads.length > 0) {
|
|
1838
|
+
this.playDirect();
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1682
1841
|
const mode = this.state.waterfallMode;
|
|
1683
1842
|
if (mode === "programmatic_only" || mode === "programmatic_then_direct") {
|
|
1684
1843
|
this.playProgrammatic();
|
|
@@ -1693,13 +1852,20 @@ var _TrillboardsAds = class _TrillboardsAds {
|
|
|
1693
1852
|
this.programmaticPlayer.play(
|
|
1694
1853
|
() => {
|
|
1695
1854
|
this.state.programmaticPlaying = false;
|
|
1855
|
+
this.resetProgrammaticBackoff();
|
|
1696
1856
|
this.emitStateChanged();
|
|
1697
1857
|
this.scheduleProgrammaticRetry();
|
|
1698
1858
|
},
|
|
1699
1859
|
(error) => {
|
|
1700
1860
|
this.state.programmaticPlaying = false;
|
|
1701
1861
|
this.state.programmaticLastError = error;
|
|
1702
|
-
|
|
1862
|
+
const normalizedError = String(error || "").toLowerCase();
|
|
1863
|
+
const isNoFillLike = normalizedError.includes("no fill") || normalizedError.includes("no ads vast response") || normalizedError.includes("waterfall_exhausted") || normalizedError === "throttled" || normalizedError === "busy";
|
|
1864
|
+
if (isNoFillLike) {
|
|
1865
|
+
this.state.programmaticRetryCount = 0;
|
|
1866
|
+
} else {
|
|
1867
|
+
this.state.programmaticRetryCount++;
|
|
1868
|
+
}
|
|
1703
1869
|
this.emitStateChanged();
|
|
1704
1870
|
if (this.state.waterfallMode === "programmatic_then_direct" && this.state.ads.length > 0) {
|
|
1705
1871
|
this.playDirect();
|
|
@@ -1821,12 +1987,12 @@ var TrillboardsError = class extends Error {
|
|
|
1821
1987
|
var TrillboardsAuthenticationError = class extends TrillboardsError {
|
|
1822
1988
|
constructor(message) {
|
|
1823
1989
|
super(
|
|
1824
|
-
message ?? "Invalid API key. Check your key at https://trillboards.com/developers",
|
|
1990
|
+
message ?? "Invalid API key. Check your key at https://trillboards.com/support/developers",
|
|
1825
1991
|
{
|
|
1826
1992
|
type: "authentication_error",
|
|
1827
1993
|
code: "invalid_api_key",
|
|
1828
1994
|
statusCode: 401,
|
|
1829
|
-
help: "https://trillboards.com/developers"
|
|
1995
|
+
help: "https://trillboards.com/support/developers"
|
|
1830
1996
|
}
|
|
1831
1997
|
);
|
|
1832
1998
|
this.name = "TrillboardsAuthenticationError";
|