Skip to content

Commit

Permalink
Operaads: add ID System sbumodule (prebid#10270)
Browse files Browse the repository at this point in the history
Co-authored-by: hongxingp <hongxingp@opera.com>
  • Loading branch information
duduchristian and DuduPan authored Aug 8, 2023
1 parent 8df5e93 commit 49418b1
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 6 deletions.
3 changes: 2 additions & 1 deletion modules/.submodules.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
"zeotapIdPlusIdSystem",
"adqueryIdSystem",
"gravitoIdSystem",
"freepassIdSystem"
"freepassIdSystem",
"operaadsIdSystem"
],
"adpod": [
"freeWheelAdserverVideo",
Expand Down
5 changes: 5 additions & 0 deletions modules/operaadsBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,11 @@ function mapNativeImage(image, type) {
* @returns {String} userId
*/
function getUserId(bidRequest) {
let operaId = deepAccess(bidRequest, 'userId.operaId');
if (operaId) {
return operaId;
}

let sharedId = deepAccess(bidRequest, 'userId.sharedid.id');
if (sharedId) {
return sharedId;
Expand Down
10 changes: 5 additions & 5 deletions modules/operaadsBidAdapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,18 +135,18 @@ var adUnits = [{

### User Ids

Opera Ads Bid Adapter uses `sharedId`, `pubcid` or `tdid`, please config at least one.
Opera Ads Bid Adapter uses `operaId`, please refer to [`Opera ID System`](./operaadsIdSystem.md).

```javascript
pbjs.setConfig({
...,
userSync: {
userIds: [{
name: 'sharedId',
name: 'operaId',
storage: {
name: '_sharedID', // name of the 1st party cookie
type: 'cookie',
expires: 30
name: 'operaId',
type: 'html5',
expires: 14
}
}]
}
Expand Down
106 changes: 106 additions & 0 deletions modules/operaadsIdSystem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* This module adds operaId to the User ID module
* The {@link module:modules/userId} module is required
* @module modules/operaadsIdSystem
* @requires module:modules/userId
*/
import * as ajax from '../src/ajax.js';
import { submodule } from '../src/hook.js';
import { logMessage, logError } from '../src/utils.js';

const MODULE_NAME = 'operaId';
const ID_KEY = MODULE_NAME;
const version = '1.0';
const SYNC_URL = 'https://t.adx.opera.com/identity/';
const AJAX_TIMEOUT = 300;
const AJAX_OPTIONS = {method: 'GET', withCredentials: true, contentType: 'application/json'};

function constructUrl(pairs) {
const queries = [];
for (let key in pairs) {
queries.push(`${key}=${encodeURIComponent(pairs[key])}`);
}
return `${SYNC_URL}?${queries.join('&')}`;
}

function asyncRequest(url, cb) {
ajax.ajaxBuilder(AJAX_TIMEOUT)(
url,
{
success: response => {
try {
const jsonResponse = JSON.parse(response);
const { uid: operaId } = jsonResponse;
cb(operaId);
return;
} catch (e) {
logError(`${MODULE_NAME}: invalid response`, response);
}
cb();
},
error: (err) => {
logError(`${MODULE_NAME}: ID error response`, err);
cb();
}
},
null,
AJAX_OPTIONS
);
}

export const operaIdSubmodule = {
/**
* used to link submodule with config
* @type {string}
*/
name: MODULE_NAME,

/**
* @type {string}
*/
version,

/**
* decode the stored id value for passing to bid requests
* @function
* @param {string} id
* @returns {{'operaId': string}}
*/
decode: (id) =>
id != null && id.length > 0
? { [ID_KEY]: id }
: undefined,

/**
* performs action to obtain id and return a value in the callback's response argument
* @function
* @param {SubmoduleConfig} [config]
* @returns {IdResponse|undefined}
*/
getId(config, consentData) {
logMessage(`${MODULE_NAME}: start synchronizing opera uid`);
const params = (config && config.params) || {};
if (typeof params.pid !== 'string' || params.pid.length == 0) {
logError(`${MODULE_NAME}: submodule requires a publisher ID to be defined`);
return;
}

const { pid, syncUrl = SYNC_URL } = params;
const url = constructUrl(syncUrl, { publisherId: pid });

return {
callback: (cb) => {
asyncRequest(url, cb);
}
}
},

eids: {
'operaId': {
source: 't.adx.opera.com',
atype: 1
},
}
};

submodule('userId', operaIdSubmodule);
52 changes: 52 additions & 0 deletions modules/operaadsIdSystem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Opera ID System

For help adding this module, please contact [adtech-prebid-group@opera.com](adtech-prebid-group@opera.com).

### Prebid Configuration

You should configure this module under your `userSync.userIds[]` configuration:

```javascript
pbjs.setConfig({
userSync: {
userIds: [
{
name: "operaId",
storage: {
name: "operaId",
type: "html5",
expires: 14
},
params: {
pid: "your-pulisher-ID-here"
}
}
]
}
})
```
<br>

| Param under `userSync.userIds[]` | Scope | Type | Description | Example |
| -------------------------------- | -------- | ------ | ----------------------------- | ----------------------------------------- |
| name | Required | string | ID for the operaId module | `"operaId"` |
| storage | Optional | Object | Settings for operaId storage | See [storage settings](#storage-settings) |
| params | Required | Object | Parameters for opreaId module | See [params](#params) |
<br>

### Params

| Param under `params` | Scope | Type | Description | Example |
| -------------------- | -------- | ------ | ------------------------------ | --------------- |
| pid | Required | string | Publisher ID assigned by Opera | `"pub12345678"` |
<br>

### Storage Settings

The following settings are suggested for the `storage` property in the `userSync.userIds[]` object:

| Param under `storage` | Type | Description | Example |
| --------------------- | ------------- | -------------------------------------------------------------------------------- | ----------- |
| name | String | Where the ID will be stored | `"operaId"` |
| type | String | For best performance, this should be `"html5"` | `"html5"` |
| expires | Number <= 30 | number of days until the stored ID expires. **Must be less than or equal to 30** | `14` |
15 changes: 15 additions & 0 deletions test/spec/modules/eids_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,21 @@ describe('eids array generation for known sub-modules', function() {
});
});

it('operaId', function() {
const userId = {
operaId: 'some-random-id-value'
};
const newEids = createEidsArray(userId);
expect(newEids.length).to.equal(1);
expect(newEids[0]).to.deep.equal({
source: 't.adx.opera.com',
uids: [{
id: 'some-random-id-value',
atype: 1
}]
});
});

it('33acrossId', function() {
const userId = {
'33acrossId': {
Expand Down
53 changes: 53 additions & 0 deletions test/spec/modules/operaadsIdSystem_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { operaIdSubmodule } from 'modules/operaadsIdSystem'
import * as ajaxLib from 'src/ajax.js'

const TEST_ID = 'opera-test-id';
const operaIdRemoteResponse = { uid: TEST_ID };

describe('operaId submodule properties', () => {
it('should expose a "name" property equal to "operaId"', () => {
expect(operaIdSubmodule.name).to.equal('operaId');
});
});

function fakeRequest(fn) {
const ajaxBuilderStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(() => {
return (url, cbObj) => {
cbObj.success(JSON.stringify(operaIdRemoteResponse));
}
});
fn();
ajaxBuilderStub.restore();
}

describe('operaId submodule getId', function() {
it('request to the fake server to correctly extract test ID', function() {
fakeRequest(() => {
const moduleIdCallbackResponse = operaIdSubmodule.getId({ params: { pid: 'pub123' } });
moduleIdCallbackResponse.callback((id) => {
expect(id).to.equal(operaIdRemoteResponse.operaId);
});
});
});

it('request to the fake server without publiser ID', function() {
fakeRequest(() => {
const moduleIdCallbackResponse = operaIdSubmodule.getId({ params: {} });
expect(moduleIdCallbackResponse).to.equal(undefined);
});
});
});

describe('operaId submodule decode', function() {
it('should respond with an object containing "operaId" as key with the value', () => {
expect(operaIdSubmodule.decode(TEST_ID)).to.deep.equal({
operaId: TEST_ID
});
});

it('should respond with undefined if the value is not a string or an empty string', () => {
[1, 2.0, null, undefined, NaN, [], {}].forEach((value) => {
expect(operaIdSubmodule.decode(value)).to.equal(undefined);
});
});
});

0 comments on commit 49418b1

Please sign in to comment.