Skip to content

Commit

Permalink
PubCommonId - Add support for localStorage (#3661)
Browse files Browse the repository at this point in the history
* Add support for localStorage

* Pubcid uses local storage first
  • Loading branch information
pycnvr authored and Isaac Dettman committed Apr 18, 2019
1 parent 8de1fac commit 984dfb1
Show file tree
Hide file tree
Showing 3 changed files with 366 additions and 38 deletions.
192 changes: 170 additions & 22 deletions modules/pubCommonId.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,126 @@
import * as utils from '../src/utils'
import { config } from '../src/config';

const COOKIE_NAME = '_pubcid';
const DEFAULT_EXPIRES = 2628000; // 5-year worth of minutes
const ID_NAME = '_pubcid';
const OPTOUT_NAME = '_pubcid_optout';
const DEFAULT_EXPIRES = 525600; // 1-year worth of minutes
const PUB_COMMON = 'PublisherCommonId';
const EXP_SUFFIX = '_exp';
const COOKIE = 'cookie';
const LOCAL_STORAGE = 'html5';

var pubcidEnabled = true;
var interval = DEFAULT_EXPIRES;
let pubcidConfig = {
enabled: true,
interval: DEFAULT_EXPIRES,
typeEnabled: LOCAL_STORAGE,
readOnly: false
};

export function isPubcidEnabled() { return pubcidEnabled; }
export function getExpInterval() { return interval; }
/**
* Set an item in the storage with expiry time.
* @param {string} key Key of the item to be stored
* @param {string} val Value of the item to be stored
* @param {number} expires Expiry time in minutes
*/

export function setStorageItem(key, val, expires) {
try {
if (expires !== undefined && expires != null) {
const expStr = (new Date(Date.now() + (expires * 60 * 1000))).toUTCString();
localStorage.setItem(key + EXP_SUFFIX, expStr);
}

localStorage.setItem(key, val);
} catch (e) {
utils.logMessage(e);
}
}

/**
* Retrieve an item from storage if it exists and hasn't expired.
* @param {string} key Key of the item.
* @returns {string|null} Value of the item.
*/
export function getStorageItem(key) {
let val = null;

try {
const expVal = localStorage.getItem(key + EXP_SUFFIX);

if (!expVal) {
// If there is no expiry time, then just return the item
val = localStorage.getItem(key);
} else {
// Only return the item if it hasn't expired yet.
// Otherwise delete the item.
const expDate = new Date(expVal);
const isValid = (expDate.getTime() - Date.now()) > 0;
if (isValid) {
val = localStorage.getItem(key);
} else {
removeStorageItem(key);
}
}
} catch (e) {
utils.logMessage(e);
}

return val;
}

/**
* Remove an item from storage
* @param {string} key Key of the item to be removed
*/
export function removeStorageItem(key) {
try {
localStorage.removeItem(key + EXP_SUFFIX);
localStorage.removeItem(key);
} catch (e) {
utils.logMessage(e);
}
}

/**
* Read a value either from cookie or local storage
* @param {string} name Name of the item
* @returns {string|null} a string if item exists
*/
function readValue(name) {
let value;
if (pubcidConfig.typeEnabled === COOKIE) {
value = getCookie(name);
} else if (pubcidConfig.typeEnabled === LOCAL_STORAGE) {
value = getStorageItem(name);
if (!value) {
value = getCookie(name);
}
}

if (value === 'undefined' || value === 'null') { return null; }

return value;
}

/**
* Write a value to either cookies or local storage
* @param {string} name Name of the item
* @param {string} value Value to be stored
* @param {number} expInterval Expiry time in minutes
*/
function writeValue(name, value, expInterval) {
if (name && value) {
if (pubcidConfig.typeEnabled === COOKIE) {
setCookie(name, value, expInterval);
} else if (pubcidConfig.typeEnabled === LOCAL_STORAGE) {
setStorageItem(name, value, expInterval);
}
}
}

export function isPubcidEnabled() { return pubcidConfig.enabled; }
export function getExpInterval() { return pubcidConfig.interval; }
export function getPubcidConfig() { return pubcidConfig; }

/**
* Decorate ad units with pubcid. This hook function is called before the
Expand All @@ -29,7 +140,7 @@ export function requestBidHook(next, config) {
let pubcid = null;

// Pass control to the next function if not enabled
if (!pubcidEnabled) {
if (!pubcidConfig.enabled || !pubcidConfig.typeEnabled) {
return next.call(this, config);
}

Expand All @@ -38,11 +149,22 @@ export function requestBidHook(next, config) {
pubcid = window[PUB_COMMON].getId();
utils.logMessage(PUB_COMMON + ': pubcid = ' + pubcid);
} else {
// Otherwise get the existing cookie or create a new id
pubcid = getCookie(COOKIE_NAME) || utils.generateUUID();
// Otherwise get the existing cookie
pubcid = readValue(ID_NAME);

if (!pubcidConfig.readOnly) {
if (!pubcid) {
pubcid = utils.generateUUID();
// Update the cookie/storage with the latest expiration date
writeValue(ID_NAME, pubcid, pubcidConfig.interval);
// Only return pubcid if it is saved successfully
pubcid = readValue(ID_NAME);
} else {
// Update the cookie/storage with the latest expiration date
writeValue(ID_NAME, pubcid, pubcidConfig.interval);
}
}

// Update the cookie with the latest expiration date
setCookie(COOKIE_NAME, pubcid, interval);
utils.logMessage('pbjs: pubcid = ' + pubcid);
}

Expand All @@ -68,21 +190,49 @@ export function setCookie(name, value, expires) {

// Helper to read a cookie
export function getCookie(name) {
let m = window.document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]*)\\s*(;|$)');
return m ? decodeURIComponent(m[2]) : null;
if (name && window.document.cookie) {
let m = window.document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]*)\\s*(;|$)');
return m ? decodeURIComponent(m[2]) : null;
}
return null;
}

/**
* Configuration function
* @param {boolean} enable Enable or disable pubcid. By default the module is enabled.
* @param {number} expInterval Expiration interval of the cookie in minutes.
* @param {string} type Type of storage to use
* @param {boolean} readOnly Read but not update id
*/

export function setConfig({ enable = true, expInterval = DEFAULT_EXPIRES } = {}) {
pubcidEnabled = enable;
interval = parseInt(expInterval, 10);
if (isNaN(interval)) {
interval = DEFAULT_EXPIRES;
export function setConfig({ enable = true, expInterval = DEFAULT_EXPIRES, type = 'html5,cookie', readOnly = false } = {}) {
pubcidConfig.enabled = enable;
pubcidConfig.interval = parseInt(expInterval, 10);
if (isNaN(pubcidConfig.interval)) {
pubcidConfig.interval = DEFAULT_EXPIRES;
}

pubcidConfig.readOnly = readOnly;

// Default is to use local storage. Fall back to
// cookie only if local storage is not supported.

pubcidConfig.typeEnabled = null;

const typeArray = type.split(',');
for (let i = 0; i < typeArray.length; ++i) {
const name = typeArray[i].trim();
if (name === COOKIE) {
if (utils.cookiesAreEnabled()) {
pubcidConfig.typeEnabled = COOKIE;
break;
}
} else if (name === LOCAL_STORAGE) {
if (utils.hasLocalStorage()) {
pubcidConfig.typeEnabled = LOCAL_STORAGE;
break;
}
}
}
}

Expand All @@ -92,10 +242,8 @@ export function setConfig({ enable = true, expInterval = DEFAULT_EXPIRES } = {})
export function initPubcid() {
config.getConfig('pubcid', config => setConfig(config.pubcid));

if (utils.cookiesAreEnabled()) {
if (!getCookie('_pubcid_optout')) {
$$PREBID_GLOBAL$$.requestBids.before(requestBidHook);
}
if (!readValue(OPTOUT_NAME)) {
$$PREBID_GLOBAL$$.requestBids.before(requestBidHook);
}
}

Expand Down
37 changes: 37 additions & 0 deletions modules/pubCommonId.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
## Publisher Common ID Example Configuration

When the module is included, it's automatically enabled and saves an id to both cookie and local storage with an expiration time of 1 year.

Example of disabling publisher common id.

```
pbjs.setConfig(
pubcid: {
enable: false
}
);
```

Example of setting expiration interval to 30 days. The interval is expressed in minutes.

```
pbjs.setConfig(
pubcid: {
expInterval: 43200
}
);
```

Example of using local storage only and setting expiration interval to 30 days.

```
pbjs.setConfig(
pubcid: {
expInterval: 43200,
type: 'html5'
}
);
```



Loading

0 comments on commit 984dfb1

Please sign in to comment.