2626** What:** Delete ` creative-opportunities.toml ` . Move ` [[slot]] ` arrays into ` trusted-server.toml ` as ` [[creative_opportunities.slot]] ` . Wire the ` vec_from_seq_or_map ` deserializer so env var JSON blobs also work. Remove the ` SLOTS_FILE ` static and ` include_str! ` from ` main.rs ` . Update ` build.rs ` to validate slot IDs from settings instead of a separate file.
2727
2828** Files:**
29+
2930- Modify: ` crates/trusted-server-core/src/creative_opportunities.rs `
3031- Modify: ` crates/trusted-server-core/src/settings.rs `
3132- Modify: ` crates/trusted-server-adapter-fastly/src/main.rs `
@@ -178,6 +179,7 @@ Note: `build.rs` already pulls in `src/creative_opportunities.rs` as a module
178179- [ ] ** Step 6: Update ` main.rs ` — remove ` SLOTS_FILE ` static**
179180
180181Remove:
182+
181183``` rust
182184const CREATIVE_OPPORTUNITIES_TOML : & str = include_str! (" ../../../creative-opportunities.toml" );
183185static SLOTS_FILE : std :: sync :: LazyLock <... > = ... ;
@@ -275,17 +277,18 @@ git commit -m "Move slot templates from creative-opportunities.toml into trusted
275277
276278** Rename table:**
277279
278- | Old global | New property | Notes |
279- | ---| ---| ---|
280- | ` window.__ts_ad_slots ` | ` window._ts.adSlots ` | Array, set at head-open |
281- | ` window.__ts_bids ` | ` window._ts.bids ` | Object, set before ` </body> ` |
282- | ` window.__tsAdInit ` | ` window._ts.adInit ` | Function |
283- | ` window.__tsPrevGptSlots ` | ` window._ts.prevGptSlots ` | Array |
284- | ` window.__tsServicesEnabled ` | ` window._ts.servicesEnabled ` | Boolean |
285- | ` window.__tsDivToSlotId ` | ` window._ts.divToSlotId ` | Object |
286- | ` window.__tsSpaHookInstalled ` | ` window._ts.spaHookInstalled ` | Boolean |
280+ | Old global | New property | Notes |
281+ | ----------------------------- | ----------------------------- | ---------------------------- |
282+ | ` window.__ts_ad_slots ` | ` window._ts.adSlots ` | Array, set at head-open |
283+ | ` window.__ts_bids ` | ` window._ts.bids ` | Object, set before ` </body> ` |
284+ | ` window.__tsAdInit ` | ` window._ts.adInit ` | Function |
285+ | ` window.__tsPrevGptSlots ` | ` window._ts.prevGptSlots ` | Array |
286+ | ` window.__tsServicesEnabled ` | ` window._ts.servicesEnabled ` | Boolean |
287+ | ` window.__tsDivToSlotId ` | ` window._ts.divToSlotId ` | Object |
288+ | ` window.__tsSpaHookInstalled ` | ` window._ts.spaHookInstalled ` | Boolean |
287289
288290** Files:**
291+
289292- Modify: ` crates/trusted-server-core/src/publisher.rs `
290293- Modify: ` crates/trusted-server-core/src/integrations/gpt_bootstrap.js `
291294- Modify: ` crates/js/lib/src/integrations/gpt/index.ts `
@@ -331,61 +334,65 @@ Update any test assertions in `publisher.rs` that check for the old global names
331334Replace all ` window.__ts* ` references. The bootstrap IIFE runs before the TS bundle, so it must initialise ` window._ts ` if absent:
332335
333336``` js
334- (function () {
335- if (typeof window === " undefined" ) return ;
337+ ; (function () {
338+ if (typeof window === ' undefined' ) return
336339 // Initialise namespace; adInit guard prevents double-install.
337- var ts = (window ._ts = window ._ts || {});
338- if (ts .adInit ) return ;
340+ var ts = (window ._ts = window ._ts || {})
341+ if (ts .adInit ) return
339342
340343 ts .adInit = function () {
341- var slots = ts .adSlots || [];
342- var bids = ts .bids || {};
343- var divToSlotId = {};
344+ var slots = ts .adSlots || []
345+ var bids = ts .bids || {}
346+ var divToSlotId = {}
344347 googletag .cmd .push (function () {
345- var newSlots = [];
348+ var newSlots = []
346349 slots .forEach (function (slot ) {
347- var s = googletag .defineSlot (slot .gam_unit_path , slot .formats , slot .div_id );
348- if (! s) return ;
349- s .addService (googletag .pubads ());
350+ var s = googletag .defineSlot (
351+ slot .gam_unit_path ,
352+ slot .formats ,
353+ slot .div_id
354+ )
355+ if (! s) return
356+ s .addService (googletag .pubads ())
350357 Object .entries (slot .targeting || {}).forEach (function (e ) {
351- s .setTargeting (e[0 ], e[1 ]);
352- });
353- var b = bids[slot .id ] || {};
354- [ " hb_pb" , " hb_bidder" , " hb_adid" ].forEach (function (k ) {
355- if (b[k]) s .setTargeting (k, b[k]);
356- });
357- s .setTargeting (" ts_initial" , " 1 " );
358- divToSlotId[slot .div_id ] = slot .id ;
359- newSlots .push (s);
360- });
361- ts .prevGptSlots = newSlots;
362- ts .divToSlotId = divToSlotId;
358+ s .setTargeting (e[0 ], e[1 ])
359+ })
360+ var b = bids[slot .id ] || {}
361+ ;[ ' hb_pb' , ' hb_bidder' , ' hb_adid' ].forEach (function (k ) {
362+ if (b[k]) s .setTargeting (k, b[k])
363+ })
364+ s .setTargeting (' ts_initial' , ' 1 ' )
365+ divToSlotId[slot .div_id ] = slot .id
366+ newSlots .push (s)
367+ })
368+ ts .prevGptSlots = newSlots
369+ ts .divToSlotId = divToSlotId
363370 if (! ts .servicesEnabled ) {
364- googletag .pubads ().enableSingleRequest ();
365- googletag .enableServices ();
366- ts .servicesEnabled = true ;
367- googletag .pubads ().addEventListener (" slotRenderEnded" , function (ev ) {
368- var divId = ev .slot .getSlotElementId ();
369- var slotId = (ts .divToSlotId || {})[divId];
370- if (! slotId) return ;
371- var b = (ts .bids || {})[slotId] || {};
371+ googletag .pubads ().enableSingleRequest ()
372+ googletag .enableServices ()
373+ ts .servicesEnabled = true
374+ googletag .pubads ().addEventListener (' slotRenderEnded' , function (ev ) {
375+ var divId = ev .slot .getSlotElementId ()
376+ var slotId = (ts .divToSlotId || {})[divId]
377+ if (! slotId) return
378+ var b = (ts .bids || {})[slotId] || {}
372379 var ourBidWon =
373380 ! ev .isEmpty &&
374381 (b .hb_adid
375- ? ev .slot .getTargeting (" hb_adid" )[0 ] === b .hb_adid
376- : !! b .hb_bidder );
382+ ? ev .slot .getTargeting (' hb_adid' )[0 ] === b .hb_adid
383+ : !! b .hb_bidder )
377384 if (ourBidWon) {
378- if (b .nurl ) navigator .sendBeacon (b .nurl );
379- if (b .burl ) navigator .sendBeacon (b .burl );
385+ if (b .nurl ) navigator .sendBeacon (b .nurl )
386+ if (b .burl ) navigator .sendBeacon (b .burl )
380387 }
381- });
388+ })
382389 }
383390 if (newSlots .length > 0 ) {
384- googletag .pubads ().refresh (newSlots);
391+ googletag .pubads ().refresh (newSlots)
385392 }
386- });
387- };
388- })();
393+ })
394+ }
395+ })()
389396```
390397
391398- [ ] ** Step 3: Update ` index.ts ` — rename ` TsWindow ` type**
@@ -394,18 +401,18 @@ Replace the `TsWindow` interface:
394401
395402``` typescript
396403type TsNamespace = {
397- adSlots? : TsAdSlot [];
398- bids? : Record <string , TsBidData >;
399- adInit? : () => void ;
400- prevGptSlots? : GoogleTagSlot [];
401- servicesEnabled? : boolean ;
402- divToSlotId? : Record <string , string >;
403- spaHookInstalled? : boolean ;
404- };
404+ adSlots? : TsAdSlot []
405+ bids? : Record <string , TsBidData >
406+ adInit? : () => void
407+ prevGptSlots? : GoogleTagSlot []
408+ servicesEnabled? : boolean
409+ divToSlotId? : Record <string , string >
410+ spaHookInstalled? : boolean
411+ }
405412
406413type TsWindow = Window & {
407- _ts? : TsNamespace ;
408- };
414+ _ts? : TsNamespace
415+ }
409416` ` `
410417
411418- [ ] **Step 4: Update ` installTsAdInit ` in ` index .ts ` **
@@ -414,64 +421,73 @@ Change every `w.__ts*` access to `w._ts.*`. Initialise `w._ts` at function entry
414421
415422` ` ` typescript
416423export function installTsAdInit(): void {
417- const w = window as TsWindow ;
418- const ts = (w ._ts = w ._ts ?? {});
424+ const w = window as TsWindow
425+ const ts = (w ._ts = w ._ts ?? {})
419426 ts .adInit = function () {
420- const slots = ts .adSlots ?? [];
421- const bids = ts .bids ?? {};
422- const g = (window as GptWindow ).googletag ;
423- if (! g ) return ;
427+ const slots = ts .adSlots ?? []
428+ const bids = ts .bids ?? {}
429+ const g = (window as GptWindow ).googletag
430+ if (! g ) return
424431
425432 g .cmd ?.push (() => {
426433 if (ts .prevGptSlots && ts .prevGptSlots .length > 0 ) {
427- g .destroySlots ?.(ts .prevGptSlots );
428- ts .prevGptSlots = [];
434+ g .destroySlots ?.(ts .prevGptSlots )
435+ ts .prevGptSlots = []
429436 }
430- const newSlots: GoogleTagSlot [] = [];
431- const divToSlotId: Record <string , string > = {};
437+ const newSlots: GoogleTagSlot [] = []
438+ const divToSlotId: Record <string , string > = {}
432439
433440 slots .forEach ((slot ) => {
434- const gptSlot = g .defineSlot ?.(slot .gam_unit_path , slot .formats as Array <number | number []>, slot .div_id );
435- if (! gptSlot ) return ;
436- gptSlot .addService (g .pubads !());
437- Object .entries (slot .targeting ?? {}).forEach (([k , v ]) => gptSlot .setTargeting (k , v ));
438- const bid = bids [slot .id ] ?? {};
439- ([' hb_pb' , ' hb_bidder' , ' hb_adid' ] as const ).forEach ((key ) => {
440- if (bid [key ]) gptSlot .setTargeting (key , bid [key ]! );
441- });
442- gptSlot .setTargeting (' ts_initial' , ' 1' );
443- divToSlotId [slot .div_id ] = slot .id ;
444- newSlots .push (gptSlot );
445- });
446-
447- ts .prevGptSlots = newSlots ;
448- ts .divToSlotId = divToSlotId ;
441+ const gptSlot = g .defineSlot ?.(
442+ slot .gam_unit_path ,
443+ slot .formats as Array <number | number []>,
444+ slot .div_id
445+ )
446+ if (! gptSlot ) return
447+ gptSlot .addService (g .pubads !())
448+ Object .entries (slot .targeting ?? {}).forEach (([k , v ]) =>
449+ gptSlot .setTargeting (k , v )
450+ )
451+ const bid = bids [slot .id ] ?? {}
452+ ;([' hb_pb' , ' hb_bidder' , ' hb_adid' ] as const ).forEach ((key ) => {
453+ if (bid [key ]) gptSlot .setTargeting (key , bid [key ]! )
454+ })
455+ gptSlot .setTargeting (' ts_initial' , ' 1' )
456+ divToSlotId [slot .div_id ] = slot .id
457+ newSlots .push (gptSlot )
458+ })
459+
460+ ts .prevGptSlots = newSlots
461+ ts .divToSlotId = divToSlotId
449462
450463 if (! ts .servicesEnabled ) {
451- g .pubads !().enableSingleRequest ();
452- g .enableServices ?.();
453- ts .servicesEnabled = true ;
454- g .pubads !().addEventListener ?.(' slotRenderEnded' , (event : SlotRenderEndedEvent ) => {
455- const divId: string = event .slot ?.getSlotElementId ?.() ?? ' ' ;
456- const slotId = (ts .divToSlotId ?? {})[divId ];
457- if (! slotId ) return ;
458- const bid = (ts .bids ?? {})[slotId ] ?? {};
459- const ourBidWon =
460- ! event .isEmpty &&
461- (bid .hb_adid
462- ? event .slot ?.getTargeting ?.(' hb_adid' )?.[0 ] === bid .hb_adid
463- : !! bid .hb_bidder );
464- if (ourBidWon ) {
465- if (bid .nurl ) navigator .sendBeacon (bid .nurl );
466- if (bid .burl ) navigator .sendBeacon (bid .burl );
464+ g .pubads !().enableSingleRequest ()
465+ g .enableServices ?.()
466+ ts .servicesEnabled = true
467+ g .pubads !().addEventListener ?.(
468+ ' slotRenderEnded' ,
469+ (event : SlotRenderEndedEvent ) => {
470+ const divId: string = event .slot ?.getSlotElementId ?.() ?? ' '
471+ const slotId = (ts .divToSlotId ?? {})[divId ]
472+ if (! slotId ) return
473+ const bid = (ts .bids ?? {})[slotId ] ?? {}
474+ const ourBidWon =
475+ ! event .isEmpty &&
476+ (bid .hb_adid
477+ ? event .slot ?.getTargeting ?.(' hb_adid' )?.[0 ] === bid .hb_adid
478+ : !! bid .hb_bidder )
479+ if (ourBidWon ) {
480+ if (bid .nurl ) navigator .sendBeacon (bid .nurl )
481+ if (bid .burl ) navigator .sendBeacon (bid .burl )
482+ }
467483 }
468- });
484+ )
469485 }
470486 if (newSlots .length > 0 ) {
471- g .pubads !().refresh (newSlots );
487+ g .pubads !().refresh (newSlots )
472488 }
473- });
474- };
489+ })
490+ }
475491}
476492```
477493
@@ -481,10 +497,10 @@ Replace `__tsSpaHookInstalled` and `__ts_ad_slots`/`__ts_bids` reads:
481497
482498``` typescript
483499export function installSpaHook(): void {
484- const win = window as TsWindow ;
485- const ts = (win ._ts = win ._ts ?? {});
486- if (ts .spaHookInstalled ) return ;
487- ts .spaHookInstalled = true ;
500+ const win = window as TsWindow
501+ const ts = (win ._ts = win ._ts ?? {})
502+ if (ts .spaHookInstalled ) return
503+ ts .spaHookInstalled = true
488504 // ... rest of SPA hook logic uses ts.adSlots, ts.bids, ts.adInit
489505}
490506```
@@ -538,6 +554,7 @@ git commit -m "Namespace window globals under window._ts"
538554** What:** Two small TypeScript/JS cleanups. ` TsAdSlot.formats ` should be typed as ` Array<[number, number]> ` (tuple, not array-of-array) to match GPT's actual input. The string ` 'ts_initial' ` is hardcoded in both ` gpt_bootstrap.js ` and ` index.ts ` — extract as a named constant in ` index.ts ` (no JS equivalent needed since the bootstrap is vanilla JS).
539555
540556** Files:**
557+
541558- Modify: ` crates/js/lib/src/integrations/gpt/index.ts `
542559- Modify: ` crates/trusted-server-core/src/integrations/gpt_bootstrap.js ` (comment only — JS can't share TS constants)
543560
@@ -576,7 +593,7 @@ slot.formats
576593Near the top of ` index.ts ` , add:
577594
578595``` typescript
579- const TS_INITIAL_TARGETING_KEY = ' ts_initial' ;
596+ const TS_INITIAL_TARGETING_KEY = ' ts_initial'
580597```
581598
582599Replace both occurrences of ` 'ts_initial' ` in ` installTsAdInit ` with ` TS_INITIAL_TARGETING_KEY ` .
@@ -585,7 +602,7 @@ Add a comment in `gpt_bootstrap.js` where `'ts_initial'` appears:
585602
586603``` js
587604// Keep in sync with TS_INITIAL_TARGETING_KEY in index.ts
588- s .setTargeting (" ts_initial" , " 1 " );
605+ s .setTargeting (' ts_initial' , ' 1 ' )
589606```
590607
591608- [ ] ** Step 3: Run JS tests and format**
0 commit comments