Skip to content

Commit eedd995

Browse files
committed
AG-15846 Added missing UI and customUi components to google-ima3-dai and convert redirect resources with .ts extension to .js
Squashed commit of the following: commit b1564ee Author: slvvko <v.leleka@adguard.com> Date: Tue Apr 28 09:03:39 2026 -0400 add tests for convertTsFileNameToJs commit d83c876 Author: Adam Wróblewski <adam@adguard.com> Date: Tue Apr 28 13:44:18 2026 +0200 Remove test commit e348060 Author: Adam Wróblewski <adam@adguard.com> Date: Tue Apr 28 13:32:51 2026 +0200 Fix changelog commit ee91e05 Author: Adam Wróblewski <adam@adguard.com> Date: Tue Apr 28 13:24:07 2026 +0200 Attempt to fix build commit 3a03df9 Author: Adam Wróblewski <adam@adguard.com> Date: Tue Apr 28 12:47:35 2026 +0200 Convert TypeScript redirect resources to JavaScript files commit a54f248 Author: Adam Wróblewski <adam@adguard.com> Date: Tue Apr 28 12:02:49 2026 +0200 Add missing `UI` and `customUi` components to `google-ima3-dai`
1 parent fafea5d commit eedd995

6 files changed

Lines changed: 346 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
1212

1313
## [Unreleased]
1414

15+
### Changed
16+
17+
- Redirect resources with a `.ts` extension are now converted to `.js`.
18+
1519
### Fixed
1620

21+
- Added the missing `google.ima.dai.api.ui`, `google.ima.dai.api.customUi` surfaces and the
22+
default `StreamRequest.ui` container in the `google-ima3-dai` redirect.
1723
- Added the missing `google.ima.dai.api.StreamRequest.StreamFormat` object to the `google-ima3-dai` redirect.
1824

1925
[Unreleased]: https://github.com/AdguardTeam/Scriptlets/compare/v2.4.2...HEAD
@@ -32,7 +38,7 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
3238

3339
- `google-ima3-dai` redirect resource and scriptlet [#239].
3440

35-
## Changed
41+
### Changed
3642

3743
- Updated [@adguard/agtree] to 4.1.1
3844

scripts/build-redirects.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { fileURLToPath } from 'node:url';
1010
import * as redirectsNamesLists from '../src/redirects/redirects-names-list';
1111
import { version } from '../package.json';
1212
import { rollupStandard } from './rollup-runners';
13-
import { writeFile, getDataFromFiles } from './helpers';
13+
import { writeFile, getDataFromFiles, convertTsFileNameToJs } from './helpers';
1414
import {
1515
redirectsFilenames,
1616
REDIRECTS_SRC_RELATIVE_DIR_PATH,
@@ -210,7 +210,7 @@ const getJsRedirects = async (options = {}) => {
210210
aliases: complement.aliases,
211211
contentType: 'application/javascript',
212212
content: complement.redirect,
213-
file: fileName,
213+
file: convertTsFileNameToJs(fileName),
214214
};
215215
}
216216
throw new Error(`Couldn't find source for non-static redirect: ${fileName}`);

scripts/helpers.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,22 @@ const generateHtmlTestFilename = (type, name) => {
150150
return `${type}-${name}.html`;
151151
};
152152

153+
/**
154+
* Converts a redirect source filename from ".ts" to ".js" extension.
155+
* Filenames that already use ".js" or any other extension are returned unchanged.
156+
*
157+
* @param {string} fileName redirect source filename
158+
* @returns {string} filename with ".ts" replaced by ".js"
159+
*/
160+
const convertTsFileNameToJs = (fileName) => fileName.replace(/\.ts$/, '.js');
161+
153162
export {
154163
writeFile,
155164
getFilesList,
156165
getDataFromFiles,
157166
runTasks,
158167
generateHtmlTestFilename,
168+
convertTsFileNameToJs,
159169
SCRIPTLET_TYPE,
160170
TRUSTED_SCRIPTLET_TYPE,
161171
REDIRECT_TYPE,

src/redirects/google-ima3-dai.ts

Lines changed: 245 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ interface StreamRequestInstance extends PlainRecord {
3838
networkCode: string | null;
3939
omidAccessModeRules: PlainRecord | null;
4040
streamActivityMonitorId: string | null;
41+
ui: StreamRequestUiInstance;
42+
}
43+
44+
interface StreamRequestUiInstance extends PlainRecord {
45+
custom: PlainRecord | null;
4146
}
4247

4348
interface LiveStreamRequestInstance extends StreamRequestInstance {
@@ -102,6 +107,46 @@ interface UiSettingsInstance {
102107
setLocale(locale: string): void;
103108
}
104109

110+
interface UiImageInstance {
111+
altText: string;
112+
height: number;
113+
url: string;
114+
width: number;
115+
}
116+
117+
interface UiDataInstance extends PlainRecord {
118+
altText?: string;
119+
clickable: boolean;
120+
clickUrl?: string;
121+
imageUrl?: string;
122+
imageVariants?: UiImageInstance[];
123+
required: boolean;
124+
text?: string;
125+
}
126+
127+
interface UiOptionsInstance {
128+
aboutThisAdSupport: boolean;
129+
clickThroughNavigation: string;
130+
skippableSupport: boolean;
131+
}
132+
133+
interface CustomUiOptionsInstance {
134+
aboutThisAdSupport: boolean;
135+
skippableSupport: boolean;
136+
}
137+
138+
interface UiDelegateInstance extends PlainRecord {
139+
getConfig?: () => Map<string, UiDataInstance>;
140+
onClick?: (uiKey: string, eventData?: unknown) => void;
141+
setVisibleElements?: (uiElements: unknown) => void;
142+
}
143+
144+
interface UiInstance {
145+
getConfig(): Map<string, UiDataInstance>;
146+
onClick(uiKey: string, eventData?: unknown): void;
147+
setVisibleElements(uiElements: unknown): void;
148+
}
149+
105150
interface DaiSdkSettingsInstance {
106151
getFeatureFlags(): PlainRecord;
107152
setFeatureFlags(featureFlags: PlainRecord): void;
@@ -189,6 +234,39 @@ type UiSettingsConstructor = {
189234
prototype: UiSettingsInstance;
190235
};
191236

237+
type UiImageConstructor = {
238+
new (uiImage?: PlainRecord | null): UiImageInstance;
239+
prototype: UiImageInstance;
240+
};
241+
242+
type UiDataConstructor = {
243+
new (uiData?: PlainRecord | null): UiDataInstance;
244+
prototype: UiDataInstance;
245+
UiImage: UiImageConstructor;
246+
};
247+
248+
type UiOptionsConstructor = {
249+
new (uiOptions?: PlainRecord | null): UiOptionsInstance;
250+
prototype: UiOptionsInstance;
251+
ClickThroughNavigation: {
252+
EXTERNAL: 'external';
253+
NOT_SUPPORTED: 'notSupported';
254+
};
255+
};
256+
257+
type CustomUiOptionsConstructor = {
258+
new (uiOptions?: PlainRecord | null): CustomUiOptionsInstance;
259+
prototype: CustomUiOptionsInstance;
260+
};
261+
262+
type UiConstructor = {
263+
new (uiDelegate?: PlainRecord | null): UiInstance;
264+
prototype: UiInstance;
265+
UiData: UiDataConstructor;
266+
UiKey: PlainRecord;
267+
UiOptions: UiOptionsConstructor;
268+
};
269+
192270
type DaiSdkSettingsConstructor = {
193271
new (): DaiSdkSettingsInstance;
194272
prototype: DaiSdkSettingsInstance;
@@ -279,6 +357,22 @@ export function GoogleIma3Dai(source: Source) {
279357
return typeof value === 'string' && value.length > 0 ? value : null;
280358
};
281359

360+
/**
361+
* Normalizes the stream request UI container.
362+
*
363+
* @param value - The candidate UI container to normalize.
364+
*/
365+
const normalizeStreamRequestUi = (value: unknown): StreamRequestUiInstance => {
366+
if (!isRecord(value)) {
367+
return { custom: null };
368+
}
369+
370+
const normalizedUi = Object.assign({ custom: null }, value);
371+
normalizedUi.custom = isRecord(normalizedUi.custom) ? normalizedUi.custom : null;
372+
373+
return normalizedUi as StreamRequestUiInstance;
374+
};
375+
282376
/**
283377
* Initializes the listener registry for an event handler instance.
284378
*
@@ -331,11 +425,13 @@ export function GoogleIma3Dai(source: Source) {
331425
instance.networkCode = null;
332426
instance.omidAccessModeRules = null;
333427
instance.streamActivityMonitorId = null;
428+
instance.ui = { custom: null };
334429

335430
if (isRecord(streamRequest)) {
336431
Object.assign(instance, streamRequest);
337432
}
338433
instance.adTagParameters = toStringRecord(instance.adTagParameters);
434+
instance.ui = normalizeStreamRequestUi(instance.ui);
339435

340436
if (typeof instance.format !== 'string' || instance.format.length === 0) {
341437
instance.format = 'hls';
@@ -598,7 +694,7 @@ export function GoogleIma3Dai(source: Source) {
598694
* Creates a UI settings container.
599695
*/
600696
const UiSettings = function (this: UiSettingsInstance) {
601-
this.locale = '';
697+
this.locale = 'en';
602698
} as unknown as UiSettingsConstructor;
603699
/**
604700
* Returns the configured locale.
@@ -612,8 +708,151 @@ export function GoogleIma3Dai(source: Source) {
612708
* @param locale - The locale string to store.
613709
*/
614710
UiSettings.prototype.setLocale = function (this: UiSettingsInstance, locale: string): void {
615-
this.locale = locale;
711+
if (typeof locale === 'string' && locale.length > 0) {
712+
this.locale = locale;
713+
}
714+
};
715+
716+
const uiKeys = {
717+
ABOUT_THIS_AD_FALLBACK_IMAGE: 'aboutThisAdFallbackImage',
718+
ABOUT_THIS_AD_ICON: 'aboutThisAdIcon',
719+
AD_TITLE: 'adTitle',
720+
ATTRIBUTION: 'attribution',
721+
AUTHOR_ICON: 'authorIcon',
722+
AUTHOR_TITLE: 'authorTitle',
723+
CALL_TO_ACTION: 'callToAction',
724+
PRE_SKIP: 'preSkip',
725+
SKIP_BUTTON: 'skipButton',
726+
VIDEO_OVERLAY: 'videoOverlay',
727+
} as const;
728+
729+
const clickThroughNavigation = {
730+
EXTERNAL: 'external',
731+
NOT_SUPPORTED: 'notSupported',
732+
} as const;
733+
734+
/**
735+
* Creates a UI image description.
736+
*
737+
* @param uiImage - Optional image fields to copy onto the instance.
738+
*/
739+
const UiImage = function (this: UiImageInstance, uiImage?: PlainRecord | null) {
740+
this.altText = '';
741+
this.height = 0;
742+
this.url = '';
743+
this.width = 0;
744+
745+
if (isRecord(uiImage)) {
746+
Object.assign(this, uiImage);
747+
}
748+
749+
this.altText = getStringValue(this.altText) || '';
750+
this.height = typeof this.height === 'number' ? this.height : 0;
751+
this.url = getStringValue(this.url) || '';
752+
this.width = typeof this.width === 'number' ? this.width : 0;
753+
} as unknown as UiImageConstructor;
754+
755+
/**
756+
* Creates a UI element configuration record.
757+
*
758+
* @param uiData - Optional UI data fields to copy onto the instance.
759+
*/
760+
const UiData = function (this: UiDataInstance, uiData?: PlainRecord | null) {
761+
this.clickable = false;
762+
this.required = false;
763+
764+
if (isRecord(uiData)) {
765+
Object.assign(this, uiData);
766+
}
767+
768+
this.clickable = Boolean(this.clickable);
769+
this.required = Boolean(this.required);
770+
771+
if (Array.isArray(this.imageVariants)) {
772+
const imageVariants: UiImageInstance[] = [];
773+
774+
for (let imageVariantIndex = 0; imageVariantIndex < this.imageVariants.length; imageVariantIndex += 1) {
775+
imageVariants.push(new UiImage(this.imageVariants[imageVariantIndex] as unknown as PlainRecord | null));
776+
}
777+
778+
this.imageVariants = imageVariants;
779+
}
780+
} as unknown as UiDataConstructor;
781+
UiData.UiImage = UiImage;
782+
783+
/**
784+
* Creates public DAI UI options.
785+
*
786+
* @param uiOptions - Optional UI option fields to copy onto the instance.
787+
*/
788+
const UiOptions = function (this: UiOptionsInstance, uiOptions?: PlainRecord | null) {
789+
this.aboutThisAdSupport = false;
790+
this.clickThroughNavigation = clickThroughNavigation.NOT_SUPPORTED;
791+
this.skippableSupport = false;
792+
793+
if (isRecord(uiOptions)) {
794+
Object.assign(this, uiOptions);
795+
}
796+
797+
this.aboutThisAdSupport = Boolean(this.aboutThisAdSupport);
798+
this.clickThroughNavigation = this.clickThroughNavigation === clickThroughNavigation.EXTERNAL
799+
? clickThroughNavigation.EXTERNAL
800+
: clickThroughNavigation.NOT_SUPPORTED;
801+
this.skippableSupport = Boolean(this.skippableSupport);
802+
} as unknown as UiOptionsConstructor;
803+
UiOptions.ClickThroughNavigation = clickThroughNavigation;
804+
805+
/**
806+
* Creates custom UI options stored on stream requests.
807+
*
808+
* @param uiOptions - Optional UI option fields to copy onto the instance.
809+
*/
810+
const CustomUiOptions = function (this: CustomUiOptionsInstance, uiOptions?: PlainRecord | null) {
811+
this.aboutThisAdSupport = false;
812+
this.skippableSupport = false;
813+
814+
if (isRecord(uiOptions)) {
815+
Object.assign(this, uiOptions);
816+
}
817+
818+
this.aboutThisAdSupport = Boolean(this.aboutThisAdSupport);
819+
this.skippableSupport = Boolean(this.skippableSupport);
820+
} as unknown as CustomUiOptionsConstructor;
821+
822+
const uiDelegates = new WeakMap<UiInstance, UiDelegateInstance>();
823+
/**
824+
* Creates the public `google.ima.dai.api.ui` wrapper.
825+
*
826+
* @param uiDelegate - Optional delegate used to back the wrapper methods.
827+
*/
828+
const Ui = function (this: UiInstance, uiDelegate?: PlainRecord | null) {
829+
uiDelegates.set(this, isRecord(uiDelegate) ? uiDelegate as UiDelegateInstance : {});
830+
} as unknown as UiConstructor;
831+
Ui.prototype.onClick = function (this: UiInstance, uiKey: string, eventData?: unknown): void {
832+
const uiDelegate = uiDelegates.get(this);
833+
if (uiDelegate && typeof uiDelegate.onClick === 'function') {
834+
uiDelegate.onClick(uiKey, eventData);
835+
}
836+
};
837+
Ui.prototype.setVisibleElements = function (this: UiInstance, uiElements: unknown): void {
838+
const uiDelegate = uiDelegates.get(this);
839+
if (uiDelegate && typeof uiDelegate.setVisibleElements === 'function') {
840+
uiDelegate.setVisibleElements(uiElements);
841+
}
842+
};
843+
Ui.prototype.getConfig = function (this: UiInstance): Map<string, UiDataInstance> {
844+
const uiDelegate = uiDelegates.get(this);
845+
if (!uiDelegate || typeof uiDelegate.getConfig !== 'function') {
846+
return new Map();
847+
}
848+
849+
const uiConfig = uiDelegate.getConfig();
850+
851+
return uiConfig instanceof Map ? uiConfig : new Map();
616852
};
853+
Ui.UiData = UiData;
854+
Ui.UiKey = uiKeys;
855+
Ui.UiOptions = UiOptions;
617856

618857
const daiSdkFeatureFlagsStorage = new WeakMap<DaiSdkSettingsInstance, PlainRecord>();
619858
/**
@@ -1429,13 +1668,17 @@ export function GoogleIma3Dai(source: Source) {
14291668
} as unknown as StreamManagerInstance['streamTimeForContentTime'];
14301669

14311670
const api: PlainRecord = {
1671+
customUi: {
1672+
UiOptions: CustomUiOptions,
1673+
},
14321674
DaiSdkSettings: new DaiSdkSettingsContainer(),
14331675
LiveStreamRequest,
14341676
PodStreamRequest,
14351677
StreamData,
14361678
StreamEvent,
14371679
StreamManager,
14381680
StreamRequest,
1681+
ui: Ui,
14391682
UiSettings,
14401683
VideoStitcherLiveStreamRequest,
14411684
VideoStitcherVodStreamRequest,

tests/api/redirects-build.spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import { convertTsFileNameToJs } from '../../scripts/helpers';
4+
5+
describe('redirect build', () => {
6+
it('converts TypeScript redirect source filename to .js extension', () => {
7+
expect(convertTsFileNameToJs('google-ima3-dai.ts')).toBe('google-ima3-dai.js');
8+
});
9+
10+
it('leaves JavaScript redirect filenames unchanged', () => {
11+
expect(convertTsFileNameToJs('google-ima3.js')).toBe('google-ima3.js');
12+
});
13+
14+
it('leaves filenames without a recognized extension unchanged', () => {
15+
expect(convertTsFileNameToJs('nooptext.txt')).toBe('nooptext.txt');
16+
});
17+
});

0 commit comments

Comments
 (0)