Skip to content

Commit ee59d5a

Browse files
authored
Start.io - Create User ID submodule and improve adapter
1 parent 1391843 commit ee59d5a

9 files changed

Lines changed: 510 additions & 5 deletions

File tree

modules/.submodules.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"quantcastIdSystem",
5454
"rewardedInterestIdSystem",
5555
"sharedIdSystem",
56+
"startioIdSystem",
5657
"taboolaIdSystem",
5758
"tapadIdSystem",
5859
"teadsIdSystem",

modules/startioBidAdapter.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { registerBidder } from '../src/adapters/bidderFactory.js';
22
import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js';
3-
import { logError, isFn, isPlainObject } from '../src/utils.js';
3+
import { logError, isFn, isPlainObject, formatQS } from '../src/utils.js';
44
import { ortbConverter } from '../libraries/ortbConverter/converter.js'
55
import { ortb25Translator } from '../libraries/ortb2.5Translator/translator.js';
6+
import { getUserSyncParams } from '../libraries/userSyncUtils/userSyncUtils.js';
67

78
const BIDDER_CODE = 'startio';
89
const METHOD = 'POST';
910
const GVLID = 1216;
1011
const ENDPOINT_URL = `https://pbc-rtb.startappnetwork.com/1.3/2.5/getbid?account=pbc`;
12+
const IFRAME_URL = 'https://cs.startappnetwork.com/sync?p=m4b8b3y4';
1113

1214
const converter = ortbConverter({
1315
imp(buildImp, bidRequest, context) {
@@ -151,6 +153,23 @@ export const spec = {
151153
},
152154

153155
onSetTargeting: (bid) => { },
156+
157+
getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) {
158+
const syncs = [];
159+
160+
if (syncOptions.iframeEnabled) {
161+
const consentParams = getUserSyncParams(gdprConsent, uspConsent, gppConsent);
162+
const queryString = formatQS(consentParams);
163+
const queryParam = queryString ? `&${queryString}` : '';
164+
165+
syncs.push({
166+
type: 'iframe',
167+
url: `${IFRAME_URL}${queryParam}`
168+
});
169+
}
170+
171+
return syncs;
172+
}
154173
};
155174

156175
registerBidder(spec);

modules/startioBidAdapter.md

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ var adUnits = [
2525
bidder: 'startio',
2626
params: {
2727
// REQUIRED - Publisher Account ID
28-
accountId: 'your-account-id',
28+
publisherId: 'your-account-id',
2929
3030
// OPTIONAL - Enable test ads
3131
testAdsEnabled: true
@@ -58,7 +58,7 @@ var videoAdUnits = [
5858
{
5959
bidder: 'startio',
6060
params: {
61-
accountId: 'your-account-id',
61+
publisherId: 'your-account-id',
6262
testAdsEnabled: true
6363
}
6464
}
@@ -85,7 +85,7 @@ var nativeAdUnits = [
8585
{
8686
bidder: 'startio',
8787
params: {
88-
accountId: 'your-account-id',
88+
publisherId: 'your-account-id',
8989
testAdsEnabled: true
9090
}
9191
}
@@ -94,8 +94,28 @@ var nativeAdUnits = [
9494
];
9595
```
9696

97+
### Prebid Params Enabling User Sync
98+
99+
To enable iframe-based user syncing for Start.io, include the `filterSettings` configuration in your `userSync` setup:
100+
101+
```javascript
102+
pbjs.setConfig({
103+
userSync: {
104+
userIds: [{
105+
name: 'startioId'
106+
}],
107+
filterSettings: {
108+
iframe: {
109+
bidders: ['startio'],
110+
filter: 'include'
111+
}
112+
}
113+
}
114+
});
115+
```
116+
97117
# Additional Notes
98118
- The adapter processes requests via OpenRTB 2.5 standards.
99-
- Ensure that the `accountId` parameter is set correctly for your integration.
119+
- Ensure that the `publisherId` parameter is set correctly for your integration.
100120
- Test ads can be enabled using `testAdsEnabled: true` during development.
101121
- The adapter supports multiple ad formats, allowing publishers to serve banners, native ads and instream video ads seamlessly.

modules/startioIdSystem.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**
2+
* This module adds startio ID support to the User ID module
3+
* The {@link module:modules/userId} module is required
4+
* @module modules/startioIdSystem
5+
* @requires module:modules/userId
6+
*/
7+
import { logError, formatQS } from '../src/utils.js';
8+
import { submodule } from '../src/hook.js';
9+
import { ajax } from '../src/ajax.js';
10+
import { getStorageManager } from '../src/storageManager.js';
11+
import { MODULE_TYPE_UID } from '../src/activities/modules.js';
12+
import { getUserSyncParams } from '../libraries/userSyncUtils/userSyncUtils.js';
13+
14+
const MODULE_NAME = 'startioId';
15+
const DEFAULT_ENDPOINT = 'https://cs.startappnetwork.com/get-uid-obj?p=m4b8b3y4';
16+
17+
const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME});
18+
19+
function getCachedId() {
20+
let cachedId;
21+
22+
if (storage.cookiesAreEnabled()) {
23+
cachedId = storage.getCookie(MODULE_NAME);
24+
}
25+
26+
if (!cachedId && storage.hasLocalStorage()) {
27+
const expirationStr = storage.getDataFromLocalStorage(`${MODULE_NAME}_exp`);
28+
if (expirationStr) {
29+
const expirationDate = new Date(expirationStr);
30+
if (expirationDate > new Date()) {
31+
cachedId = storage.getDataFromLocalStorage(MODULE_NAME);
32+
}
33+
}
34+
}
35+
36+
return cachedId || null;
37+
}
38+
39+
function storeId(id, expiresInDays) {
40+
expiresInDays = expiresInDays || 90;
41+
const expirationDate = new Date(Date.now() + expiresInDays * 24 * 60 * 60 * 1000).toUTCString();
42+
43+
if (storage.cookiesAreEnabled()) {
44+
storage.setCookie(MODULE_NAME, id, expirationDate, 'None');
45+
}
46+
47+
if (storage.hasLocalStorage()) {
48+
storage.setDataInLocalStorage(`${MODULE_NAME}_exp`, expirationDate);
49+
storage.setDataInLocalStorage(MODULE_NAME, id);
50+
}
51+
}
52+
53+
function fetchIdFromServer(callback, expiresInDays, consentData) {
54+
const consentParams = getUserSyncParams(
55+
consentData?.gdpr,
56+
consentData?.usp,
57+
consentData?.gpp
58+
);
59+
const queryString = formatQS(consentParams);
60+
const url = queryString ? `${DEFAULT_ENDPOINT}&${queryString}` : DEFAULT_ENDPOINT;
61+
62+
const callbacks = {
63+
success: response => {
64+
let responseId;
65+
try {
66+
const responseObj = JSON.parse(response);
67+
if (responseObj && responseObj.uid) {
68+
responseId = responseObj.uid;
69+
storeId(responseId, expiresInDays);
70+
} else {
71+
logError(`${MODULE_NAME}: Server response missing 'uid' field`);
72+
}
73+
} catch (error) {
74+
logError(`${MODULE_NAME}: Error parsing server response`, error);
75+
}
76+
callback(responseId);
77+
},
78+
error: error => {
79+
logError(`${MODULE_NAME}: ID fetch encountered an error`, error);
80+
callback();
81+
}
82+
};
83+
ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true });
84+
}
85+
86+
export const startioIdSubmodule = {
87+
name: MODULE_NAME,
88+
decode(value) {
89+
return value && typeof value === 'string'
90+
? { 'startioId': value }
91+
: undefined;
92+
},
93+
getId(config, consentData, storedId) {
94+
if (storedId) {
95+
return { id: storedId };
96+
}
97+
98+
const cachedId = getCachedId();
99+
if (cachedId) {
100+
return { id: cachedId };
101+
}
102+
const storageConfig = config && config.storage;
103+
const expiresInDays = storageConfig && storageConfig.expires;
104+
return { callback: (cb) => fetchIdFromServer(cb, expiresInDays, consentData) };
105+
},
106+
107+
eids: {
108+
'startioId': {
109+
source: 'start.io',
110+
atype: 1
111+
},
112+
}
113+
};
114+
115+
submodule('userId', startioIdSubmodule);

modules/startioIdSystem.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
## Start.io User ID Submodule
2+
3+
The Start.io User ID submodule generates and persists a unique user identifier by fetching it from a publisher-supplied endpoint. The ID is stored in both cookies and local storage for subsequent page loads and is made available to other Prebid.js modules via the standard `eids` interface.
4+
5+
For integration support, contact prebid@start.io.
6+
7+
### Prebid Params Enabling User Sync
8+
9+
To enable iframe-based user syncing for Start.io, include the `filterSettings` configuration in your `userSync` setup:
10+
11+
```javascript
12+
pbjs.setConfig({
13+
userSync: {
14+
userIds: [{
15+
name: 'startioId'
16+
}],
17+
filterSettings: {
18+
iframe: {
19+
bidders: ['startio'],
20+
filter: 'include'
21+
}
22+
}
23+
}
24+
});
25+
```
26+
27+
This configuration allows Start.io to sync user data via iframe, which is necessary for cross-domain user identification.
28+
29+
## Parameter Descriptions for the `userSync` Configuration Section
30+
31+
The below parameters apply only to the Start.io User ID integration.
32+
33+
| Param under userSync.userIds[] | Scope | Type | Description | Example |
34+
| --- | --- | --- | --- | --- |
35+
| name | Required | String | The name of this module. | `"startioId"` |

modules/userId/eids.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,13 @@ userIdAsEids = [
350350
id: 'some-random-id-value',
351351
atype: 1
352352
}]
353+
},
354+
{
355+
source: 'start.io',
356+
uids: [{
357+
id: 'some-random-id-value',
358+
atype: 1
359+
}]
353360
}
354361
]
355362
```

modules/userId/userId.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ pbjs.setConfig({
167167
},
168168
{
169169
name: "mygaruId"
170+
},
171+
{
172+
name: "startioId"
170173
}
171174
],
172175
syncDelay: 5000,

test/spec/modules/startioBidAdapter_spec.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,4 +370,98 @@ describe('Prebid Adapter: Startio', function () {
370370
});
371371
}
372372
});
373+
374+
describe('getUserSyncs', function () {
375+
it('should return an iframe sync when iframeEnabled is true', function () {
376+
const syncs = spec.getUserSyncs({ iframeEnabled: true }, []);
377+
378+
expect(syncs).to.have.lengthOf(1);
379+
expect(syncs[0].type).to.equal('iframe');
380+
expect(syncs[0].url).to.be.a('string');
381+
});
382+
383+
it('should return an empty array when iframeEnabled is false', function () {
384+
const syncs = spec.getUserSyncs({ iframeEnabled: false }, []);
385+
386+
expect(syncs).to.have.lengthOf(0);
387+
});
388+
389+
it('should return an empty array when syncOptions is empty', function () {
390+
const syncs = spec.getUserSyncs({}, []);
391+
392+
expect(syncs).to.have.lengthOf(0);
393+
});
394+
395+
it('should append GDPR consent params to the sync URL', function () {
396+
const gdprConsent = {
397+
gdprApplies: true,
398+
consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='
399+
};
400+
401+
const syncs = spec.getUserSyncs({ iframeEnabled: true }, [], gdprConsent);
402+
403+
expect(syncs).to.have.lengthOf(1);
404+
expect(syncs[0].url).to.include('gdpr=1');
405+
expect(syncs[0].url).to.include('gdpr_consent=BOJ/P2HOJ/P2HABABMAAAAAZ+A==');
406+
});
407+
408+
it('should append gdpr=0 when gdprApplies is false', function () {
409+
const gdprConsent = {
410+
gdprApplies: false,
411+
consentString: ''
412+
};
413+
414+
const syncs = spec.getUserSyncs({ iframeEnabled: true }, [], gdprConsent);
415+
416+
expect(syncs[0].url).to.include('gdpr=0');
417+
});
418+
419+
it('should append USP consent param to the sync URL', function () {
420+
const syncs = spec.getUserSyncs({ iframeEnabled: true }, [], undefined, '1YNN');
421+
422+
expect(syncs).to.have.lengthOf(1);
423+
expect(syncs[0].url).to.include('us_privacy=1YNN');
424+
});
425+
426+
it('should append GPP consent params to the sync URL', function () {
427+
const gppConsent = {
428+
gppString: 'DBABMA~BAAAAAAAAgA.QA',
429+
applicableSections: [7, 8]
430+
};
431+
432+
const syncs = spec.getUserSyncs({ iframeEnabled: true }, [], undefined, undefined, gppConsent);
433+
434+
expect(syncs).to.have.lengthOf(1);
435+
expect(syncs[0].url).to.include('gpp=DBABMA~BAAAAAAAAgA.QA');
436+
expect(syncs[0].url).to.include('gpp_sid=7,8');
437+
});
438+
439+
it('should append all consent params together when all are provided', function () {
440+
const gdprConsent = {
441+
gdprApplies: true,
442+
consentString: 'testConsent'
443+
};
444+
const uspConsent = '1YNN';
445+
const gppConsent = {
446+
gppString: 'testGpp',
447+
applicableSections: [2]
448+
};
449+
450+
const syncs = spec.getUserSyncs({ iframeEnabled: true }, [], gdprConsent, uspConsent, gppConsent);
451+
452+
expect(syncs).to.have.lengthOf(1);
453+
expect(syncs[0].url).to.include('gdpr=1');
454+
expect(syncs[0].url).to.include('gdpr_consent=testConsent');
455+
expect(syncs[0].url).to.include('us_privacy=1YNN');
456+
expect(syncs[0].url).to.include('gpp=testGpp');
457+
expect(syncs[0].url).to.include('gpp_sid=2');
458+
});
459+
460+
it('should not append query string when no consent params are provided', function () {
461+
const syncs = spec.getUserSyncs({ iframeEnabled: true }, []);
462+
463+
expect(syncs).to.have.lengthOf(1);
464+
expect(syncs[0].url).to.equal('https://cs.startappnetwork.com/sync?p=m4b8b3y4');
465+
});
466+
});
373467
});

0 commit comments

Comments
 (0)