Skip to content

Commit

Permalink
Update prebid helpers to return a partial ortb2 object rather than us…
Browse files Browse the repository at this point in the history
…er.data only (#44)

* Fix various package flags with vulnerabilities

* BREAKING: Update prebid helpers to return a partial ortb2 object to merge using mergeConfig
  • Loading branch information
zapo authored Jan 6, 2023
1 parent fc63fcf commit a40a8b2
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 121 deletions.
16 changes: 4 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,11 +425,11 @@ A working example of both targeting and event witnessing is available in the dem

## Integrating Prebid

The Optable Web SDK can fetch targeting data from a DCN and prepare an audience taxonomy object similar to the one described in [the prebid.js first party data documentation](https://docs.prebid.org/features/firstPartyData.html#segments-and-taxonomy). The `prebidUserDataFromCache()` function returns the object from the targeting data stored by `targeting()` API calls in `LocalStorage`.
The Optable Web SDK can fetch targeting data from a DCN and prepare an audience taxonomy object similar to the one described in [the prebid.js first party data documentation](https://docs.prebid.org/features/firstPartyData.html#segments-and-taxonomy). The `prebidORTB2FromCache()` function returns the object from the targeting data stored by `targeting()` API calls in `LocalStorage`.

### Seller Defined Audiences

The HTML code snippet below shows how `prebidUserDataFromCache()` can be used to retrieve targeting data from the `LocalStorage` administered by the Optable SDK, and write Seller Defined Audiences (SDA) into [prebid.js](https://prebid.org/product-suite/prebid-js/) which is also loaded into the page, using `pbjs.setConfig({ ortb2: { user: { data: [ { ... } ] } } })` as documented in [the prebid.js first party data documentation](https://docs.prebid.org/features/firstPartyData.html#segments-and-taxonomy). The `targeting()` API is also called in order to retrieve and locally store the latest matching activations from `dcn.customer.com/my-site`.
The HTML code snippet below shows how `prebidORTB2FromCache()` can be used to retrieve targeting data from the `LocalStorage` administered by the Optable SDK, and write Seller Defined Audiences (SDA) into [prebid.js](https://prebid.org/product-suite/prebid-js/) which is also loaded into the page, using `pbjs.mergeConfig({ ortb2: ortb2 })` as documented in [the prebid.js first party data documentation](https://docs.prebid.org/features/firstPartyData.html#segments-and-taxonomy). The `targeting()` API is also called in order to retrieve and locally store the latest matching activations from `dcn.customer.com/my-site`.

Note that [prebid.js bidder adapters](https://docs.prebid.org/dev-docs/bidders.html) can subsequently retrieve the data from the [global config](https://docs.prebid.org/features/firstPartyData.html#supplying-global-data).

Expand Down Expand Up @@ -503,16 +503,8 @@ For a working demo showing a `pbjs` and GAM integrated together, see the [demo p
pbjs.que.push(function () {
optable.cmd.push(function () {
const pbdata = optable.instance.prebidUserDataFromCache();
if (pbdata.length > 0) {
pbjs.setConfig({
ortb2: {
user: {
data: pbdata,
},
},
});
}
const ortb2 = optable.instance.prebidORTB2FromCache();
pbjs.mergeConfig({ ortb2: ortb2 });
// ... etc ...
Expand Down
18 changes: 5 additions & 13 deletions demos/vanilla/nocookies/targeting/prebid.html.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -153,19 +153,11 @@
pbjs.que.push(function () {
optable.cmd.push(function () {
const pbdata = optable.instance.prebidUserDataFromCache();
if (pbdata.length > 0) {
pbjs.setConfig({
ortb2: {
user: {
data: pbdata
}
}
});
const ortb2 = optable.instance.prebidORTB2FromCache();
pbjs.mergeConfig({ ortb2: ortb2 });
console.log("[OptableSDK] pbjs.setConfig(ortb2.user.data)");
console.log(pbdata);
}
console.log("[OptableSDK] pbjs.mergeConfig(ortb2)");
console.log(ortb2);
pbjs.setConfig({
priceGranularity: "low",
Expand Down Expand Up @@ -240,7 +232,7 @@
we also pass matching active cohorts to GAM.
</p>
<p>
In this example, we use the <code>prebidUserDataFromCache()</code> API to retrieve any targeting data from browser
In this example, we use the <code>prebidORTB2FromCache()</code> API to retrieve any targeting data from browser
LocalStorage, in order to pass it to Prebid.js via <a href="https://docs.prebid.org/features/firstPartyData.html#segments-and-taxonomy">seller defined audiences</a>. We also call the SDK <code>targeting</code> API
which will fetch the latest targeting data from our DCN and cache it locally for later use. Since these
two events happen asynchronously, it's possible that the targeting data passed to GAM is slightly outdated.
Expand Down
16 changes: 4 additions & 12 deletions demos/vanilla/targeting/prebid.html.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -152,18 +152,10 @@
pbjs.que.push(function () {
optable.cmd.push(function () {
const pbdata = optable.instance.prebidUserDataFromCache();
if (pbdata.length > 0) {
pbjs.setConfig({
ortb2: {
user: {
data: pbdata
}
}
});
const ortb2 = optable.instance.prebidORTB2FromCache();
pbjs.mergeConfig({ ortb2: ortb2 });
console.log("[OptableSDK] pbjs.setConfig(ortb2.user.data)");
}
console.log("[OptableSDK] pbjs.mergeConfig(ortb2)");
pbjs.setConfig({
priceGranularity: "low",
Expand Down Expand Up @@ -238,7 +230,7 @@
we also pass matching active cohorts to GAM.
</p>
<p>
In this example, we use the <code>prebidUserDataFromCache()</code> API to retrieve any targeting data from browser
In this example, we use the <code>prebidORTB2FromCache()</code> API to retrieve any targeting data from browser
LocalStorage, in order to pass it to Prebid.js via <a href="https://docs.prebid.org/features/firstPartyData.html#segments-and-taxonomy">seller defined audiences</a>. We also call the SDK <code>targeting</code> API
which will fetch the latest targeting data from our DCN and cache it locally for later use. Since these
two events happen asynchronously, it's possible that the targeting data passed to Prebid is slightly outdated.
Expand Down
43 changes: 43 additions & 0 deletions lib/edge/rtb2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Based on https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/extensions/2.x_official_extensions/eids.md
type UserSegment = {
id?: string
name?: string
value?: string
ext?: any
};

type UserData = {
id?: string
name?: string
segment?: UserSegment[]
ext?: { segtax: number }
};

type ExtendedIdentifierUID = {
id: string
atype: UIDAgentType;
ext?: any;
}

type ExtendedIdentifiers = {
source: string;
uids: ExtendedIdentifierUID[];
}


type UserExt = {
eids?: ExtendedIdentifiers[];
}

type User = {
data?: UserData[]
ext?: UserExt
}

enum UIDAgentType {
DeviceID = 1,
InAppImpression = 2,
PersonID = 3,
}

export { User, UIDAgentType }
18 changes: 12 additions & 6 deletions lib/edge/targeting.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { PrebidUserData, TargetingKeyValues } from "./targeting";
import { PrebidORTB2, TargetingKeyValues } from "./targeting";

describe("PrebidUserData", () => {
describe("PrebidORTB2", () => {
test("returns empty array on empty input", () => {
expect(PrebidUserData(null)).toEqual([])
expect(PrebidUserData({})).toEqual([])
const empty = { user: { data: [], ext: { eids: []} }}
expect(PrebidORTB2(null)).toEqual(empty)
expect(PrebidORTB2({})).toEqual(empty)
})

test("returns for each targeting audiences a user segments compatible with ortb2.user.data", () => {
Expand All @@ -16,8 +17,13 @@ describe("PrebidUserData", () => {
]
};

expect(PrebidUserData(targeting)).toEqual(
[{name: "optable.co", segment: [{ id: "a"}, {id: "b"}, {id: "c"}], ext: {segtax: 123}}]
expect(PrebidORTB2(targeting)).toEqual(
{
user: {
data: [{name: "optable.co", segment: [{ id: "a"}, {id: "b"}, {id: "c"}], ext: {segtax: 123}}],
ext: { eids: [{ source: "uidapi.com", uids: [{id: "d", atype: 3}]}] },
},
}
)
})
})
Expand Down
34 changes: 21 additions & 13 deletions lib/edge/targeting.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { OptableConfig } from "../config";
import { fetch } from "../core/network";
import { LocalStorage } from "../core/storage";
import {UIDAgentType, User as RTB2User} from "./rtb2";

type Identifier = {
id: string;
Expand Down Expand Up @@ -49,29 +50,36 @@ function TargetingClearCache(config: OptableConfig) {
ls.clearTargeting();
}

type PrebidUserSegment = Identifier
type PrebidSegtax = { segtax: number };
type PrebidUserSegmentProvider = { name: string; ext: PrebidSegtax; segment: PrebidUserSegment[] };
type PrebidUserData = PrebidUserSegmentProvider[];
type PrebidORTB2 = {user?: RTB2User}

/*
* Prebid.js supports passing seller-defined audiences to compatible
* bidder adapters.
*
* We return the contents to be pushed to ortb2.user.data and passed to
* bidder adapters via setConfig(ortb2.user.data)... the caller is free
* We return the contents to be merged in ortb2 and passed to
* bidder adapters via mergeConfig(ortb2)... the caller is free
* to append additional objects before setting the final result.
*
* References:
* https://docs.prebid.org/features/firstPartyData.html#segments-and-taxonomy
* https://iabtechlab.com/wp-content/uploads/2021/03/IABTechLab_Taxonomy_and_Data_Transparency_Standards_to_Support_Seller-defined_Audience_and_Context_Signaling_2021-03.pdf
*/
function PrebidUserData(tdata: TargetingResponse | null): PrebidUserData {
return (tdata?.audience ?? []).map((identifiers) => ({
name: identifiers.provider,
segment: identifiers.ids,
ext: { segtax: identifiers.rtb_segtax },
}))
function PrebidORTB2(tdata: TargetingResponse | null): PrebidORTB2 {
return {
user: {
data: (tdata?.audience ?? []).map((identifiers) => ({
name: identifiers.provider,
segment: identifiers.ids,
ext: { segtax: identifiers.rtb_segtax },
})),
ext: {
eids: (tdata?.user ?? []).map((identifiers) => ({
source: identifiers.provider,
uids: identifiers.ids.map(({ id }) => ({ id, atype: UIDAgentType.PersonID })),
})),
}
}
}
}

type TargetingKeyValues = { [key: string]: string[] };
Expand Down Expand Up @@ -99,7 +107,7 @@ export {
TargetingFromCache,
TargetingClearCache,
TargetingResponse,
PrebidUserData,
PrebidORTB2,
TargetingKeyValues,
};
export default Targeting;
16 changes: 8 additions & 8 deletions lib/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import type { ProfileTraits } from "./edge/profile";
import { Identify } from "./edge/identify";
import {
TargetingKeyValues,
PrebidUserData,
TargetingResponse,
Targeting,
TargetingFromCache,
TargetingClearCache
TargetingClearCache,
PrebidORTB2
} from "./edge/targeting";
import { Witness } from "./edge/witness";
import { Profile } from "./edge/profile";
Expand Down Expand Up @@ -40,13 +40,13 @@ class OptableSDK {
TargetingClearCache(this.dcn);
}

async prebidUserData(): Promise<PrebidUserData> {
return PrebidUserData(await this.targeting())
async prebidORTB2(): Promise<PrebidORTB2> {
return PrebidORTB2(await this.targeting())
}

prebidUserDataFromCache(): PrebidUserData {
prebidORTB2FromCache(): PrebidORTB2 {
const tdata = this.targetingFromCache()
return PrebidUserData(tdata);
return PrebidORTB2(tdata);
}

async targetingKeyValues(): Promise<TargetingKeyValues> {
Expand Down Expand Up @@ -78,8 +78,8 @@ class OptableSDK {
return TargetingKeyValues(tdata)
}

static PrebidUserData(tdata: TargetingResponse): PrebidUserData {
return PrebidUserData(tdata)
static PrebidORTB2(tdata: TargetingResponse): PrebidORTB2 {
return PrebidORTB2(tdata)
}
}

Expand Down
Loading

0 comments on commit a40a8b2

Please sign in to comment.