UNPKG

optinout.js

Version:

a simple js framework for handling opt-in and opt-out for tracking & cookies

593 lines (471 loc) 16.3 kB
/* optinout.js - v1.1.2 | Copyright: fabian todt and other contributors | License: https://gitlab.com/optinout/optinout.js */ 'use strict'; 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; }; module.exports = optInOut;