@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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@trillboards/ads-sdk` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [2.2.0] - 2026-02-24
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
- Heartbeat payloads now include runtime telemetry (`telemetry`, `sdk`, `device`, `network`) for partner-side device profiling.
|
|
9
|
+
- SDK now consumes heartbeat `ad_delivery_profile` responses and prefers direct fallback playback when profile mode is `vast_fallback`.
|
|
10
|
+
- SDK now executes queued heartbeat commands (`refresh_ads`, `restart`) delivered by the partner API polling channel.
|
|
11
|
+
|
|
12
|
+
### Fixes
|
|
13
|
+
- Programmatic retry/backoff now resets correctly on ad completion and no-fill-like errors to prevent runaway retry escalation.
|
|
14
|
+
|
|
5
15
|
## [2.1.1] - 2026-02-15
|
|
6
16
|
|
|
7
17
|
### Fixes
|
package/README.md
CHANGED
|
@@ -93,6 +93,14 @@ The `TrillboardsConfig` interface accepts the following options:
|
|
|
93
93
|
| `refreshInterval` | `number` | `120000` | Ad refresh interval (ms) |
|
|
94
94
|
| `cacheSize` | `number` | `10` | Max cached ads |
|
|
95
95
|
|
|
96
|
+
## Heartbeat Telemetry and Delivery Profile
|
|
97
|
+
|
|
98
|
+
Every heartbeat now includes runtime telemetry (`sdk`, `device`, `network`, `ad`) so the partner API can segment legacy devices and return an `ad_delivery_profile`.
|
|
99
|
+
|
|
100
|
+
- `mode: "ima_sdk"` keeps normal IMA playback.
|
|
101
|
+
- `mode: "vast_fallback"` indicates the SDK should prefer direct fallback playback when direct ads are available.
|
|
102
|
+
- Heartbeat command polling now executes queued `refresh_ads` commands immediately.
|
|
103
|
+
|
|
96
104
|
## Debug Mode
|
|
97
105
|
|
|
98
106
|
Enable debug logging to see all API requests, state transitions, and cache operations:
|
|
@@ -215,7 +223,7 @@ console.log('Tracked:', result.tracked);
|
|
|
215
223
|
|
|
216
224
|
## Links
|
|
217
225
|
|
|
218
|
-
- [Developer documentation](https://trillboards.com/developers/partner-sdk)
|
|
226
|
+
- [Developer documentation](https://trillboards.com/support/developers/partner-sdk)
|
|
219
227
|
- [API reference](https://api.trillboards.com/docs/partner)
|
|
220
228
|
|
|
221
229
|
## License
|
package/dist/cli.js
CHANGED
|
@@ -28,7 +28,7 @@ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
|
|
|
28
28
|
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
29
29
|
|
|
30
30
|
// src/core/config.ts
|
|
31
|
-
var SDK_VERSION = "2.
|
|
31
|
+
var SDK_VERSION = "2.2.0";
|
|
32
32
|
|
|
33
33
|
// src/cli.ts
|
|
34
34
|
var BANNER = `
|
|
@@ -127,7 +127,7 @@ async function runInit() {
|
|
|
127
127
|
console.log(" Next steps:");
|
|
128
128
|
console.log(" 1. Register more devices: POST /v1/partner/device");
|
|
129
129
|
console.log(" 2. Connect Stripe for payouts: POST /v1/partner/stripe/connect");
|
|
130
|
-
console.log(" 3. Read the docs: https://trillboards.com/developers");
|
|
130
|
+
console.log(" 3. Read the docs: https://trillboards.com/support/developers");
|
|
131
131
|
console.log("");
|
|
132
132
|
completed = true;
|
|
133
133
|
} catch (err) {
|
package/dist/index.d.mts
CHANGED
|
@@ -59,6 +59,7 @@ interface TrillboardsState {
|
|
|
59
59
|
programmaticPlaying: boolean;
|
|
60
60
|
prefetchedReady: boolean;
|
|
61
61
|
waterfallMode: WaterfallMode;
|
|
62
|
+
adDeliveryProfileMode: AdDeliveryMode | null;
|
|
62
63
|
screenId: string | null;
|
|
63
64
|
deviceId: string | null;
|
|
64
65
|
}
|
|
@@ -113,6 +114,37 @@ interface CircuitBreakerState {
|
|
|
113
114
|
consecutiveFailures: number;
|
|
114
115
|
openUntil: number;
|
|
115
116
|
}
|
|
117
|
+
type AdDeliveryMode = 'ima_sdk' | 'vast_fallback';
|
|
118
|
+
interface AdDeliveryProfile {
|
|
119
|
+
mode: AdDeliveryMode;
|
|
120
|
+
reason?: string;
|
|
121
|
+
auto_recovery_enabled?: boolean;
|
|
122
|
+
ima_integration?: 'android_native' | 'html5_webview' | 'disabled' | 'unknown';
|
|
123
|
+
android_api_level?: number | null;
|
|
124
|
+
webview_chromium_major?: number | null;
|
|
125
|
+
notes?: string[];
|
|
126
|
+
}
|
|
127
|
+
interface HeartbeatCommand {
|
|
128
|
+
id: string;
|
|
129
|
+
command: string;
|
|
130
|
+
payload?: Record<string, unknown>;
|
|
131
|
+
queued_at?: string;
|
|
132
|
+
}
|
|
133
|
+
interface HeartbeatResponseData {
|
|
134
|
+
status?: 'online' | 'offline' | string;
|
|
135
|
+
last_seen_at?: string;
|
|
136
|
+
ad_delivery_profile?: AdDeliveryProfile;
|
|
137
|
+
commands?: HeartbeatCommand[];
|
|
138
|
+
}
|
|
139
|
+
interface HeartbeatPayload {
|
|
140
|
+
telemetry?: Record<string, unknown>;
|
|
141
|
+
metadata?: Record<string, unknown>;
|
|
142
|
+
sdk?: Record<string, unknown>;
|
|
143
|
+
device?: Record<string, unknown>;
|
|
144
|
+
network?: Record<string, unknown>;
|
|
145
|
+
ad?: Record<string, unknown>;
|
|
146
|
+
[key: string]: unknown;
|
|
147
|
+
}
|
|
116
148
|
interface FetchAdsResult {
|
|
117
149
|
ads: AdItem[];
|
|
118
150
|
settings: Record<string, unknown>;
|
|
@@ -260,6 +292,10 @@ declare class TrillboardsAds {
|
|
|
260
292
|
private refreshAds;
|
|
261
293
|
private startRefreshTimer;
|
|
262
294
|
private startHeartbeatTimer;
|
|
295
|
+
private sendHeartbeatTick;
|
|
296
|
+
private buildHeartbeatPayload;
|
|
297
|
+
private applyAdDeliveryProfile;
|
|
298
|
+
private runHeartbeatCommands;
|
|
263
299
|
private playNextAd;
|
|
264
300
|
private playProgrammatic;
|
|
265
301
|
private playDirect;
|
|
@@ -317,7 +353,7 @@ declare class EventEmitter {
|
|
|
317
353
|
}
|
|
318
354
|
|
|
319
355
|
/** Single source of truth for the SDK version string. */
|
|
320
|
-
declare const SDK_VERSION = "2.
|
|
356
|
+
declare const SDK_VERSION = "2.2.0";
|
|
321
357
|
/**
|
|
322
358
|
* Compile-time constants that mirror the original IIFE CONFIG
|
|
323
359
|
* object. Every value here acts as a sensible production default
|
|
@@ -336,7 +372,7 @@ declare const DEFAULT_CONFIG: {
|
|
|
336
372
|
readonly PROGRAMMATIC_MIN_INTERVAL_MS: 5000;
|
|
337
373
|
readonly PROGRAMMATIC_RETRY_MS: 5000;
|
|
338
374
|
readonly PROGRAMMATIC_BACKOFF_MAX_MS: number;
|
|
339
|
-
readonly VERSION: "2.
|
|
375
|
+
readonly VERSION: "2.2.0";
|
|
340
376
|
};
|
|
341
377
|
/**
|
|
342
378
|
* Merge a caller-supplied TrillboardsConfig with the defaults,
|
|
@@ -392,6 +428,7 @@ interface InternalState {
|
|
|
392
428
|
programmaticRetryActive: boolean;
|
|
393
429
|
programmaticRetryCount: number;
|
|
394
430
|
programmaticLastError: string | null;
|
|
431
|
+
adDeliveryProfile: AdDeliveryProfile | null;
|
|
395
432
|
initialized: boolean;
|
|
396
433
|
screenOrientation: string | null;
|
|
397
434
|
screenDimensions: ScreenDimensions | null;
|
|
@@ -537,6 +574,10 @@ declare class WaterfallEngine {
|
|
|
537
574
|
recordFailure(sourceName: string): void;
|
|
538
575
|
}
|
|
539
576
|
|
|
577
|
+
interface HeartbeatResult {
|
|
578
|
+
ok: boolean;
|
|
579
|
+
data: HeartbeatResponseData | null;
|
|
580
|
+
}
|
|
540
581
|
/**
|
|
541
582
|
* Low-level HTTP client for the Trillboards Partner API.
|
|
542
583
|
*
|
|
@@ -580,10 +621,9 @@ declare class ApiClient {
|
|
|
580
621
|
completed: boolean;
|
|
581
622
|
}): Promise<boolean>;
|
|
582
623
|
/**
|
|
583
|
-
* Ping
|
|
584
|
-
* Returns `true` on 2xx, `false` on any error.
|
|
624
|
+
* Ping heartbeat endpoint and return delivery profile + queued commands.
|
|
585
625
|
*/
|
|
586
|
-
sendHeartbeat(deviceId: string, screenId: string | null): Promise<
|
|
626
|
+
sendHeartbeat(deviceId: string, screenId: string | null, payload?: HeartbeatPayload): Promise<HeartbeatResult>;
|
|
587
627
|
/**
|
|
588
628
|
* Report a programmatic lifecycle event (VAST fill, timeout,
|
|
589
629
|
* error, etc.) to the analytics backend.
|
package/dist/index.d.ts
CHANGED
|
@@ -59,6 +59,7 @@ interface TrillboardsState {
|
|
|
59
59
|
programmaticPlaying: boolean;
|
|
60
60
|
prefetchedReady: boolean;
|
|
61
61
|
waterfallMode: WaterfallMode;
|
|
62
|
+
adDeliveryProfileMode: AdDeliveryMode | null;
|
|
62
63
|
screenId: string | null;
|
|
63
64
|
deviceId: string | null;
|
|
64
65
|
}
|
|
@@ -113,6 +114,37 @@ interface CircuitBreakerState {
|
|
|
113
114
|
consecutiveFailures: number;
|
|
114
115
|
openUntil: number;
|
|
115
116
|
}
|
|
117
|
+
type AdDeliveryMode = 'ima_sdk' | 'vast_fallback';
|
|
118
|
+
interface AdDeliveryProfile {
|
|
119
|
+
mode: AdDeliveryMode;
|
|
120
|
+
reason?: string;
|
|
121
|
+
auto_recovery_enabled?: boolean;
|
|
122
|
+
ima_integration?: 'android_native' | 'html5_webview' | 'disabled' | 'unknown';
|
|
123
|
+
android_api_level?: number | null;
|
|
124
|
+
webview_chromium_major?: number | null;
|
|
125
|
+
notes?: string[];
|
|
126
|
+
}
|
|
127
|
+
interface HeartbeatCommand {
|
|
128
|
+
id: string;
|
|
129
|
+
command: string;
|
|
130
|
+
payload?: Record<string, unknown>;
|
|
131
|
+
queued_at?: string;
|
|
132
|
+
}
|
|
133
|
+
interface HeartbeatResponseData {
|
|
134
|
+
status?: 'online' | 'offline' | string;
|
|
135
|
+
last_seen_at?: string;
|
|
136
|
+
ad_delivery_profile?: AdDeliveryProfile;
|
|
137
|
+
commands?: HeartbeatCommand[];
|
|
138
|
+
}
|
|
139
|
+
interface HeartbeatPayload {
|
|
140
|
+
telemetry?: Record<string, unknown>;
|
|
141
|
+
metadata?: Record<string, unknown>;
|
|
142
|
+
sdk?: Record<string, unknown>;
|
|
143
|
+
device?: Record<string, unknown>;
|
|
144
|
+
network?: Record<string, unknown>;
|
|
145
|
+
ad?: Record<string, unknown>;
|
|
146
|
+
[key: string]: unknown;
|
|
147
|
+
}
|
|
116
148
|
interface FetchAdsResult {
|
|
117
149
|
ads: AdItem[];
|
|
118
150
|
settings: Record<string, unknown>;
|
|
@@ -260,6 +292,10 @@ declare class TrillboardsAds {
|
|
|
260
292
|
private refreshAds;
|
|
261
293
|
private startRefreshTimer;
|
|
262
294
|
private startHeartbeatTimer;
|
|
295
|
+
private sendHeartbeatTick;
|
|
296
|
+
private buildHeartbeatPayload;
|
|
297
|
+
private applyAdDeliveryProfile;
|
|
298
|
+
private runHeartbeatCommands;
|
|
263
299
|
private playNextAd;
|
|
264
300
|
private playProgrammatic;
|
|
265
301
|
private playDirect;
|
|
@@ -317,7 +353,7 @@ declare class EventEmitter {
|
|
|
317
353
|
}
|
|
318
354
|
|
|
319
355
|
/** Single source of truth for the SDK version string. */
|
|
320
|
-
declare const SDK_VERSION = "2.
|
|
356
|
+
declare const SDK_VERSION = "2.2.0";
|
|
321
357
|
/**
|
|
322
358
|
* Compile-time constants that mirror the original IIFE CONFIG
|
|
323
359
|
* object. Every value here acts as a sensible production default
|
|
@@ -336,7 +372,7 @@ declare const DEFAULT_CONFIG: {
|
|
|
336
372
|
readonly PROGRAMMATIC_MIN_INTERVAL_MS: 5000;
|
|
337
373
|
readonly PROGRAMMATIC_RETRY_MS: 5000;
|
|
338
374
|
readonly PROGRAMMATIC_BACKOFF_MAX_MS: number;
|
|
339
|
-
readonly VERSION: "2.
|
|
375
|
+
readonly VERSION: "2.2.0";
|
|
340
376
|
};
|
|
341
377
|
/**
|
|
342
378
|
* Merge a caller-supplied TrillboardsConfig with the defaults,
|
|
@@ -392,6 +428,7 @@ interface InternalState {
|
|
|
392
428
|
programmaticRetryActive: boolean;
|
|
393
429
|
programmaticRetryCount: number;
|
|
394
430
|
programmaticLastError: string | null;
|
|
431
|
+
adDeliveryProfile: AdDeliveryProfile | null;
|
|
395
432
|
initialized: boolean;
|
|
396
433
|
screenOrientation: string | null;
|
|
397
434
|
screenDimensions: ScreenDimensions | null;
|
|
@@ -537,6 +574,10 @@ declare class WaterfallEngine {
|
|
|
537
574
|
recordFailure(sourceName: string): void;
|
|
538
575
|
}
|
|
539
576
|
|
|
577
|
+
interface HeartbeatResult {
|
|
578
|
+
ok: boolean;
|
|
579
|
+
data: HeartbeatResponseData | null;
|
|
580
|
+
}
|
|
540
581
|
/**
|
|
541
582
|
* Low-level HTTP client for the Trillboards Partner API.
|
|
542
583
|
*
|
|
@@ -580,10 +621,9 @@ declare class ApiClient {
|
|
|
580
621
|
completed: boolean;
|
|
581
622
|
}): Promise<boolean>;
|
|
582
623
|
/**
|
|
583
|
-
* Ping
|
|
584
|
-
* Returns `true` on 2xx, `false` on any error.
|
|
624
|
+
* Ping heartbeat endpoint and return delivery profile + queued commands.
|
|
585
625
|
*/
|
|
586
|
-
sendHeartbeat(deviceId: string, screenId: string | null): Promise<
|
|
626
|
+
sendHeartbeat(deviceId: string, screenId: string | null, payload?: HeartbeatPayload): Promise<HeartbeatResult>;
|
|
587
627
|
/**
|
|
588
628
|
* Report a programmatic lifecycle event (VAST fill, timeout,
|
|
589
629
|
* error, etc.) to the analytics backend.
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// src/core/config.ts
|
|
4
|
-
var SDK_VERSION = "2.
|
|
4
|
+
var SDK_VERSION = "2.2.0";
|
|
5
5
|
var DEFAULT_CONFIG = {
|
|
6
6
|
API_BASE: "https://api.trillboards.com/v1/partner",
|
|
7
7
|
CDN_BASE: "https://cdn.trillboards.com",
|
|
@@ -60,6 +60,7 @@ var DEFAULT_PUBLIC_STATE = {
|
|
|
60
60
|
programmaticPlaying: false,
|
|
61
61
|
prefetchedReady: false,
|
|
62
62
|
waterfallMode: "programmatic_only",
|
|
63
|
+
adDeliveryProfileMode: null,
|
|
63
64
|
screenId: null,
|
|
64
65
|
deviceId: null
|
|
65
66
|
};
|
|
@@ -87,6 +88,7 @@ function createInitialState() {
|
|
|
87
88
|
programmaticRetryActive: false,
|
|
88
89
|
programmaticRetryCount: 0,
|
|
89
90
|
programmaticLastError: null,
|
|
91
|
+
adDeliveryProfile: null,
|
|
90
92
|
initialized: false,
|
|
91
93
|
screenOrientation: null,
|
|
92
94
|
screenDimensions: null,
|
|
@@ -104,6 +106,7 @@ function getPublicState(internal) {
|
|
|
104
106
|
programmaticPlaying: internal.programmaticPlaying,
|
|
105
107
|
prefetchedReady: internal.prefetchedReady ?? false,
|
|
106
108
|
waterfallMode: internal.waterfallMode,
|
|
109
|
+
adDeliveryProfileMode: internal.adDeliveryProfile?.mode ?? null,
|
|
107
110
|
screenId: internal.screenId,
|
|
108
111
|
deviceId: internal.deviceId
|
|
109
112
|
};
|
|
@@ -364,10 +367,9 @@ var ApiClient = class {
|
|
|
364
367
|
}
|
|
365
368
|
}
|
|
366
369
|
/**
|
|
367
|
-
* Ping
|
|
368
|
-
* Returns `true` on 2xx, `false` on any error.
|
|
370
|
+
* Ping heartbeat endpoint and return delivery profile + queued commands.
|
|
369
371
|
*/
|
|
370
|
-
async sendHeartbeat(deviceId, screenId) {
|
|
372
|
+
async sendHeartbeat(deviceId, screenId, payload = {}) {
|
|
371
373
|
logger.debug("Sending heartbeat", { deviceId });
|
|
372
374
|
try {
|
|
373
375
|
const response = await fetch(`${this.apiBase}/device/${deviceId}/heartbeat`, {
|
|
@@ -377,13 +379,24 @@ var ApiClient = class {
|
|
|
377
379
|
device_id: deviceId,
|
|
378
380
|
screen_id: screenId,
|
|
379
381
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
380
|
-
status: "active"
|
|
382
|
+
status: "active",
|
|
383
|
+
...payload
|
|
381
384
|
}),
|
|
382
385
|
signal: AbortSignal.timeout(5e3)
|
|
383
386
|
});
|
|
384
|
-
|
|
387
|
+
if (!response.ok) {
|
|
388
|
+
return { ok: false, data: null };
|
|
389
|
+
}
|
|
390
|
+
let data = null;
|
|
391
|
+
try {
|
|
392
|
+
const json = await response.json();
|
|
393
|
+
data = json?.data ?? null;
|
|
394
|
+
} catch {
|
|
395
|
+
data = null;
|
|
396
|
+
}
|
|
397
|
+
return { ok: true, data };
|
|
385
398
|
} catch {
|
|
386
|
-
return false;
|
|
399
|
+
return { ok: false, data: null };
|
|
387
400
|
}
|
|
388
401
|
}
|
|
389
402
|
/**
|
|
@@ -936,7 +949,7 @@ var WaterfallEngine = class {
|
|
|
936
949
|
};
|
|
937
950
|
|
|
938
951
|
// src/player/ProgrammaticPlayer.ts
|
|
939
|
-
var
|
|
952
|
+
var _ProgrammaticPlayer = class _ProgrammaticPlayer {
|
|
940
953
|
constructor(events, timeoutMs = 12e3) {
|
|
941
954
|
// ── Public state ──────────────────────────────────────────
|
|
942
955
|
this.vastTagUrl = null;
|
|
@@ -1023,8 +1036,11 @@ var ProgrammaticPlayer = class {
|
|
|
1023
1036
|
this.telemetry.recordRequest();
|
|
1024
1037
|
const correlator = Date.now();
|
|
1025
1038
|
const finalUrl = vastUrl.includes("correlator=") ? vastUrl.replace(/correlator=[^&]*/, `correlator=${correlator}`) : `${vastUrl}${vastUrl.includes("?") ? "&" : "?"}correlator=${correlator}`;
|
|
1026
|
-
|
|
1027
|
-
|
|
1039
|
+
const imaReady = await this.ensureImaSdk();
|
|
1040
|
+
if (!imaReady) {
|
|
1041
|
+
const msg = "Google IMA SDK not loaded";
|
|
1042
|
+
this.events.emit("programmatic_error", { error: msg, code: void 0 });
|
|
1043
|
+
onError(msg);
|
|
1028
1044
|
this.telemetry.recordError();
|
|
1029
1045
|
this.waterfallEngine.recordFailure(sourceName);
|
|
1030
1046
|
return;
|
|
@@ -1041,6 +1057,50 @@ var ProgrammaticPlayer = class {
|
|
|
1041
1057
|
onError(err instanceof Error ? err.message : String(err));
|
|
1042
1058
|
}
|
|
1043
1059
|
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Ensure Google IMA SDK is available. If already loaded, resolves
|
|
1062
|
+
* immediately. Otherwise injects the `<script>` tag and waits.
|
|
1063
|
+
* Matches the Lite SDK's `loadImaScript()` pattern.
|
|
1064
|
+
*/
|
|
1065
|
+
async ensureImaSdk() {
|
|
1066
|
+
if (typeof google !== "undefined" && google?.ima) return true;
|
|
1067
|
+
if (typeof document === "undefined") return false;
|
|
1068
|
+
const existing = document.querySelector(
|
|
1069
|
+
`script[src="${_ProgrammaticPlayer.IMA_SDK_URL}"]`
|
|
1070
|
+
);
|
|
1071
|
+
if (existing) {
|
|
1072
|
+
return new Promise((resolve) => {
|
|
1073
|
+
const start = Date.now();
|
|
1074
|
+
const check = () => {
|
|
1075
|
+
if (typeof google !== "undefined" && google?.ima) {
|
|
1076
|
+
resolve(true);
|
|
1077
|
+
} else if (Date.now() - start > 5e3) {
|
|
1078
|
+
resolve(false);
|
|
1079
|
+
} else {
|
|
1080
|
+
setTimeout(check, 100);
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
check();
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
if (!_ProgrammaticPlayer.imaLoadPromise) {
|
|
1087
|
+
_ProgrammaticPlayer.imaLoadPromise = new Promise((resolve) => {
|
|
1088
|
+
const script = document.createElement("script");
|
|
1089
|
+
script.src = _ProgrammaticPlayer.IMA_SDK_URL;
|
|
1090
|
+
script.async = true;
|
|
1091
|
+
script.onload = () => {
|
|
1092
|
+
_ProgrammaticPlayer.imaLoadPromise = null;
|
|
1093
|
+
resolve(true);
|
|
1094
|
+
};
|
|
1095
|
+
script.onerror = () => {
|
|
1096
|
+
_ProgrammaticPlayer.imaLoadPromise = null;
|
|
1097
|
+
resolve(false);
|
|
1098
|
+
};
|
|
1099
|
+
document.head.appendChild(script);
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
return _ProgrammaticPlayer.imaLoadPromise;
|
|
1103
|
+
}
|
|
1044
1104
|
// ── IMA request / playback lifecycle ──────────────────────
|
|
1045
1105
|
async requestAdsViaIMA(vastUrl, onComplete, onError) {
|
|
1046
1106
|
return new Promise((resolve) => {
|
|
@@ -1242,6 +1302,12 @@ var ProgrammaticPlayer = class {
|
|
|
1242
1302
|
this.telemetry.reset();
|
|
1243
1303
|
}
|
|
1244
1304
|
};
|
|
1305
|
+
// ── IMA SDK loader ──────────────────────────────────────────
|
|
1306
|
+
/** IMA script URL */
|
|
1307
|
+
_ProgrammaticPlayer.IMA_SDK_URL = "https://imasdk.googleapis.com/js/sdkloader/ima3.js";
|
|
1308
|
+
/** Singleton load promise so concurrent calls don't insert multiple scripts. */
|
|
1309
|
+
_ProgrammaticPlayer.imaLoadPromise = null;
|
|
1310
|
+
var ProgrammaticPlayer = _ProgrammaticPlayer;
|
|
1245
1311
|
|
|
1246
1312
|
// src/player/Player.ts
|
|
1247
1313
|
var MAX_AD_DURATION_SECONDS = 300;
|
|
@@ -1676,11 +1742,104 @@ var _TrillboardsAds = class _TrillboardsAds {
|
|
|
1676
1742
|
}
|
|
1677
1743
|
startHeartbeatTimer() {
|
|
1678
1744
|
if (this.state.heartbeatTimer) clearInterval(this.state.heartbeatTimer);
|
|
1745
|
+
const tick = () => {
|
|
1746
|
+
this.sendHeartbeatTick().catch(() => {
|
|
1747
|
+
});
|
|
1748
|
+
};
|
|
1749
|
+
tick();
|
|
1679
1750
|
this.state.heartbeatTimer = setInterval(() => {
|
|
1680
|
-
|
|
1751
|
+
tick();
|
|
1681
1752
|
}, this.config.heartbeatInterval);
|
|
1682
1753
|
}
|
|
1754
|
+
async sendHeartbeatTick() {
|
|
1755
|
+
const result = await this.api.sendHeartbeat(
|
|
1756
|
+
this.config.deviceId,
|
|
1757
|
+
this.state.screenId,
|
|
1758
|
+
this.buildHeartbeatPayload()
|
|
1759
|
+
);
|
|
1760
|
+
if (!result.ok || !result.data) return;
|
|
1761
|
+
if (result.data.ad_delivery_profile?.mode) {
|
|
1762
|
+
this.applyAdDeliveryProfile(result.data.ad_delivery_profile);
|
|
1763
|
+
}
|
|
1764
|
+
if (Array.isArray(result.data.commands) && result.data.commands.length > 0) {
|
|
1765
|
+
this.runHeartbeatCommands(result.data.commands);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
buildHeartbeatPayload() {
|
|
1769
|
+
const userAgent = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
1770
|
+
const platform = typeof navigator !== "undefined" ? navigator.platform : null;
|
|
1771
|
+
const connection = typeof navigator !== "undefined" ? navigator.connection : null;
|
|
1772
|
+
const chromiumMatch = userAgent.match(/(?:Chrome|Chromium)\/(\d+)/i);
|
|
1773
|
+
const chromiumMajor = chromiumMatch ? Number(chromiumMatch[1]) : void 0;
|
|
1774
|
+
const imaSupported = typeof window !== "undefined" ? Boolean(window?.google?.ima) : null;
|
|
1775
|
+
const telemetry = {
|
|
1776
|
+
powerState: typeof document !== "undefined" && document.hidden ? "standby" : "on",
|
|
1777
|
+
agentStatus: typeof document !== "undefined" && document.hidden ? "background" : "foreground",
|
|
1778
|
+
os: platform || void 0,
|
|
1779
|
+
agentVersion: SDK_VERSION,
|
|
1780
|
+
ima_integration: "html5_webview",
|
|
1781
|
+
ima_supported: imaSupported,
|
|
1782
|
+
webview_chromium_major: Number.isFinite(chromiumMajor) ? chromiumMajor : void 0
|
|
1783
|
+
};
|
|
1784
|
+
const payload = {
|
|
1785
|
+
telemetry,
|
|
1786
|
+
sdk: {
|
|
1787
|
+
version: SDK_VERSION,
|
|
1788
|
+
ima_supported: imaSupported,
|
|
1789
|
+
ima_integration: "html5_webview",
|
|
1790
|
+
webview_chromium_major: Number.isFinite(chromiumMajor) ? chromiumMajor : void 0
|
|
1791
|
+
},
|
|
1792
|
+
device: {
|
|
1793
|
+
os: platform || void 0,
|
|
1794
|
+
model: userAgent || void 0
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1797
|
+
if (connection) {
|
|
1798
|
+
payload.network = {
|
|
1799
|
+
connectionType: connection.type || void 0,
|
|
1800
|
+
effectiveType: connection.effectiveType || void 0,
|
|
1801
|
+
downlinkMbps: typeof connection.downlink === "number" ? connection.downlink : void 0,
|
|
1802
|
+
bandwidthMbps: typeof connection.downlink === "number" ? connection.downlink : void 0,
|
|
1803
|
+
rtt: typeof connection.rtt === "number" ? connection.rtt : void 0,
|
|
1804
|
+
latencyRtt: typeof connection.rtt === "number" ? connection.rtt : void 0,
|
|
1805
|
+
saveData: typeof connection.saveData === "boolean" ? connection.saveData : void 0
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
return payload;
|
|
1809
|
+
}
|
|
1810
|
+
applyAdDeliveryProfile(profile) {
|
|
1811
|
+
if (!profile?.mode) return;
|
|
1812
|
+
const previousMode = this.state.adDeliveryProfile?.mode ?? null;
|
|
1813
|
+
this.state.adDeliveryProfile = profile;
|
|
1814
|
+
if (profile.mode === "vast_fallback" && this.state.programmaticPlaying) {
|
|
1815
|
+
this.programmaticPlayer.stop({ silent: true });
|
|
1816
|
+
this.state.programmaticPlaying = false;
|
|
1817
|
+
}
|
|
1818
|
+
if (previousMode !== profile.mode) {
|
|
1819
|
+
this.emitStateChanged();
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
runHeartbeatCommands(commands) {
|
|
1823
|
+
for (const command of commands) {
|
|
1824
|
+
const name = String(command?.command || "").toLowerCase();
|
|
1825
|
+
if (name === "refresh_ads") {
|
|
1826
|
+
this.refresh().catch(() => {
|
|
1827
|
+
});
|
|
1828
|
+
} else if (name === "restart") {
|
|
1829
|
+
if (typeof window !== "undefined" && typeof window.location?.reload === "function") {
|
|
1830
|
+
window.location.reload();
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
this.refresh().catch(() => {
|
|
1834
|
+
});
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1683
1838
|
playNextAd() {
|
|
1839
|
+
if (this.state.adDeliveryProfile?.mode === "vast_fallback" && this.state.ads.length > 0) {
|
|
1840
|
+
this.playDirect();
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1684
1843
|
const mode = this.state.waterfallMode;
|
|
1685
1844
|
if (mode === "programmatic_only" || mode === "programmatic_then_direct") {
|
|
1686
1845
|
this.playProgrammatic();
|
|
@@ -1695,13 +1854,20 @@ var _TrillboardsAds = class _TrillboardsAds {
|
|
|
1695
1854
|
this.programmaticPlayer.play(
|
|
1696
1855
|
() => {
|
|
1697
1856
|
this.state.programmaticPlaying = false;
|
|
1857
|
+
this.resetProgrammaticBackoff();
|
|
1698
1858
|
this.emitStateChanged();
|
|
1699
1859
|
this.scheduleProgrammaticRetry();
|
|
1700
1860
|
},
|
|
1701
1861
|
(error) => {
|
|
1702
1862
|
this.state.programmaticPlaying = false;
|
|
1703
1863
|
this.state.programmaticLastError = error;
|
|
1704
|
-
|
|
1864
|
+
const normalizedError = String(error || "").toLowerCase();
|
|
1865
|
+
const isNoFillLike = normalizedError.includes("no fill") || normalizedError.includes("no ads vast response") || normalizedError.includes("waterfall_exhausted") || normalizedError === "throttled" || normalizedError === "busy";
|
|
1866
|
+
if (isNoFillLike) {
|
|
1867
|
+
this.state.programmaticRetryCount = 0;
|
|
1868
|
+
} else {
|
|
1869
|
+
this.state.programmaticRetryCount++;
|
|
1870
|
+
}
|
|
1705
1871
|
this.emitStateChanged();
|
|
1706
1872
|
if (this.state.waterfallMode === "programmatic_then_direct" && this.state.ads.length > 0) {
|
|
1707
1873
|
this.playDirect();
|
|
@@ -1823,12 +1989,12 @@ var TrillboardsError = class extends Error {
|
|
|
1823
1989
|
var TrillboardsAuthenticationError = class extends TrillboardsError {
|
|
1824
1990
|
constructor(message) {
|
|
1825
1991
|
super(
|
|
1826
|
-
message ?? "Invalid API key. Check your key at https://trillboards.com/developers",
|
|
1992
|
+
message ?? "Invalid API key. Check your key at https://trillboards.com/support/developers",
|
|
1827
1993
|
{
|
|
1828
1994
|
type: "authentication_error",
|
|
1829
1995
|
code: "invalid_api_key",
|
|
1830
1996
|
statusCode: 401,
|
|
1831
|
-
help: "https://trillboards.com/developers"
|
|
1997
|
+
help: "https://trillboards.com/support/developers"
|
|
1832
1998
|
}
|
|
1833
1999
|
);
|
|
1834
2000
|
this.name = "TrillboardsAuthenticationError";
|