UNPKG

optinout.js

Version:

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

255 lines (212 loc) 7.32 kB
import cookieStorage from './storages/cookie'; import localStorage from './storages/localStorage'; import doNotTrack from './helper/doNotTrack'; // storage needs to have get & set methods // service needs to have mode (optIn|optOut) const optInOut = (userOptions) => { const self = {}; const defaultOptions = { storages: { cookie: cookieStorage(), localStorage: localStorage(), }, services: {}, plugins: [], doNotTrack: false, language: { undefined: 'undefined', optedIn: 'opted in', optedOut: 'opted out', defaults: {}, }, }; // PRIVATE PROPERTIES let options; let storages; let services; let events; let language; // eslint-disable-line prefer-const // PRIVATE METHODS const setValueInStorages = (serviceKey, key, value, storageKey = false, update = 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((storagesKey) => { storages[storagesKey].set(serviceKey, key, value, update); }); } }; const triggerEvent = (event, data) => { const handlers = events[event]; if (!handlers || handlers.length < 1) { return; } handlers.forEach((handler) => { handler.call(self, data, event); }); }; const getIsAllowed = (serviceKey, storageKey = false) => { let service; let checkStorages; if (serviceKey && services[serviceKey]) { service = services[serviceKey]; if (storageKey && storages[storageKey]) { const storagesCopy = Object.assign({}, storages); delete storagesCopy[storageKey]; checkStorages = [storageKey, ...Object.keys(storagesCopy)]; } else { checkStorages = Object.keys(storages); } } else { throw new Error(`service ${serviceKey} does not exist/is not configured`); } let allowed = null; // default checkStorages.every((currentStorageKey) => { const storage = storages[currentStorageKey]; const 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 = (serviceKey, storageKey = false) => { const date = new Date(); setValueInStorages(serviceKey, 'optedIn', date, storageKey); triggerEvent('optIn', { service: serviceKey, storage: storageKey, date, status: self.getStatus(serviceKey, true), }); }; self.optOut = (serviceKey, storageKey = false) => { const date = new Date(); setValueInStorages(serviceKey, 'optedOut', date, storageKey); triggerEvent('optOut', { service: serviceKey, storage: storageKey, date, status: self.getStatus(serviceKey, false), }); }; self.getOption = optionKey => options[optionKey]; self.getStorage = storageKey => storages[storageKey]; self.getService = serviceKey => services[serviceKey]; self.getStorages = () => storages; self.getServices = () => services; self.getOptions = () => options; self.getPlugins = () => options.plugins; self.on = (event, callback) => { const handlers = events[event] || []; handlers.push(callback); events[event] = handlers; }; self.isAllowed = (serviceKey, storageKey = false) => { let allowed = getIsAllowed(serviceKey, storageKey); const service = services[serviceKey]; const 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 = (serviceKey, storageKey = false) => { const allowed = getIsAllowed(serviceKey, storageKey); if (allowed === null) { if (options.doNotTrack && doNotTrack()) { return true; } return false; } return true; }; self.getStatus = (serviceKey, isAllowed = null) => { const 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 = Object.assign({}, defaultOptions, userOptions); // init storages storages = {}; Object.keys(options.storages).forEach((storagesKey) => { const 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((serviceKey) => { const service = options.services[serviceKey]; if (typeof service.mode !== 'undefined') { services[serviceKey] = options.services[serviceKey]; } }); delete options.services; // init plugins events = {}; options.plugins.forEach((plugin) => { if (typeof plugin === 'function') { plugin(self); } else if (typeof plugin.setup === 'function') { plugin.setup(self); } }); // init language language = Object.assign( // eslint-disable-line prefer-const {}, defaultOptions.language, userOptions.language, ); // finalizie setup triggerEvent('init', self); return self; }; export default optInOut;