optinout.js
Version:
a simple js framework for handling opt-in and opt-out for tracking & cookies
591 lines (470 loc) • 16.3 kB
JavaScript
/* optinout.js - v1.1.2 | Copyright: fabian todt and other contributors | License: https://gitlab.com/optinout/optinout.js */
var defineProperty = function (obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
};
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
var toConsumableArray = function (arr) {
if (Array.isArray(arr)) {
for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
return arr2;
} else {
return Array.from(arr);
}
};
var Storage = function Storage(userOptions) {
var defaultOptions = {
namespace: 'optInOut',
expiration: Infinity,
domain: false,
path: false,
secure: false
};
var options = _extends({}, defaultOptions, userOptions);
var getExpirationString = function getExpirationString(date) {
var expirationDate = date || options.expiration;
var expires = void 0;
switch (expirationDate.constructor) {
case Number:
expires = expirationDate === Infinity ? '; expires=Fri, 31 Dec 9999 23:59:59 GMT' : '; max-age=' + expirationDate;
break;
case String:
expires = '; expires=' + expirationDate;
break;
case Date:
expires = '; expires=' + expirationDate.toUTCString();
break;
default:
expires = '';
break;
}
return expires;
};
var getKey = function getKey(key) {
return options.namespace + '.' + key;
};
var getValue = function getValue(key) {
if (!key || /^(?:expires|max-age|path|domain|secure)$/i.test(key)) {
return false;
}
return decodeURIComponent(document.cookie.replace(new RegExp('(?:(?:^|.*;)\\s*' + encodeURIComponent(getKey(key)).replace(/[-.+*]/g, '\\$&') + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1'));
};
var getItem = function getItem(service, key) {
if (!service) {
return null;
}
var value = getValue(service) || null;
var valueJson = void 0;
try {
valueJson = JSON.parse(value);
} catch (err) {
valueJson = null;
}
if (key) {
return valueJson[key] || null;
}
return valueJson || null;
};
var writeValue = function writeValue(key, value, expires) {
document.cookie = encodeURIComponent(key) + '=' + encodeURIComponent(JSON.stringify(value)) + getExpirationString(expires) + (options.domain ? '; domain=' + options.domain : '') + (options.path ? '; path=' + options.path : '') + (options.secure ? '; secure' : '');
};
var setItem = function setItem(service, key, value, update) {
if (!service || /^(?:expires|max-age|path|domain|secure)$/i.test(service)) {
return false;
}
var saveValue = void 0;
if (update) {
var newData = defineProperty({}, key, value);
var currentData = getItem(service);
saveValue = _extends({}, currentData, newData);
} else {
saveValue = defineProperty({}, key, value);
}
writeValue(getKey(service), saveValue);
return true;
};
var hasItem = function hasItem(service) {
return Boolean(getValue(service));
};
var removeItem = function removeItem(service, key) {
if (!service || !hasItem(service)) {
return false;
}
if (key) {
var currentValue = getItem(service);
var newValue = currentValue;
delete newValue[key];
writeValue(getKey(service), newValue);
return true;
}
writeValue(getKey(service), '', new Date('01 Jan 1970'));
return true;
};
return {
get: function get$$1(service) {
var key = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
return getItem(service, key);
},
set: function set$$1(service, key, value) {
var update = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
return setItem(service, key, value, update);
},
delete: function _delete(service) {
var key = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
return removeItem(service, key);
}
};
};
var Storage$1 = function Storage(userOptions) {
var defaultOptions = {
namespace: 'optInOut'
};
var options = _extends({}, defaultOptions, userOptions);
var getNamespacedKey = function getNamespacedKey(key) {
// eslint-disable-line arrow-body-style
return key ? options.namespace + '.' + key : options.namespace;
};
var storageAvailable = function () {
try {
var testKey = '__storage_test__';
var testValue = testKey;
localStorage.setItem(testKey, testValue);
localStorage.removeItem(testKey);
return true;
} catch (e) {
return e instanceof DOMException && (
// everything except Firefox
e.code === 22 ||
// Firefox
e.code === 1014 ||
// test name field too, because code might not be present
// everything except Firefox
e.name === 'QuotaExceededError' ||
// Firefox
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
// acknowledge QuotaExceededError only if there's something already stored
localStorage.length !== 0;
}
}();
var getItem = function getItem(service, key) {
if (storageAvailable) {
var data = void 0;
try {
data = JSON.parse(localStorage.getItem(getNamespacedKey(service)));
} catch (err) {
data = null;
}
if (key) {
return data[key] || null;
}
return data || null;
}
return null;
};
var writeValue = function writeValue(key, value) {
if (storageAvailable) {
localStorage.setItem(key, value);
return true;
}
return false;
};
var setItem = function setItem(service, key, value, update) {
if (storageAvailable) {
var currentValue = getItem(service) || {};
var newValue = void 0;
if (update) {
var newData = defineProperty({}, key, value);
newValue = _extends(currentValue, newData);
} else {
newValue = currentValue;
newValue[key] = value;
}
return writeValue(getNamespacedKey(service), JSON.stringify(newValue));
}
return false;
};
var removeItem = function removeItem(service, key) {
if (storageAvailable) {
if (key) {
var currentValue = getItem(service) || {};
var newValue = currentValue;
delete newValue[key];
writeValue(getNamespacedKey(service), JSON.stringify(newValue));
} else {
localStorage.removeItem(getNamespacedKey(service));
}
return true;
}
return false;
};
return {
get: function get$$1(service) {
var key = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
return getItem(service, key);
},
set: function set$$1(service, key, value) {
var update = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
return setItem(service, key, value, update);
},
delete: function _delete(service) {
var key = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
return removeItem(service, key);
}
};
};
/* FROM: https://github.com/jhobz/dnt-polyfill */
/* eslint-disable */
var doNotTrack = function doNotTrack() {
return Boolean(window && (
// Internet Explorer 11 uses window.doNotTrack rather than navigator.doNotTrack.
// Safari 7.1.3+ uses window.doNotTrack rather than navigator.doNotTrack.
window.doNotTrack === '1' || window.navigator && (window.navigator.doNotTrack === '1' ||
// Prior to Gecko 32 Firefox would report navigator.doNotTrack with values of yes and no rather than 1 and 0.
window.navigator.doNotTrack === 'yes' ||
// Internet Explorer 9 and 10 use a vendor prefix, i.e. navigator.msDoNotTrack.
window.navigator.msDoNotTrack === '1')));
};
// storage needs to have get & set methods
// service needs to have mode (optIn|optOut)
var optInOut = function optInOut(userOptions) {
var self = {};
var defaultOptions = {
storages: {
cookie: Storage(),
localStorage: Storage$1()
},
services: {},
plugins: [],
doNotTrack: false,
language: {
undefined: 'undefined',
optedIn: 'opted in',
optedOut: 'opted out',
defaults: {}
}
};
// PRIVATE PROPERTIES
var options = void 0;
var storages = void 0;
var services = void 0;
var events = void 0;
var language = void 0; // eslint-disable-line prefer-const
// PRIVATE METHODS
var setValueInStorages = function setValueInStorages(serviceKey, key, value) {
var storageKey = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
var update = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
if (!services[serviceKey]) throw new Error('service ' + serviceKey + ' is not configured for optInOut');
if (storageKey && storages[storageKey]) {
storages[storageKey].set(serviceKey, key, value);
} else {
Object.keys(storages).forEach(function (storagesKey) {
storages[storagesKey].set(serviceKey, key, value, update);
});
}
};
var triggerEvent = function triggerEvent(event, data) {
var handlers = events[event];
if (!handlers || handlers.length < 1) {
return;
}
handlers.forEach(function (handler) {
handler.call(self, data, event);
});
};
var getIsAllowed = function getIsAllowed(serviceKey) {
var storageKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var service = void 0;
var checkStorages = void 0;
if (serviceKey && services[serviceKey]) {
service = services[serviceKey];
if (storageKey && storages[storageKey]) {
var storagesCopy = _extends({}, storages);
delete storagesCopy[storageKey];
checkStorages = [storageKey].concat(toConsumableArray(Object.keys(storagesCopy)));
} else {
checkStorages = Object.keys(storages);
}
} else {
throw new Error('service ' + serviceKey + ' does not exist/is not configured');
}
var allowed = null; // default
checkStorages.every(function (currentStorageKey) {
var storage = storages[currentStorageKey];
var value = storage.get(serviceKey);
if (typeof value === 'undefined' || value === undefined || value === null) return true; // continue
if (service.mode === 'optIn') {
if (value.optedIn === false) {
// optedIn is explicitly false (has been set, but not to optin)
allowed = false;
} else if (value.optedIn && !value.optedOut) {
// optedIn is set and optedOut is not set
allowed = true;
} else if (value.optedOut && !value.optedIn) {
// optedOut is set and optedIn is not set
allowed = false;
} else if (Date.parse(value.optedOut) < Date.parse(value.optedIn)) {
// both set, optedIn is newer
allowed = true;
} else if (Date.parse(value.optedOut) > Date.parse(value.optedIn)) {
// both set, optedOut is newer
allowed = false;
}
} else if (service.mode === 'optOut') {
if (value.optedIn === undefined || !value.optedIn) {
// optedIn is not set - refer to optOut
allowed = !value.optedOut;
} else if (value.optedIn && !value.optedOut) {
// optIn is explicitly set and optOut not
allowed = true;
} else if (Date.parse(value.optedIn) > Date.parse(value.optedOut)) {
// both set, optedIn is newser
allowed = true;
} else if (Date.parse(value.optedIn) < Date.parse(value.optedOut)) {
// both set, optedOut is newer
allowed = false;
}
}
if (allowed !== null) return false; // break
return true;
});
return allowed; // can be null: means, nothing is set yet
};
// PUBLIC PROPERTIES
// PUBLIC METHODS
self.optIn = function (serviceKey) {
var storageKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var date = new Date();
setValueInStorages(serviceKey, 'optedIn', date, storageKey);
triggerEvent('optIn', {
service: serviceKey, storage: storageKey, date: date, status: self.getStatus(serviceKey, true)
});
};
self.optOut = function (serviceKey) {
var storageKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var date = new Date();
setValueInStorages(serviceKey, 'optedOut', date, storageKey);
triggerEvent('optOut', {
service: serviceKey, storage: storageKey, date: date, status: self.getStatus(serviceKey, false)
});
};
self.getOption = function (optionKey) {
return options[optionKey];
};
self.getStorage = function (storageKey) {
return storages[storageKey];
};
self.getService = function (serviceKey) {
return services[serviceKey];
};
self.getStorages = function () {
return storages;
};
self.getServices = function () {
return services;
};
self.getOptions = function () {
return options;
};
self.getPlugins = function () {
return options.plugins;
};
self.on = function (event, callback) {
var handlers = events[event] || [];
handlers.push(callback);
events[event] = handlers;
};
self.isAllowed = function (serviceKey) {
var storageKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var allowed = getIsAllowed(serviceKey, storageKey);
var service = services[serviceKey];
var defaultAllowed = service.default ? service.default : service.mode !== 'optIn';
if (allowed === null) {
// use do not track or default
if (options.doNotTrack) {
if (doNotTrack()) {
allowed = false;
} else {
allowed = defaultAllowed;
}
} else {
allowed = defaultAllowed;
}
}
return allowed;
};
self.isSet = function (serviceKey) {
var storageKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var allowed = getIsAllowed(serviceKey, storageKey);
if (allowed === null) {
if (options.doNotTrack && doNotTrack()) {
return true;
}
return false;
}
return true;
};
self.getStatus = function (serviceKey) {
var isAllowed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var isCurrentlyAllowed = isAllowed !== null ? isAllowed : getIsAllowed(serviceKey);
if (isCurrentlyAllowed === true) return language.optedIn;else if (isCurrentlyAllowed === false) return language.optedOut;
return [language.undefined, language.defaults[serviceKey] || null];
};
// INIT OBJECT
// PROPERTIES
// options
options = _extends({}, defaultOptions, userOptions);
// init storages
storages = {};
Object.keys(options.storages).forEach(function (storagesKey) {
var storage = options.storages[storagesKey];
if (typeof storage.get === 'function' && typeof storage.set === 'function') {
storages[storagesKey] = options.storages[storagesKey];
}
return true;
});
delete options.storages;
// init services
services = {};
Object.keys(options.services).forEach(function (serviceKey) {
var service = options.services[serviceKey];
if (typeof service.mode !== 'undefined') {
services[serviceKey] = options.services[serviceKey];
}
});
delete options.services;
// init plugins
events = {};
options.plugins.forEach(function (plugin) {
if (typeof plugin === 'function') {
plugin(self);
} else if (typeof plugin.setup === 'function') {
plugin.setup(self);
}
});
// init language
language = _extends( // eslint-disable-line prefer-const
{}, defaultOptions.language, userOptions.language);
// finalizie setup
triggerEvent('init', self);
return self;
};
export default optInOut;