/** * Initialize the SDK with the specified configuration */ function initSdk(config, gvl, sdkURL, languageCode) { if (config) { // Set the global window configuration to the config object window.didomiConfig = prepareConfigFromMobile(config, languageCode); // If app, vendors, and iab exist, set the vendorList property if ( window.didomiConfig.app && window.didomiConfig.app.vendors && window.didomiConfig.app.vendors.iab ) { window.didomiConfig.app.vendors.iab.vendorList = prepareGVLFromMobile(gvl); } } var script = document.createElement("script"); script.setAttribute("type", "text/javascript"); script.setAttribute("src", sdkURL); document.getElementsByTagName("head")[0].appendChild(script); addReadyHandler(); addErrorHandler(); } // Used to trigger a "ready" message when the Web SDK is ready. function addReadyHandler() { window.didomiOnReady = window.didomiOnReady || []; window.didomiOnReady.push(function () { if (isIOS()) { window.webkit.messageHandlers.ready.postMessage(""); } }); } // Used to detect when there's an error loading files so native can dismiss the notice if required. function addErrorHandler() { window.addEventListener('error', function(event) { if (event.target.tagName === 'IMG' || event.target.tagName === 'SCRIPT' || event.target.tagName === 'LINK') { const errorObject = { url: event.target.src || event.target.href, tagName: event.target.tagName, errorMessage: event.message || "Resource failed to load" }; if (isIOS()) { window.webkit.messageHandlers.errorLoadingResource.postMessage(errorObject); } else { androidInterface.onErrorLoadingResource(JSON.stringify(errorObject)); } } }, true); } /** * Open the notice screen */ function openNotice(options) { window.didomiOnReady = window.didomiOnReady || []; window.didomiOnReady.push(function (Didomi) { if (options != null && options.deepLinkView != null) { Didomi.preferences.show( options.deepLinkView == 0 ? "purposes" : "vendors" ); } else { Didomi.notice.show(); } }); window.didomiEventListeners = window.didomiEventListeners || []; // TODO: Handle all iOS events window.didomiEventListeners.push( { event: 'api.error', listener: function ({ id, reason }) { if (window.webkit && window.webkit.messageHandlers) { window.webkit.messageHandlers.onError.postMessage(reason); } else { androidInterface.onError(id, reason); } } }, { event: "consent.changed", listener: function (event) { // If the change is coming from the WebView we don't want to trigger the message back to the WebView. if (event.action == "webview") { return; } if (isIOS()) { window.webkit.messageHandlers.consentChanged.postMessage(Didomi.getUserStatus()); } else { androidInterface.onConsentChanged(JSON.stringify(Didomi.getUserStatus())); } }, }, { event: 'notice.clickagree', listener: function () { if (isIOS()) { window.webkit.messageHandlers.noticeClickAgree.postMessage(""); } else { androidInterface.onNoticeClickAgree(); } } }, { event: 'notice.clickdisagree', listener: function () { if (isIOS()) { window.webkit.messageHandlers.noticeClickDisagree.postMessage(""); } else { androidInterface.onNoticeClickDisagree(); } } }, { event: 'notice.clickmoreinfo', listener: function () { if (isIOS()) { window.webkit.messageHandlers.noticeClickMoreInfo.postMessage(""); } else { androidInterface.onNoticeClickMoreInfo(); } } }, { event: 'notice.clickviewvendors', listener: function () { if (isIOS()) { window.webkit.messageHandlers.noticeClickViewVendors.postMessage(""); } else { androidInterface.onNoticeClickViewVendors(); } } }, { event: 'notice.hidden', listener: function () { if (isIOS()) { if (Didomi.notice.isVisible()) { window.webkit.messageHandlers.noticeHidden.postMessage(); } } else { androidInterface.onNoticeHidden(); } } }, { event: 'notice.shown', listener: function () { if (isIOS()) { window.webkit.messageHandlers.noticeShown.postMessage(""); } else { androidInterface.onNoticeShown(); } } }, { event: 'preferences.clickagreetoall', listener: function () { if (isIOS()) { window.webkit.messageHandlers.preferencesClickAgreeToAll.postMessage(""); } else { androidInterface.onPreferencesClickAgreeToAll(); } } }, { event: 'preferences.clickcategoryagree', listener: function ({ categoryId }) { if (isIOS()) { window.webkit.messageHandlers.preferencesClickCategoryAgree.postMessage(categoryId); } else { androidInterface.onPreferencesClickCategoryAgree(categoryId); } } }, { event: 'preferences.clickcategorydisagree', listener: function ({ categoryId }) { if (isIOS()) { window.webkit.messageHandlers.preferencesClickCategoryDisagree.postMessage(categoryId); } else { androidInterface.onPreferencesClickCategoryDisagree(categoryId); } } }, { event: "preferences.clickclose", listener: function () { // Not handled by mobile SDKs }, }, { event: 'preferences.clickdisagreetoall', listener: function () { if (isIOS()) { window.webkit.messageHandlers.preferencesClickDisagreeToAll.postMessage(""); } else { androidInterface.onPreferencesClickDisagreeToAll(); } } }, { event: 'preferences.clickpurposeagree', listener: function ({ purposeId }) { if (isIOS()) { window.webkit.messageHandlers.preferencesClickPurposeAgree.postMessage(purposeId); } else { androidInterface.onPreferencesClickPurposeAgree(purposeId); } } }, { event: 'preferences.clickpurposedisagree', listener: function ({ purposeId }) { if (isIOS()) { window.webkit.messageHandlers.preferencesClickPurposeDisagree.postMessage(purposeId); } else { androidInterface.onPreferencesClickPurposeDisagree(purposeId); } } }, { event: 'preferences.clicksavechoices', listener: function () { if (isIOS()) { window.webkit.messageHandlers.preferencesClickSaveChoices.postMessage(""); } else { androidInterface.onPreferencesClickSaveChoices(); } } }, { event: 'preferences.clickvendoragree', listener: function ({ vendorId }) { if (isIOS()) { window.webkit.messageHandlers.preferencesClickVendorAgree.postMessage(vendorId); } else { androidInterface.onPreferencesClickVendorAgree(vendorId); } } }, { event: 'preferences.clickvendordisagree', listener: function ({ vendorId }) { if (isIOS()) { window.webkit.messageHandlers.preferencesClickVendorDisagree.postMessage(vendorId); } else { androidInterface.onPreferencesClickVendorDisagree(vendorId); } } }, { event: 'preferences.clickvendorsavechoices', listener: function () { if (isIOS()) { window.webkit.messageHandlers.preferencesClickVendorSaveChoices.postMessage(""); } else { androidInterface.onPreferencesClickVendorSaveChoices(); } } }, { event: 'preferences.clickviewvendors', listener: function () { if (isIOS()) { window.webkit.messageHandlers.preferencesClickViewVendors.postMessage(""); } else { androidInterface.onPreferencesClickViewVendors(); } } }, { event: "preferences.hidden", listener: function () { if (Didomi.notice.isVisible()) { if (isIOS()) { window.webkit.messageHandlers.preferencesHidden.postMessage(""); } else { androidInterface.onPreferencesHidden(); } } else { if (isIOS()) { window.webkit.messageHandlers.dismissWebView.postMessage(""); } else { androidInterface.onDismissPreferences(); } } } }, { event: "preferences.shown", listener: function () { if (isIOS()) { window.webkit.messageHandlers.preferencesShown.postMessage(""); } else { androidInterface.onPreferencesShown(); } } }, ); } /** * Show the Preferences screen programmatically after the notice was already opened */ function showPreferences() { window.didomiOnReady = window.didomiOnReady || []; window.didomiOnReady.push(function (Didomi) { Didomi.preferences.show(); }); } /** * Click on the 1st visible slider button if it exists. * Used to cache both states of the slider button. */ function toggleFirstSlider() { new Promise(function(resolve) { setTimeout(() => { // Add delay to let time for the element to be attached var element = document.getElementsByClassName('didomi-switch')[0]; if (element) { element.click(); resolve(true); } else { resolve(false); } }, 100); }).then((result) => { enabledToggleIsCachedOrNotRequired(result); }); } /** * If the preferences page shows toggles, we let the native code know that the enable toggle image should be loaded on the page and cached now. * If the preferences page does not show toggles, we also let native know that we can continue. * @param {*} result whether the toggle has been found and clicked or not. */ function enabledToggleIsCachedOrNotRequired(result) { if (isIOS()) { window.webkit.messageHandlers.enabledToggleIsCached.postMessage(""); } else if (!result) { androidInterface.onSliderNotPresent(); } } /** * Hide the Preferences screen programmatically */ function hidePreferences() { window.didomiOnReady = window.didomiOnReady || []; window.didomiOnReady.push(function (Didomi) { Didomi.preferences.hide(); }); } /** * Hide the Vendors screen programmatically */ function hideVendors() { // preferences.show() command will display the purposes screen with no changes in user choices showPreferences(); } /** * Function used to convert objects that are mapped by ID into an array of objects that only contain IDs. * @param {*} object JSON object. * @returns array of objects that only contain an ID. */ function convertObjectToArrayWithIDs(object) { if (!object) { return []; } return Object.keys(object).map(function (id) { return { id: Number(id), }; }); } /** * Remove all accents and special characters from id field * @param {string} id * @returns the sanitized id */ function sanitizeID(id) { return id // Remove accents and diacritics .normalize('NFD') .replace(/[\u0300-\u036f]/g, '') // Remove special characters .replace(/[^a-zA-Z0-9_-]/g, ''); } /** * Fix unescaped quotes and html tags. * @param {string} text * @returns the sanitized text */ function sanitizeText(text) { return text // Escape unescaped quotes .replace(/(?]/g, ''); } /** * Function used to sanitize custom fields in config. * @param {*} jsonConfig * @returns the sanitized config */ function sanitizeConfig(jsonConfig) { const resultJson = JSON.parse(JSON.stringify(jsonConfig)); if (resultJson && resultJson.app) { if(resultJson.app.vendors && resultJson.app.vendors.custom && Array.isArray(resultJson.app.vendors.custom)) { resultJson.app.vendors.custom = resultJson.app.vendors.custom.map(vendor => { if (vendor.id) { vendor.id = sanitizeID(vendor.id); } if (vendor.purposeIds) { vendor.purposeIds = vendor.purposeIds.map(purposeID => sanitizeID(purposeID)); } if (vendor.legIntPurposeIds) { vendor.legIntPurposeIds = vendor.legIntPurposeIds.map(purposeID => sanitizeID(purposeID)); } if (vendor.name) { vendor.name = sanitizeText(vendor.name); } return vendor; }); } if (resultJson.app.customPurposes) { resultJson.app.customPurposes = resultJson.app.customPurposes.map(purpose => { if (purpose.id) { purpose.id = sanitizeID(purpose.id); } if (purpose.name) { for (const key in purpose.name) { if (purpose.name.hasOwnProperty(key)) { purpose.name[key] = sanitizeText(purpose.name[key]); } } } if (purpose.description) { for (const key in purpose.description) { if (purpose.description.hasOwnProperty(key)) { purpose.description[key] = sanitizeText(purpose.description[key]); } } } if (purpose.descriptionLegal) { for (const key in purpose.descriptionLegal) { if (purpose.descriptionLegal.hasOwnProperty(key)) { purpose.descriptionLegal[key] = sanitizeText(purpose.descriptionLegal[key]); } } } return purpose; }); } } return resultJson; } /** * Check if the platform is iOS. * @returns true if the platform is iOS, false otherwise. */ function isIOS() { return window.webkit != null && window.webkit.messageHandlers != null; } /** * Prepare config file to be consumed by the Web SDK, by disabling the features already handled by Mobile SDKs. * @param {*} configFromMobile Config provided by mobile. * @param {string} languageCode language code to be set in the Config. * @returns Config with unneeded features disabled */ function prepareConfigFromMobile(configFromMobile, languageCode) { if (configFromMobile == null) { return null; } configFromMobile = sanitizeConfig(configFromMobile); // If the `events` object is not defined, we define it and make sure the enabled property is set to `false`. configFromMobile.events = configFromMobile.events || {}; configFromMobile.events.enabled = false; if (configFromMobile.sync) { configFromMobile.sync.enabled = false; } if (configFromMobile.app && configFromMobile.app.consentString) { configFromMobile.app.consentString.signatureEnabled = false; } if (languageCode) { configFromMobile.languages = { enabled: [languageCode], default: languageCode }; } return configFromMobile; } /** * Prepare GVL to be consumed by the Web SDK based on the format used by Mobile SDKs. * @param {*} gvlFromMobile GVL in the format used by mobile. * @returns GVL in the format used by web. */ function prepareGVLFromMobile(gvlFromMobile) { if (gvlFromMobile == null) { return null; } gvlFromMobile.purposes = convertObjectToArrayWithIDs(gvlFromMobile.purposes); gvlFromMobile.specialPurposes = convertObjectToArrayWithIDs(gvlFromMobile.specialPurposes); gvlFromMobile.features = convertObjectToArrayWithIDs(gvlFromMobile.features); gvlFromMobile.specialFeatures = convertObjectToArrayWithIDs(gvlFromMobile.specialFeatures); gvlFromMobile.dataCategories = convertObjectToArrayWithIDs(gvlFromMobile.dataCategories); gvlFromMobile.stacks = Object.keys(gvlFromMobile.stacks).map(function (key) { var stack = gvlFromMobile.stacks[key]; return { id: stack.id, purposeIds: stack.purposes || [], specialFeatureIds: stack.specialFeatures || [], }; }); gvlFromMobile.vendors = Object.keys(gvlFromMobile.vendors).map(function (key) { var vendor = gvlFromMobile.vendors[key]; var { purposes, flexiblePurposes, specialPurposes, legIntPurposes, features, specialFeatures, ...rest } = vendor; return { ...rest, purposeIds: purposes || [], flexiblePurposeIds: flexiblePurposes || [], specialPurposeIds: specialPurposes || [], legIntPurposeIds: legIntPurposes || [], featureIds: features || [], specialFeatureIds: specialFeatures || [], tmpDeletedDate: rest.deletedDate, }; }); return gvlFromMobile; }