diff --git a/modules/.submodules.json b/modules/.submodules.json
index b3685658084..fdc79c8b868 100644
--- a/modules/.submodules.json
+++ b/modules/.submodules.json
@@ -46,7 +46,8 @@
"zeotapIdPlusIdSystem",
"adqueryIdSystem",
"gravitoIdSystem",
- "freepassIdSystem"
+ "freepassIdSystem",
+ "operaadsIdSystem"
],
"adpod": [
"freeWheelAdserverVideo",
diff --git a/modules/operaadsBidAdapter.js b/modules/operaadsBidAdapter.js
index e721fb85fd7..b45c0452319 100644
--- a/modules/operaadsBidAdapter.js
+++ b/modules/operaadsBidAdapter.js
@@ -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;
diff --git a/modules/operaadsBidAdapter.md b/modules/operaadsBidAdapter.md
index 709c67a04a7..6c5a4646dd0 100644
--- a/modules/operaadsBidAdapter.md
+++ b/modules/operaadsBidAdapter.md
@@ -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
}
}]
}
diff --git a/modules/operaadsIdSystem.js b/modules/operaadsIdSystem.js
new file mode 100644
index 00000000000..09dd8512a2b
--- /dev/null
+++ b/modules/operaadsIdSystem.js
@@ -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);
diff --git a/modules/operaadsIdSystem.md b/modules/operaadsIdSystem.md
new file mode 100644
index 00000000000..288fb960b96
--- /dev/null
+++ b/modules/operaadsIdSystem.md
@@ -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"
+ }
+ }
+ ]
+ }
+})
+```
+
+
+| 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) |
+
+
+### Params
+
+| Param under `params` | Scope | Type | Description | Example |
+| -------------------- | -------- | ------ | ------------------------------ | --------------- |
+| pid | Required | string | Publisher ID assigned by Opera | `"pub12345678"` |
+
+
+### 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` |
\ No newline at end of file
diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js
index 9291ec88569..f82defc3c45 100644
--- a/test/spec/modules/eids_spec.js
+++ b/test/spec/modules/eids_spec.js
@@ -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': {
diff --git a/test/spec/modules/operaadsIdSystem_spec.js b/test/spec/modules/operaadsIdSystem_spec.js
new file mode 100644
index 00000000000..d81f643d62f
--- /dev/null
+++ b/test/spec/modules/operaadsIdSystem_spec.js
@@ -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);
+ });
+ });
+});