diff --git a/.size-limit.js b/.size-limit.js index 647c1d884c..a12a3dfdad 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -4,21 +4,21 @@ */ module.exports = [ { - name: "Core - CDN", - path: "dist/rudder-analytics.min.js", + name: 'Core - CDN', + path: 'dist/rudder-analytics.min.js', gzip: true, - limit: "38 kB", + limit: '38 kB', }, { - name: "All Integrations - CDN", - path: "dist/integrations/*.min.js", + name: 'All Integrations - CDN', + path: 'dist/integrations/*.min.js', gzip: true, - limit: "625 kB", + limit: "650 kB", }, { - name: "Core - NPM", - path: "dist/rudder-sdk-js/index.js", + name: 'Core - NPM', + path: 'dist/rudder-sdk-js/index.js', gzip: true, - limit: "38 kB", + limit: '38 kB', }, ]; diff --git a/analytics.js b/analytics.js index 362aba6cca..77d5ba42f8 100644 --- a/analytics.js +++ b/analytics.js @@ -10,14 +10,15 @@ /* eslint-disable no-unused-expressions */ /* eslint-disable import/extensions */ /* eslint-disable no-param-reassign */ -import Emitter from "component-emitter"; -import { parse } from "component-querystring"; -import merge from "lodash.merge"; -import cloneDeep from "lodash.clonedeep"; +import Emitter from 'component-emitter'; +import { parse } from 'component-querystring'; +import merge from 'lodash.merge'; +import cloneDeep from 'lodash.clonedeep'; import { getJSONTrimmed, generateUUID, handleError, + leaveBreadcrumb, getDefaultPageProperties, getUserProvidedConfigUrl, findAllEnabledDestinations, @@ -30,7 +31,8 @@ import { getConfigUrl, checkSDKUrl, commonNames, -} from "./utils/utils"; + get, +} from './utils/utils'; import { MAX_WAIT_FOR_INTEGRATION_LOAD, INTEGRATION_LOAD_CHECK_INTERVAL, @@ -38,15 +40,18 @@ import { CDN_INT_DIR, INTG_SUFFIX, POLYFILL_URL, -} from "./utils/constants"; -import RudderElementBuilder from "./utils/RudderElementBuilder"; -import Storage from "./utils/storage"; -import { EventRepository } from "./utils/EventRepository"; -import logger from "./utils/logUtil"; -import ScriptLoader from "./integrations/ScriptLoader"; -import parseLinker from "./utils/linker"; -import { configToIntNames } from "./utils/config_to_integration_names"; -import CookieConsentFactory from "./cookieConsent/CookieConsentFactory"; + DEFAULT_ERROR_REPORT_PROVIDER, + ERROR_REPORT_PROVIDERS, +} from './utils/constants'; +import RudderElementBuilder from './utils/RudderElementBuilder'; +import Storage from './utils/storage'; +import { EventRepository } from './utils/EventRepository'; +import logger from './utils/logUtil'; +import ScriptLoader from './integrations/ScriptLoader'; +import parseLinker from './utils/linker'; +import { configToIntNames } from './utils/config_to_integration_names'; +import CookieConsentFactory from './cookieConsent/CookieConsentFactory'; +import * as BugsnagLib from './metrics/error-report/Bugsnag'; /** * class responsible for handling core @@ -74,7 +79,7 @@ class Analytics { // Array to store the callback functions registered in the ready API this.readyCallbacks = []; this.methodToCallbackMapping = { - syncPixel: "syncPixelCallback", + syncPixel: 'syncPixelCallback', }; this.loaded = false; this.loadIntegration = true; @@ -91,13 +96,13 @@ class Analytics { */ initializeUser(anonymousIdOptions) { // save once for storing older values to encrypted - this.userId = this.storage.getUserId() || ""; + this.userId = this.storage.getUserId() || ''; this.storage.setUserId(this.userId); this.userTraits = this.storage.getUserTraits() || {}; this.storage.setUserTraits(this.userTraits); - this.groupId = this.storage.getGroupId() || ""; + this.groupId = this.storage.getGroupId() || ''; this.storage.setGroupId(this.groupId); this.groupTraits = this.storage.getGroupTraits() || {}; @@ -114,9 +119,7 @@ class Analytics { ) { const initialReferrer = getReferrer(); this.storage.setInitialReferrer(initialReferrer); - this.storage.setInitialReferringDomain( - getReferringDomain(initialReferrer) - ); + this.storage.setInitialReferringDomain(getReferringDomain(initialReferrer)); } } @@ -125,9 +128,8 @@ class Analytics { if ( this.clientIntegrations.every( (intg) => - this.dynamicallyLoadedIntegrations[ - `${configToIntNames[intg.name]}${INTG_SUFFIX}` - ] != undefined + this.dynamicallyLoadedIntegrations[`${configToIntNames[intg.name]}${INTG_SUFFIX}`] != + undefined, ) ) { // logger.debug( @@ -143,9 +145,7 @@ class Analytics { return this.pause(INTEGRATION_LOAD_CHECK_INTERVAL).then(() => { // logger.debug("Check if all integration SDKs are loaded after pause") - return this.allModulesInitialized( - time + INTEGRATION_LOAD_CHECK_INTERVAL - ).then(resolve); + return this.allModulesInitialized(time + INTEGRATION_LOAD_CHECK_INTERVAL).then(resolve); }); }); } @@ -169,10 +169,33 @@ class Analytics { processResponse(status, response) { try { // logger.debug(`===in process response=== ${status}`) - if (typeof response === "string") { + if (typeof response === 'string') { response = JSON.parse(response); } + // Fetch Error reporting enable option from sourceConfig + const isErrorReportEnabled = get( + response.source.config, + 'statsCollection.errorReports.enabled', + ); + + // Load Bugsnag only if it is enabled in the source config + if (isErrorReportEnabled === true) { + // Fetch the name of the Error reporter from sourceConfig + const provider = + get(response.source.config, 'statsCollection.errorReports.provider') || + DEFAULT_ERROR_REPORT_PROVIDER; + if (!ERROR_REPORT_PROVIDERS.includes(provider)) { + logger.error('Invalid error reporting provider value'); + } + + if (provider === 'bugsnag') { + // Load Bugsnag client SDK + BugsnagLib.load(); + BugsnagLib.init(response.source.id); + } + } + response.source.destinations.forEach(function (destination, index) { // logger.debug( // `Destination ${index} Enabled? ${destination.enabled} Type: ${destination.destinationDefinition.name} Use Native SDK? true` @@ -188,16 +211,14 @@ class Analytics { // intersection of config-plane native sdk destinations with sdk load time destination list this.clientIntegrations = findAllEnabledDestinations( this.loadOnlyIntegrations, - this.clientIntegrations + this.clientIntegrations, ); // Check if cookie consent manager is being set through load options if (Object.keys(this.cookieConsentOptions).length) { // Call the cookie consent factory to initialise and return the type of cookie // consent being set. For now we only support OneTrust. try { - const cookieConsent = CookieConsentFactory.initialize( - this.cookieConsentOptions - ); + const cookieConsent = CookieConsentFactory.initialize(this.cookieConsentOptions); // If cookie consent object is return we filter according to consents given by user // else we do not consider any filtering for cookie consent. this.clientIntegrations = this.clientIntegrations.filter((intg) => { @@ -207,18 +228,19 @@ class Analytics { ); }); } catch (e) { - logger.error(e); + handleError(e); } } - let suffix = ""; // default suffix + let suffix = ''; // default suffix // Get the CDN base URL is rudder staging url const { rudderSDK, staging } = checkSDKUrl(); if (rudderSDK && staging) { - suffix = "-staging"; // stagging suffix + suffix = '-staging'; // stagging suffix } + leaveBreadcrumb('Starting device-mode initialization'); // logger.debug("this.clientIntegrations: ", this.clientIntegrations) // Load all the client integrations dynamically this.clientIntegrations.forEach((intg) => { @@ -240,10 +262,9 @@ class Analytics { let intgInstance; try { - // logger.debug( - // pluginName, - // " [Analytics] processResponse :: trying to initialize integration ::" - // ); + const msg = `[Analytics] processResponse :: trying to initialize integration name:: ${pluginName}`; + // logger.debug(msg); + leaveBreadcrumb(msg); intgInstance = new intMod[modName](intg.config, self); intgInstance.init(); @@ -251,15 +272,11 @@ class Analytics { self.isInitialized(intgInstance).then(() => { // logger.debug(pluginName, " module init sequence complete"); - self.dynamicallyLoadedIntegrations[pluginName] = - intMod[modName]; + self.dynamicallyLoadedIntegrations[pluginName] = intMod[modName]; }); } catch (e) { - logger.error( - pluginName, - " [Analytics] initialize integration (integration.init()) failed", - e - ); + e.message = `[Analytics] 'integration.init()' failed :: ${pluginName} :: ${e.message}`; + handleError(e); self.failedToBeLoadedIntegration.push(intgInstance); } } @@ -298,6 +315,7 @@ class Analytics { // " failed loaded count: ", // object.failedToBeLoadedIntegration.length // ); + leaveBreadcrumb(`Started replaying buffered events`); // eslint-disable-next-line no-param-reassign object.clientIntegrationObjects = []; // eslint-disable-next-line no-param-reassign @@ -309,16 +327,12 @@ class Analytics { // object.clientIntegrationObjects.length // ); - if ( - object.clientIntegrationObjects.every( - (intg) => !intg.isReady || intg.isReady() - ) - ) { - // Integrations are ready - // set clientIntegrationsReady to be true - object.clientIntegrationsReady = true; - // Execute the callbacks if any - object.executeReadyCallback(); + if (object.clientIntegrationObjects.every((intg) => !intg.isReady || intg.isReady())) { + // Integrations are ready + // set clientIntegrationsReady to be true + object.clientIntegrationsReady = true; + // Execute the callbacks if any + object.executeReadyCallback(); } // send the queued events to the fetched integration @@ -336,37 +350,30 @@ class Analytics { // get intersection between config plane native enabled destinations // (which were able to successfully load on the page) vs user supplied integrations - const succesfulLoadedIntersectClientSuppliedIntegrations = - findAllEnabledDestinations( - clientSuppliedIntegrations, - object.clientIntegrationObjects - ); + const succesfulLoadedIntersectClientSuppliedIntegrations = findAllEnabledDestinations( + clientSuppliedIntegrations, + object.clientIntegrationObjects, + ); // send to all integrations now from the 'toBeProcessedByIntegrationArray' replay queue - for ( - let i = 0; - i < succesfulLoadedIntersectClientSuppliedIntegrations.length; - i += 1 - ) { + for (let i = 0; i < succesfulLoadedIntersectClientSuppliedIntegrations.length; i += 1) { try { if ( !succesfulLoadedIntersectClientSuppliedIntegrations[i].isFailed || !succesfulLoadedIntersectClientSuppliedIntegrations[i].isFailed() ) { - if ( - succesfulLoadedIntersectClientSuppliedIntegrations[i][methodName] - ) { + if (succesfulLoadedIntersectClientSuppliedIntegrations[i][methodName]) { const sendEvent = !object.IsEventBlackListed( event[0].message.event, - succesfulLoadedIntersectClientSuppliedIntegrations[i].name + succesfulLoadedIntersectClientSuppliedIntegrations[i].name, ); // Block the event if it is blacklisted for the device-mode destination if (sendEvent) { const clonedBufferEvent = cloneDeep(event); - succesfulLoadedIntersectClientSuppliedIntegrations[i][ - methodName - ](...clonedBufferEvent); + succesfulLoadedIntersectClientSuppliedIntegrations[i][methodName]( + ...clonedBufferEvent, + ); } } } @@ -399,10 +406,7 @@ class Analytics { return this.pause(INTEGRATION_LOAD_CHECK_INTERVAL).then(() => { // logger.debug("====after pause, again checking====") - return this.isInitialized( - instance, - time + INTEGRATION_LOAD_CHECK_INTERVAL - ).then(resolve); + return this.isInitialized(instance, time + INTEGRATION_LOAD_CHECK_INTERVAL).then(resolve); }); }); } @@ -418,27 +422,22 @@ class Analytics { * @memberof Analytics */ page(category, name, properties, options, callback) { + leaveBreadcrumb(`Page event`); if (!this.loaded) return; - if (typeof options === "function") (callback = options), (options = null); - if (typeof properties === "function") - (callback = properties), (options = properties = null); - if (typeof name === "function") - (callback = name), (options = properties = name = null); - if ( - typeof category === "object" && - category != null && - category != undefined - ) + if (typeof options === 'function') (callback = options), (options = null); + if (typeof properties === 'function') (callback = properties), (options = properties = null); + if (typeof name === 'function') (callback = name), (options = properties = name = null); + if (typeof category === 'object' && category != null && category != undefined) (options = name), (properties = category), (name = category = null); - if (typeof name === "object" && name != null && name != undefined) + if (typeof name === 'object' && name != null && name != undefined) (options = properties), (properties = name), (name = null); - if (typeof category === "string" && typeof name !== "string") + if (typeof category === 'string' && typeof name !== 'string') (name = category), (category = null); - if (this.sendAdblockPage && category != "RudderJS-Initiated") { + if (this.sendAdblockPage && category != 'RudderJS-Initiated') { this.sendSampleRequest(); } - const rudderElement = new RudderElementBuilder().setType("page").build(); + const rudderElement = new RudderElementBuilder().setType('page').build(); if (!properties) { properties = {}; } @@ -450,12 +449,7 @@ class Analytics { } rudderElement.message.properties = this.getPageProperties(properties); - this.processAndSendDataToDestinations( - "page", - rudderElement, - options, - callback - ); + this.processAndSendDataToDestinations('page', rudderElement, options, callback); } /** @@ -468,23 +462,19 @@ class Analytics { * @memberof Analytics */ track(event, properties, options, callback) { + leaveBreadcrumb(`Track event`); if (!this.loaded) return; - if (typeof options === "function") (callback = options), (options = null); - if (typeof properties === "function") + if (typeof options === 'function') (callback = options), (options = null); + if (typeof properties === 'function') (callback = properties), (options = null), (properties = null); - const rudderElement = new RudderElementBuilder().setType("track").build(); + const rudderElement = new RudderElementBuilder().setType('track').build(); if (event) { rudderElement.setEventName(event); } rudderElement.setProperty(properties || {}); - this.processAndSendDataToDestinations( - "track", - rudderElement, - options, - callback - ); + this.processAndSendDataToDestinations('track', rudderElement, options, callback); } /** @@ -497,12 +487,11 @@ class Analytics { * @memberof Analytics */ identify(userId, traits, options, callback) { + leaveBreadcrumb(`Identify event`); if (!this.loaded) return; - if (typeof options === "function") (callback = options), (options = null); - if (typeof traits === "function") - (callback = traits), (options = null), (traits = null); - if (typeof userId === "object") - (options = traits), (traits = userId), (userId = this.userId); + if (typeof options === 'function') (callback = options), (options = null); + if (typeof traits === 'function') (callback = traits), (options = null), (traits = null); + if (typeof userId === 'object') (options = traits), (traits = userId), (userId = this.userId); if (userId && this.userId && userId !== this.userId) { this.reset(); @@ -516,16 +505,9 @@ class Analytics { } this.storage.setUserTraits(this.userTraits); } - const rudderElement = new RudderElementBuilder() - .setType("identify") - .build(); - - this.processAndSendDataToDestinations( - "identify", - rudderElement, - options, - callback - ); + const rudderElement = new RudderElementBuilder().setType('identify').build(); + + this.processAndSendDataToDestinations('identify', rudderElement, options, callback); } /** @@ -536,23 +518,17 @@ class Analytics { * @param {*} callback */ alias(to, from, options, callback) { + leaveBreadcrumb(`Alias event`); if (!this.loaded) return; - if (typeof options === "function") (callback = options), (options = null); - if (typeof from === "function") - (callback = from), (options = null), (from = null); - if (typeof from === "object") (options = from), (from = null); - - const rudderElement = new RudderElementBuilder().setType("alias").build(); - rudderElement.message.previousId = - from || (this.userId ? this.userId : this.getAnonymousId()); + if (typeof options === 'function') (callback = options), (options = null); + if (typeof from === 'function') (callback = from), (options = null), (from = null); + if (typeof from === 'object') (options = from), (from = null); + + const rudderElement = new RudderElementBuilder().setType('alias').build(); + rudderElement.message.previousId = from || (this.userId ? this.userId : this.getAnonymousId()); rudderElement.message.userId = to; - this.processAndSendDataToDestinations( - "alias", - rudderElement, - options, - callback - ); + this.processAndSendDataToDestinations('alias', rudderElement, options, callback); } /** @@ -563,19 +539,19 @@ class Analytics { * @param {*} callback */ group(groupId, traits, options, callback) { + leaveBreadcrumb(`Group event`); if (!this.loaded) return; if (!arguments.length) return; - if (typeof options === "function") (callback = options), (options = null); - if (typeof traits === "function") - (callback = traits), (options = null), (traits = null); - if (typeof groupId === "object") + if (typeof options === 'function') (callback = options), (options = null); + if (typeof traits === 'function') (callback = traits), (options = null), (traits = null); + if (typeof groupId === 'object') (options = traits), (traits = groupId), (groupId = this.groupId); this.groupId = groupId; this.storage.setGroupId(this.groupId); - const rudderElement = new RudderElementBuilder().setType("group").build(); + const rudderElement = new RudderElementBuilder().setType('group').build(); if (traits) { for (const key in traits) { this.groupTraits[key] = traits[key]; @@ -585,25 +561,17 @@ class Analytics { } this.storage.setGroupTraits(this.groupTraits); - this.processAndSendDataToDestinations( - "group", - rudderElement, - options, - callback - ); + this.processAndSendDataToDestinations('group', rudderElement, options, callback); } IsEventBlackListed(eventName, intgName) { - if (!eventName || !(typeof eventName === "string")) { + if (!eventName || !(typeof eventName === 'string')) { return false; } const sdkIntgName = commonNames[intgName]; - const intg = this.clientIntegrations.find( - (intg) => intg.name === sdkIntgName - ); + const intg = this.clientIntegrations.find((intg) => intg.name === sdkIntgName); - const { blacklistedEvents, whitelistedEvents, eventFilteringOption } = - intg.config; + const { blacklistedEvents, whitelistedEvents, eventFilteringOption } = intg.config; if (!eventFilteringOption) { return false; @@ -612,32 +580,30 @@ class Analytics { const formattedEventName = eventName.trim().toUpperCase(); switch (eventFilteringOption) { // disabled filtering - case "disable": + case 'disable': return false; // Blacklist is choosen for filtering events - case "blacklistedEvents": + case 'blacklistedEvents': if (Array.isArray(blacklistedEvents)) { - return blacklistedEvents.find( - (eventObj) => - eventObj.eventName.trim().toUpperCase() === formattedEventName - ) === undefined - ? false - : true; - } else { - return false; + return ( + blacklistedEvents.find( + (eventObj) => eventObj.eventName.trim().toUpperCase() === formattedEventName, + ) !== undefined + ); } + return false; + // Whitelist is choosen for filtering events - case "whitelistedEvents": + case 'whitelistedEvents': if (Array.isArray(whitelistedEvents)) { - return whitelistedEvents.find( - (eventObj) => - eventObj.eventName.trim().toUpperCase() === formattedEventName - ) === undefined - ? true - : false; - } else { - return true; + return ( + whitelistedEvents.find( + (eventObj) => eventObj.eventName.trim().toUpperCase() === formattedEventName, + ) === undefined + ); } + return true; + default: return false; } @@ -659,7 +625,7 @@ class Analytics { // assign page properties to context // rudderElement.message.context.page = getDefaultPageProperties(); - + leaveBreadcrumb('Started sending data to destinations'); rudderElement.message.context.traits = { ...this.userTraits, }; @@ -670,7 +636,7 @@ class Analytics { ? rudderElement.message.userId : this.userId; - if (type == "group") { + if (type == 'group') { if (this.groupId) { rudderElement.message.groupId = this.groupId; } @@ -702,21 +668,17 @@ class Analytics { // get intersection between config plane native enabled destinations // (which were able to successfully load on the page) vs user supplied integrations - const succesfulLoadedIntersectClientSuppliedIntegrations = - findAllEnabledDestinations( - clientSuppliedIntegrations, - this.clientIntegrationObjects - ); + const succesfulLoadedIntersectClientSuppliedIntegrations = findAllEnabledDestinations( + clientSuppliedIntegrations, + this.clientIntegrationObjects, + ); // try to first send to all integrations, if list populated from BE - try { - succesfulLoadedIntersectClientSuppliedIntegrations.forEach((obj) => { + succesfulLoadedIntersectClientSuppliedIntegrations.forEach((obj) => { + try { if (!obj.isFailed || !obj.isFailed()) { if (obj[type]) { - let sendEvent = !this.IsEventBlackListed( - rudderElement.message.event, - obj.name - ); + let sendEvent = !this.IsEventBlackListed(rudderElement.message.event, obj.name); // Block the event if it is blacklisted for the device-mode destination if (sendEvent) { @@ -725,10 +687,11 @@ class Analytics { } } } - }); - } catch (err) { - handleError({ message: `[sendToNative]:${err}` }); - } + } catch (err) { + err.message = `[sendToNative]::[Destination:${obj.name}]:: ${err}`; + handleError(err); + } + }); } // convert integrations object to server identified names, kind of hack now! @@ -748,11 +711,11 @@ class Analytics { utm(query) { // Remove leading ? if present - if (query.charAt(0) === "?") { + if (query.charAt(0) === '?') { query = query.substring(1); } - query = query.replace(/\?/g, "&"); + query = query.replace(/\?/g, '&'); let param; const params = parse(query); @@ -760,9 +723,9 @@ class Analytics { for (const key in params) { if (Object.prototype.hasOwnProperty.call(params, key)) { - if (key.substr(0, 4) === "utm_") { + if (key.substr(0, 4) === 'utm_') { param = key.substr(4); - if (param === "campaign") param = "name"; + if (param === 'campaign') param = 'name'; results[param] = params[key]; } } @@ -777,7 +740,7 @@ class Analytics { */ addCampaignInfo(rudderElement) { const msgContext = rudderElement.message.context; - if (msgContext && typeof msgContext === "object") { + if (msgContext && typeof msgContext === 'object') { const { search } = getDefaultPageProperties(); rudderElement.message.context.campaign = this.utm(search); } @@ -800,29 +763,23 @@ class Analytics { // assign page properties to context.page rudderElement.message.context.page = this.getContextPageProperties( - type === "page" ? properties : undefined + type === 'page' ? properties : undefined, ); - const topLevelElements = [ - "integrations", - "anonymousId", - "originalTimestamp", - ]; + const topLevelElements = ['integrations', 'anonymousId', 'originalTimestamp']; for (const key in options) { if (topLevelElements.includes(key)) { rudderElement.message[key] = options[key]; - } else if (key !== "context") { + } else if (key !== 'context') { rudderElement.message.context = merge(rudderElement.message.context, { [key]: options[key], }); - } else if (typeof options[key] === "object" && options[key] != null) { + } else if (typeof options[key] === 'object' && options[key] != null) { rudderElement.message.context = merge(rudderElement.message.context, { ...options[key], }); } else { - logger.error( - "[Analytics: processOptionsParam] context passed in options is not object" - ); + logger.error('[Analytics: processOptionsParam] context passed in options is not object'); } } } @@ -832,8 +789,7 @@ class Analytics { const optionPageProperties = (options && options.page) || {}; for (const key in defaultPageProperties) { if (properties[key] === undefined) { - properties[key] = - optionPageProperties[key] || defaultPageProperties[key]; + properties[key] = optionPageProperties[key] || defaultPageProperties[key]; } } return properties; @@ -845,9 +801,7 @@ class Analytics { const contextPageProperties = {}; for (const key in defaultPageProperties) { contextPageProperties[key] = - properties && properties[key] - ? properties[key] - : defaultPageProperties[key]; + properties && properties[key] ? properties[key] : defaultPageProperties[key]; } return contextPageProperties; } @@ -858,13 +812,15 @@ class Analytics { * @memberof Analytics */ reset(flag) { + leaveBreadcrumb(`reset API :: flag: ${flag}`); + if (!this.loaded) return; if (flag) { - this.anonymousId = ""; + this.anonymousId = ''; } - this.userId = ""; + this.userId = ''; this.userTraits = {}; - this.groupId = ""; + this.groupId = ''; this.groupTraits = {}; this.storage.clear(flag); } @@ -906,33 +862,21 @@ class Analytics { */ setAnonymousId(anonymousId, rudderAmpLinkerParm) { // if (!this.loaded) return; - const parsedAnonymousIdObj = rudderAmpLinkerParm - ? parseLinker(rudderAmpLinkerParm) - : null; - const parsedAnonymousId = parsedAnonymousIdObj - ? parsedAnonymousIdObj.rs_amp_id - : null; + const parsedAnonymousIdObj = rudderAmpLinkerParm ? parseLinker(rudderAmpLinkerParm) : null; + const parsedAnonymousId = parsedAnonymousIdObj ? parsedAnonymousIdObj.rs_amp_id : null; this.anonymousId = anonymousId || parsedAnonymousId || generateUUID(); this.storage.setAnonymousId(this.anonymousId); } isValidWriteKey(writeKey) { - if ( - !writeKey || - typeof writeKey !== "string" || - writeKey.trim().length == 0 - ) { + if (!writeKey || typeof writeKey !== 'string' || writeKey.trim().length == 0) { return false; } return true; } isValidServerUrl(serverUrl) { - if ( - !serverUrl || - typeof serverUrl !== "string" || - serverUrl.trim().length == 0 - ) { + if (!serverUrl || typeof serverUrl !== 'string' || serverUrl.trim().length == 0) { return false; } return true; @@ -951,16 +895,12 @@ class Analytics { logger.setLogLevel(options.logLevel); } if (!this.storage || Object.keys(this.storage).length === 0) { - throw Error("Cannot proceed as no storage is available"); + throw Error('Cannot proceed as no storage is available'); } if (options && options.cookieConsentManager) this.cookieConsentOptions = cloneDeep(options.cookieConsentManager); if (!this.isValidWriteKey(writeKey) || !this.isValidServerUrl(serverUrl)) { - handleError({ - message: - "[Analytics] load:: Unable to load due to invalid writeKey or serverUrl", - }); - throw Error("failed to initialize"); + throw Error('Unable to load the SDK due to invalid writeKey or serverUrl'); } let storageOptions = {}; @@ -985,7 +925,7 @@ class Analytics { if ( options && options.sendAdblockPageOptions && - typeof options.sendAdblockPageOptions === "object" + typeof options.sendAdblockPageOptions === 'object' ) { this.sendAdblockPageOptions = options.sendAdblockPageOptions; } @@ -994,15 +934,9 @@ class Analytics { const transformedCallbackMapping = {}; Object.keys(this.methodToCallbackMapping).forEach((methodName) => { if (this.methodToCallbackMapping.hasOwnProperty(methodName)) { - if ( - options.clientSuppliedCallbacks[ - this.methodToCallbackMapping[methodName] - ] - ) { + if (options.clientSuppliedCallbacks[this.methodToCallbackMapping[methodName]]) { transformedCallbackMapping[methodName] = - options.clientSuppliedCallbacks[ - this.methodToCallbackMapping[methodName] - ]; + options.clientSuppliedCallbacks[this.methodToCallbackMapping[methodName]]; } } }); @@ -1027,31 +961,25 @@ class Analytics { this.destSDKBaseURL = removeTrailingSlashes(options.destSDKBaseURL); if (!this.destSDKBaseURL) { handleError({ - message: "[Analytics] load:: CDN base URL is not valid", + message: '[Analytics] load:: CDN base URL is not valid', }); - throw Error("failed to load"); + throw Error('failed to load'); } } else { // Get the CDN base URL from the included 'rudder-analytics.min.js' script tag const { rudderSDK } = checkSDKUrl(); if (rudderSDK) { - this.destSDKBaseURL = rudderSDK - .split("/") - .slice(0, -1) - .concat(CDN_INT_DIR) - .join("/"); + this.destSDKBaseURL = rudderSDK.split('/').slice(0, -1).concat(CDN_INT_DIR).join('/'); } } if (options && options.getSourceConfig) { - if (typeof options.getSourceConfig !== "function") { - handleError('option "getSourceConfig" must be a function'); + if (typeof options.getSourceConfig !== 'function') { + handleError(new Error('option "getSourceConfig" must be a function')); } else { const res = options.getSourceConfig(); if (res instanceof Promise) { - res - .then((pRes) => this.processResponse(200, pRes)) - .catch(errorHandler); + res.then((pRes) => this.processResponse(200, pRes)).catch(errorHandler); } else { this.processResponse(200, res); } @@ -1092,11 +1020,11 @@ class Analytics { !Promise || !Object.entries ) { - ScriptLoader("polyfill", POLYFILL_URL); + ScriptLoader('polyfill', POLYFILL_URL); const self = this; const interval = setInterval(function () { // check if the polyfill is loaded - if (window.hasOwnProperty("polyfill")) { + if (window.hasOwnProperty('polyfill')) { clearInterval(interval); self.loadAfterPolyfill(writeKey, serverUrl, options); } @@ -1112,7 +1040,7 @@ class Analytics { ready(callback) { if (!this.loaded) return; - if (typeof callback === "function") { + if (typeof callback === 'function') { /** * If integrations are loaded or no integration is available for loading * execute the callback immediately @@ -1125,7 +1053,7 @@ class Analytics { } return; } - logger.error("ready callback is not a function"); + logger.error('ready callback is not a function'); } initializeCallbacks() { @@ -1142,14 +1070,10 @@ class Analytics { if (this.methodToCallbackMapping.hasOwnProperty(methodName)) { if (window.rudderanalytics) { if ( - typeof window.rudderanalytics[ - this.methodToCallbackMapping[methodName] - ] === "function" + typeof window.rudderanalytics[this.methodToCallbackMapping[methodName]] === 'function' ) { this.clientSuppliedCallbacks[methodName] = - window.rudderanalytics[ - this.methodToCallbackMapping[methodName] - ]; + window.rudderanalytics[this.methodToCallbackMapping[methodName]]; } } // let callback = @@ -1180,10 +1104,7 @@ class Analytics { } sendSampleRequest() { - ScriptLoader( - "ad-block", - "//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js" - ); + ScriptLoader('ad-block', '//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js'); } } @@ -1207,8 +1128,8 @@ function processDataInAnalyticsArray(analytics) { */ function parseQueryString(query) { const queryDefaults = { - trait: "ajs_trait_", - prop: "ajs_prop_", + trait: 'ajs_trait_', + prop: 'ajs_prop_', }; function getDataFromQueryObj(qObj, dataType) { @@ -1223,12 +1144,12 @@ function parseQueryString(query) { const queryObject = parse(query); if (queryObject.ajs_aid) { - instance.toBeProcessedArray.push(["setAnonymousId", queryObject.ajs_aid]); + instance.toBeProcessedArray.push(['setAnonymousId', queryObject.ajs_aid]); } if (queryObject.ajs_uid) { instance.toBeProcessedArray.push([ - "identify", + 'identify', queryObject.ajs_uid, getDataFromQueryObj(queryObject, queryDefaults.trait), ]); @@ -1236,7 +1157,7 @@ function parseQueryString(query) { if (queryObject.ajs_event) { instance.toBeProcessedArray.push([ - "track", + 'track', queryObject.ajs_event, getDataFromQueryObj(queryObject, queryDefaults.prop), ]); @@ -1246,11 +1167,11 @@ function parseQueryString(query) { Emitter(instance); window.addEventListener( - "error", + 'error', (e) => { handleError(e, instance); }, - true + true, ); // initialize supported callbacks @@ -1259,12 +1180,12 @@ instance.initializeCallbacks(); // register supported callbacks instance.registerCallbacks(false); -const defaultMethod = "load"; +const defaultMethod = 'load'; const argumentsArray = window.rudderanalytics; const isValidArgsArray = Array.isArray(argumentsArray); if (isValidArgsArray) { /** - * Iterate the buffered API calls until we find load call and + * Iterate the buffered API calls until we find load call and * queue it first for processing */ let i = 0; @@ -1281,8 +1202,7 @@ if (isValidArgsArray) { // parse querystring of the page url to send events parseQueryString(window.location.search); -if (isValidArgsArray) - argumentsArray.forEach((x) => instance.toBeProcessedArray.push(x)); +if (isValidArgsArray) argumentsArray.forEach((x) => instance.toBeProcessedArray.push(x)); processDataInAnalyticsArray(instance); diff --git a/buildspec.prod.yaml b/buildspec.prod.yaml index 34b95d645c..e2c89d5314 100644 --- a/buildspec.prod.yaml +++ b/buildspec.prod.yaml @@ -6,20 +6,15 @@ phases: nodejs: 12 build: commands: - - ls - npm install --unsafe-perm - npm run test - npm run buildProdBrowser - # - npm run buildDevBrowser - node -r esm integrationBuildScript.js - - sed 's|//# sourceMappingURL=rudder-analytics.min.js.map||' dist/rudder-analytics.min.js > dist/prod.js - - mv dist/prod.js dist/rudder-analytics.min.js + - sed -i -e 's|{{RS_BUGSNAG_API_KEY}}|'$RS_BUGSNAG_API_KEY'|' dist/rudder-analytics.min.js - aws s3 cp dist/rudder-analytics.min.js s3://$S3_BUCKET_NAME/v1.1/rudder-analytics.min.js --cache-control max-age=3600 --acl public-read - # - aws s3 cp dist/rudder-analytics.js s3://$S3_BUCKET_NAME/v1.1/rudder-analytics.js --cache-control max-age=3600 --acl public-read + - aws s3 cp dist/rudder-analytics.min.js.map s3://$S3_BUCKET_NAME/v1.1/rudder-analytics.min.js.map --cache-control max-age=3600 --acl public-read - aws s3 cp dist/integrations/ s3://$S3_BUCKET_NAME/v1.1/js-integrations/ --recursive --cache-control max-age=3600 --acl public-read - - aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/v1.1/rudder-analytics.min.js" - # - aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/v1.1/rudder-analytics.js" - - aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/v1.1/js-integrations" + - aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/v1.1/rudder-analytics.min.js" "/v1.1/rudder-analytics.min.js.map" "/v1.1/js-integrations" artifacts: files: - "**/*" diff --git a/buildspec.staging.yaml b/buildspec.staging.yaml index 6c2dec5db6..9a2646f3cf 100644 --- a/buildspec.staging.yaml +++ b/buildspec.staging.yaml @@ -6,21 +6,20 @@ phases: nodejs: 12 build: commands: - - ls - npm install --unsafe-perm - npm run test - npm run buildProdBrowser - node -r esm integrationBuildScript.js - - sed 's|//# sourceMappingURL=rudder-analytics.min.js.map||' dist/rudder-analytics.min.js > dist/prod.js - - mv dist/prod.js dist/rudder-analytics.min.js + - sed -i -e 's|rudder-analytics.min.js.map|rudder-analytics-staging.min.js.map|' -e 's|{{RS_BUGSNAG_API_KEY}}|'$RS_BUGSNAG_API_KEY'|' dist/rudder-analytics.min.js - aws s3 cp dist/rudder-analytics.min.js s3://$S3_BUCKET_NAME/v1.1/rudder-analytics-staging.min.js --cache-control max-age=3600 --acl public-read + - aws s3 cp dist/rudder-analytics.min.js.map s3://$S3_BUCKET_NAME/v1.1/rudder-analytics-staging.min.js.map --cache-control max-age=3600 --acl public-read - mkdir dist/temp - cp dist/integrations/* dist/temp + - for file in dist/temp/*.min.js ; do old_name=$(basename "$file"); old_name=$(echo $old_name.map); new_name=$(echo $old_name | awk '{split($0, tmp, "."); print sprintf("%s-staging.%s.%s.%s", tmp[1], tmp[2], tmp[3], tmp[4])}'); sed -i 's|'$old_name'|'$new_name'|' $file; done - for file in dist/temp/* ; do old_name=$(basename "$file"); new_name=$(echo $old_name | awk '{split($0, tmp, "."); print sprintf("%s-staging.%s.%s", tmp[1], tmp[2], tmp[3])}'); mv "dist/temp/$old_name" "dist/temp/$new_name"; done - aws s3 cp dist/temp/ s3://$S3_BUCKET_NAME/v1.1/js-integrations/ --recursive --cache-control max-age=3600 --acl public-read - rm -rf dist/temp - - aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/v1.1/rudder-analytics-staging.min.js" - - aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/v1.1/js-integrations" + - aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/v1.1/rudder-analytics-staging.min.js" "/v1.1/rudder-analytics-staging.min.js.map" "/v1.1/js-integrations" artifacts: files: - - "**/*" + - '**/*' diff --git a/integrations/Amplitude/browser.js b/integrations/Amplitude/browser.js index 4d2adb0b1a..5ca11b7d36 100644 --- a/integrations/Amplitude/browser.js +++ b/integrations/Amplitude/browser.js @@ -2,6 +2,7 @@ /* eslint-disable class-methods-use-this */ import logger from "../../utils/logUtil"; import { type } from "../../utils/utils"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { NAME } from "./constants"; class Amplitude { @@ -66,6 +67,7 @@ class Amplitude { "sha384-girahbTbYZ9tT03PWWj0mEVgyxtZoyDF9KVZdL+R53PP5wCY0PiVUKq0jeRlMx9M"; r.crossOrigin = "anonymous"; r.async = true; + r.dataset.loader = LOAD_ORIGIN; r.src = "https://cdn.amplitude.com/libs/amplitude-7.2.1-min.gz.js"; r.onload = function () { if (!e.amplitude.runQueuedFunctions) { diff --git a/integrations/BingAds/browser.js b/integrations/BingAds/browser.js index 1ab0316989..60efeb2a75 100644 --- a/integrations/BingAds/browser.js +++ b/integrations/BingAds/browser.js @@ -1,4 +1,5 @@ import logger from "../../utils/logUtil"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { NAME } from "./constants"; class BingAds { @@ -23,6 +24,7 @@ class BingAds { (n = d.createElement(t)), (n.src = r), (n.async = 1), + (n.dataset.loader = LOAD_ORIGIN), (n.onload = n.onreadystatechange = function () { let s = this.readyState; diff --git a/integrations/Braze/browser.js b/integrations/Braze/browser.js index daea4042a4..2fc87b84e0 100644 --- a/integrations/Braze/browser.js +++ b/integrations/Braze/browser.js @@ -1,5 +1,6 @@ /* eslint-disable class-methods-use-this */ import logger from "../../utils/logUtil"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { NAME } from "./constants"; /* @@ -91,6 +92,7 @@ class Braze { }; (y = p.createElement(P)).type = "text/javascript"; y.src = "https://js.appboycdn.com/web-sdk/2.4/appboy.min.js"; + y.dataset.loader = LOAD_ORIGIN; y.async = 1; (b = p.getElementsByTagName(P)[0]).parentNode.insertBefore(y, b); })(window, document, "script"); diff --git a/integrations/Chartbeat/browser.js b/integrations/Chartbeat/browser.js index 1977da09f9..7c532245c6 100644 --- a/integrations/Chartbeat/browser.js +++ b/integrations/Chartbeat/browser.js @@ -6,6 +6,7 @@ import { INTEGRATION_LOAD_CHECK_INTERVAL, } from "../../utils/constants"; import { NAME } from "./constants"; +import { LOAD_ORIGIN } from "../ScriptLoader"; class Chartbeat { constructor(config, analytics) { @@ -111,6 +112,7 @@ class Chartbeat { e.type = "text/javascript"; e.async = true; e.src = `//static.chartbeat.com/js/${script}`; + e.dataset.loader = LOAD_ORIGIN; n.parentNode.insertBefore(e, n); } loadChartbeat(); diff --git a/integrations/Comscore/browser.js b/integrations/Comscore/browser.js index 1d44ae0e86..8116443d44 100644 --- a/integrations/Comscore/browser.js +++ b/integrations/Comscore/browser.js @@ -5,6 +5,7 @@ import { INTEGRATION_LOAD_CHECK_INTERVAL, } from "../../utils/constants"; import { NAME } from "./constants"; +import { LOAD_ORIGIN } from "../ScriptLoader"; class Comscore { constructor(config, analytics) { @@ -74,6 +75,7 @@ class Comscore { const s = document.createElement("script"); const el = document.getElementsByTagName("script")[0]; s.async = true; + s.dataset.loader = LOAD_ORIGIN; s.src = `${ document.location.protocol == "https:" ? "https://sb" : "http://b" }.scorecardresearch.com/beacon.js`; diff --git a/integrations/CustomerIO/browser.js b/integrations/CustomerIO/browser.js index 6f9bb6920e..9b19c373ae 100644 --- a/integrations/CustomerIO/browser.js +++ b/integrations/CustomerIO/browser.js @@ -1,5 +1,6 @@ /* eslint-disable class-methods-use-this */ import logger from "../../utils/logUtil"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { NAME } from "./constants"; class CustomerIO { @@ -34,6 +35,7 @@ class CustomerIO { const t = document.createElement("script"); const s = document.getElementsByTagName("script")[0]; t.async = true; + t.dataset.loader = LOAD_ORIGIN; t.id = "cio-tracker"; t.setAttribute("data-site-id", siteID); t.src = "https://assets.customer.io/assets/track.js"; diff --git a/integrations/Drip/browser.js b/integrations/Drip/browser.js index 333a6b75bf..51d6b51124 100644 --- a/integrations/Drip/browser.js +++ b/integrations/Drip/browser.js @@ -10,6 +10,7 @@ import { getDestinationExternalID } from "./utils"; import { extractCustomFields } from "../../utils/utils"; import { NAME } from "./constants"; +import { LOAD_ORIGIN } from "../ScriptLoader"; class Drip { constructor(config, analytics) { @@ -44,6 +45,7 @@ class Drip { (function () { var dc = document.createElement("script"); dc.type = "text/javascript"; + dc.dataset.loader = LOAD_ORIGIN; dc.async = true; dc.src = `//tag.getdrip.com/${window._dcs.account}.js`; var s = document.getElementsByTagName("script")[0]; diff --git a/integrations/Fullstory/browser.js b/integrations/Fullstory/browser.js index 6bc9fe5e9d..36a46e69f5 100644 --- a/integrations/Fullstory/browser.js +++ b/integrations/Fullstory/browser.js @@ -2,6 +2,7 @@ /* eslint-disable no-undef */ import camelcase from "../../utils/camelcase"; import logger from "../../utils/logUtil"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { NAME } from "./constants"; class Fullstory { @@ -72,6 +73,7 @@ class Fullstory { o.async = 1; o.crossOrigin = "anonymous"; o.src = `https://${_fs_script}`; + o.dataset.loader = LOAD_ORIGIN; y = n.getElementsByTagName(t)[0]; y.parentNode.insertBefore(o, y); g.identify = function (i, v, s) { diff --git a/integrations/GoogleAds/browser.js b/integrations/GoogleAds/browser.js index 68a5e55ace..0dde98ae4c 100644 --- a/integrations/GoogleAds/browser.js +++ b/integrations/GoogleAds/browser.js @@ -1,5 +1,6 @@ /* eslint-disable class-methods-use-this */ import logger from "../../utils/logUtil"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { removeUndefinedAndNullValues } from "../utils/commonUtils"; import { NAME } from "./constants"; @@ -27,6 +28,7 @@ class GoogleAds { const js = document.createElement("script"); js.src = src; js.async = 1; + js.dataset.loader = LOAD_ORIGIN; js.type = "text/javascript"; js.id = id; const e = document.getElementsByTagName("head")[0]; diff --git a/integrations/GoogleOptimize/browser.js b/integrations/GoogleOptimize/browser.js index 305a68bd7d..ce63dd844f 100644 --- a/integrations/GoogleOptimize/browser.js +++ b/integrations/GoogleOptimize/browser.js @@ -1,7 +1,7 @@ /* eslint-disable class-methods-use-this */ import logger from "../../utils/logUtil"; -import ScriptLoader from "../ScriptLoader"; +import ScriptLoader, { LOAD_ORIGIN } from "../ScriptLoader"; import { NAME } from "./constants"; class GoogleOptimize { @@ -50,6 +50,7 @@ class GoogleOptimize { const flick = document.createElement("style"); flick.innerHTML = ".async-hide { opacity: 0 !important}"; const js = document.createElement("script"); + js.dataset.loader = LOAD_ORIGIN; js.innerHTML = `(function(a,s,y,n,c,h,i,d,e){s.className+=' '+y;h.start=1*new Date;h.end=i=function(){s.className=s.className.replace(RegExp(' ?'+y),'')};(a[n]=a[n]||[]).hide=h;setTimeout(function(){i();h.end=null},c);h.timeout=c;})(window,document.documentElement,'async-hide','dataLayer',4000,{'${this.containerId}':true});`; const e = document.getElementsByTagName("script")[0]; e.parentNode.insertBefore(flick, e); // style tag in anti flicker snippet should be before the a-flicker script as per docs diff --git a/integrations/GoogleTagManager/browser.js b/integrations/GoogleTagManager/browser.js index 7d544e0883..82ccc025bd 100644 --- a/integrations/GoogleTagManager/browser.js +++ b/integrations/GoogleTagManager/browser.js @@ -1,5 +1,6 @@ /* eslint-disable class-methods-use-this */ import logger from "../../utils/logUtil"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { NAME } from "./constants"; class GoogleTagManager { @@ -27,6 +28,7 @@ class GoogleTagManager { const f = d.getElementsByTagName(s)[0]; const j = d.createElement(s); const dl = l !== "dataLayer" ? `&l=${l}` : ""; + j.dataset.loader = LOAD_ORIGIN; j.async = true; j.src = `${window.finalUrl}/gtm.js?id=${i}${dl}`; f.parentNode.insertBefore(j, f); diff --git a/integrations/Heap/browser.js b/integrations/Heap/browser.js index 31cc46dca6..a17ae5e900 100644 --- a/integrations/Heap/browser.js +++ b/integrations/Heap/browser.js @@ -2,6 +2,7 @@ import processHeapProperties from "./util"; import { NAME } from "./constants"; +import { LOAD_ORIGIN } from "../ScriptLoader"; class Heap { constructor(config) { @@ -21,6 +22,7 @@ class Heap { var r = document.createElement("script"); (r.type = "text/javascript"), (r.async = !0), + (r.dataset.loader = LOAD_ORIGIN), (r.src = "https://cdn.heapanalytics.com/js/heap-" + e + ".js"); var a = document.getElementsByTagName("script")[0]; a.parentNode.insertBefore(r, a); diff --git a/integrations/Hotjar/browser.js b/integrations/Hotjar/browser.js index 5ced8bc78f..f76c5065c1 100644 --- a/integrations/Hotjar/browser.js +++ b/integrations/Hotjar/browser.js @@ -1,6 +1,7 @@ /* eslint-disable no-underscore-dangle */ /* eslint-disable class-methods-use-this */ import logger from "../../utils/logUtil"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { NAME } from "./constants"; class Hotjar { @@ -26,6 +27,7 @@ class Hotjar { h._hjSettings = { hjid: h.hotjarSiteId, hjsv: 6 }; a = o.getElementsByTagName("head")[0]; r = o.createElement("script"); + r.dataset.loader = LOAD_ORIGIN; r.async = 1; r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv; a.appendChild(r); diff --git a/integrations/INTERCOM/browser.js b/integrations/INTERCOM/browser.js index ea0b5808f7..e09d941d0e 100644 --- a/integrations/INTERCOM/browser.js +++ b/integrations/INTERCOM/browser.js @@ -1,6 +1,7 @@ /* eslint-disable class-methods-use-this */ import md5 from "md5"; import logger from "../../utils/logUtil"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { NAME } from "./constants"; class INTERCOM { @@ -38,6 +39,7 @@ class INTERCOM { w.Intercom = i; const l = function () { const s = d.createElement("script"); + s.dataset.loader = LOAD_ORIGIN; s.type = "text/javascript"; s.async = true; s.src = `https://widget.intercom.io/widget/${window.intercomSettings.app_id}`; diff --git a/integrations/Kissmetrics/browser.js b/integrations/Kissmetrics/browser.js index d9e4f544ec..ac5281028b 100644 --- a/integrations/Kissmetrics/browser.js +++ b/integrations/Kissmetrics/browser.js @@ -4,6 +4,7 @@ import each from "component-each"; import { getRevenue } from "../../utils/utils"; import logger from "../../utils/logUtil"; import { NAME } from "./constants"; +import { LOAD_ORIGIN } from "../ScriptLoader"; class Kissmetrics { constructor(config, analytics) { @@ -27,6 +28,7 @@ class Kissmetrics { const s = d.createElement("script"); s.type = "text/javascript"; s.async = true; + s.dataset.loader = LOAD_ORIGIN; s.src = u; f.parentNode.insertBefore(s, f); }, 1); diff --git a/integrations/Lytics/browser.js b/integrations/Lytics/browser.js index faabf9800e..69b66f37e5 100644 --- a/integrations/Lytics/browser.js +++ b/integrations/Lytics/browser.js @@ -19,6 +19,7 @@ /* eslint-disable lines-around-directive */ /* eslint-disable strict */ import logger from "../../utils/logUtil"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { NAME } from "./constants"; class Lytics { @@ -61,7 +62,7 @@ class Lytics { n("call"), (o.loadScript = function (n, t, i) { var e = document.createElement("script"); - (e.async = !0), (e.src = n), (e.onload = t), (e.onerror = i); + (e.async = !0), (e.src = n), (e.onload = t), (e.onerror = i), (e.dataset.loader = LOAD_ORIGIN); var o = document.getElementsByTagName("script")[0], r = (o && o.parentNode) || document.head || document.body, c = o || r.lastChild; diff --git a/integrations/Mixpanel/browser.js b/integrations/Mixpanel/browser.js index 1eba31e3af..d585aae5ef 100644 --- a/integrations/Mixpanel/browser.js +++ b/integrations/Mixpanel/browser.js @@ -40,6 +40,7 @@ import { formatTraits, } from "./util"; import { NAME } from "./constants"; +import { LOAD_ORIGIN } from "../ScriptLoader"; class Mixpanel { constructor(config, analytics) { @@ -136,6 +137,7 @@ class Mixpanel { e = f.createElement("script"); e.type = "text/javascript"; e.async = !0; + e.dataset.loader = LOAD_ORIGIN; e.src = "undefined" !== typeof MIXPANEL_CUSTOM_LIB_URL ? MIXPANEL_CUSTOM_LIB_URL diff --git a/integrations/MoEngage/browser.js b/integrations/MoEngage/browser.js index a53aa07b01..71e0476513 100644 --- a/integrations/MoEngage/browser.js +++ b/integrations/MoEngage/browser.js @@ -1,5 +1,6 @@ import each from "@ndhoule/each"; import logger from "../../utils/logUtil"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { NAME } from "./constants"; // custom traits mapping context.traits --> moengage properties @@ -70,6 +71,7 @@ class MoEngage { a = s.createElement(o); m = s.getElementsByTagName(o)[0]; a.async = 1; + a.dataset.loader = LOAD_ORIGIN; a.src = g; m.parentNode.insertBefore(a, m); i.moe = diff --git a/integrations/Pendo/browser.js b/integrations/Pendo/browser.js index 563e68e485..e3452e9314 100644 --- a/integrations/Pendo/browser.js +++ b/integrations/Pendo/browser.js @@ -1,6 +1,7 @@ /* eslint-disable class-methods-use-this */ /* eslint-disable lines-between-class-members */ import logger from "../../utils/logUtil"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { NAME } from "./constants"; class Pendo { @@ -32,6 +33,7 @@ class Pendo { }; })(v[w]); y = e.createElement(n); + y.dataset.loader = LOAD_ORIGIN; y.async = !0; y.src = `https://cdn.pendo.io/agent/static/${apiKey}/pendo.js`; z = e.getElementsByTagName(n)[0]; diff --git a/integrations/PinterestTag/browser.js b/integrations/PinterestTag/browser.js index cf921ed59a..c11c91a4c8 100644 --- a/integrations/PinterestTag/browser.js +++ b/integrations/PinterestTag/browser.js @@ -13,6 +13,7 @@ import { getDataFromSource, } from "../../utils/utils"; import { NAME } from "./constants"; +import { LOAD_ORIGIN } from "../ScriptLoader"; export default class PinterestTag { constructor(config, analytics) { @@ -37,7 +38,7 @@ export default class PinterestTag { var n = window.pintrk; (n.queue = []), (n.version = "3.0"); var t = document.createElement("script"); - (t.async = !0), (t.src = e); + (t.async = !0), (t.src = e), (t.dataset.loader = LOAD_ORIGIN); var r = document.getElementsByTagName("script")[0]; r.parentNode.insertBefore(t, r); } diff --git a/integrations/Posthog/browser.js b/integrations/Posthog/browser.js index 48c1764cad..9daadb62a4 100644 --- a/integrations/Posthog/browser.js +++ b/integrations/Posthog/browser.js @@ -2,6 +2,7 @@ /* eslint-disable class-methods-use-this */ import logger from "../../utils/logUtil"; import { removeTrailingSlashes } from "../../utils/utils"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { NAME } from "./constants"; class Posthog { @@ -60,6 +61,7 @@ class Posthog { } ((p = t.createElement("script")).type = "text/javascript"), (p.async = !0), + (p.dataset.loader = LOAD_ORIGIN), (p.src = s.api_host + "/static/array.js"), (r = t.getElementsByTagName("script")[0]).parentNode.insertBefore( p, diff --git a/integrations/ProfitWell/browser.js b/integrations/ProfitWell/browser.js index 3505a9a9d7..7c4edcbb30 100644 --- a/integrations/ProfitWell/browser.js +++ b/integrations/ProfitWell/browser.js @@ -1,6 +1,7 @@ /* eslint-disable class-methods-use-this */ import get from "get-value"; import logger from "../../utils/logUtil"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { NAME } from "./constants"; class ProfitWell { @@ -37,6 +38,7 @@ class ProfitWell { a = s.createElement(g); m = s.getElementsByTagName(g)[0]; a.async = 1; + a.dataset.loader = LOAD_ORIGIN; a.src = r + "?auth=" + window.publicApiKey; m.parentNode.insertBefore(a, m); })( diff --git a/integrations/Qualtrics/browser.js b/integrations/Qualtrics/browser.js index 8f97a7c0b2..9ef2bd33dd 100644 --- a/integrations/Qualtrics/browser.js +++ b/integrations/Qualtrics/browser.js @@ -10,6 +10,7 @@ // eslint-disable-next-line class-methods-use-this import logger from "../../utils/logUtil"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { NAME } from "./constants"; class Qualtrics { @@ -93,6 +94,7 @@ class Qualtrics { if (this.check()) { var a = document.createElement("script"); a.type = "text/javascript"; + a.dataset.loader = LOAD_ORIGIN; a.src = g; document.body && document.body.appendChild(a); } diff --git a/integrations/RedditPixel/browser.js b/integrations/RedditPixel/browser.js index 1ef75723a2..5137c607a7 100644 --- a/integrations/RedditPixel/browser.js +++ b/integrations/RedditPixel/browser.js @@ -8,6 +8,7 @@ /* eslint-disable vars-on-top */ /* eslint-disable no-unused-expressions */ import logger from "../../utils/logUtil"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { NAME } from "./constants"; class RedditPixel { @@ -32,6 +33,7 @@ class RedditPixel { p.callQueue = []; var t = d.createElement("script"); (t.src = "https://www.redditstatic.com/ads/pixel.js"), (t.async = !0); + (t.dataset.loader = LOAD_ORIGIN); var s = d.getElementsByTagName("script")[0]; s.parentNode.insertBefore(t, s); } diff --git a/integrations/ScriptLoader.js b/integrations/ScriptLoader.js index a537352cd5..b11b9cede2 100644 --- a/integrations/ScriptLoader.js +++ b/integrations/ScriptLoader.js @@ -3,6 +3,8 @@ const defaultAsyncState = true; +export const LOAD_ORIGIN = "RS_JS_SDK"; + const ScriptLoader = (id, src, async = defaultAsyncState) => { const exists = document.getElementById(id); if (exists) { @@ -14,6 +16,7 @@ const ScriptLoader = (id, src, async = defaultAsyncState) => { js.async = async === undefined ? defaultAsyncState : async; js.type = "text/javascript"; js.id = id; + js.dataset.loader = LOAD_ORIGIN; const headElmColl = document.getElementsByTagName("head"); if (headElmColl.length !== 0) { // logger.debug("==adding script==", js); diff --git a/integrations/Sentry/utils.js b/integrations/Sentry/utils.js index 7dd9e565d2..d4c081e1fb 100644 --- a/integrations/Sentry/utils.js +++ b/integrations/Sentry/utils.js @@ -1,6 +1,7 @@ /* eslint-disable no-param-reassign */ /* eslint-disable object-shorthand */ import logger from "../../utils/logUtil"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { isDefinedAndNotNullAndNotEmpty } from "../utils/commonUtils"; const convertObjectToArray = (objectInput, propertyName) => { @@ -17,6 +18,7 @@ const SentryScriptLoader = (id, src, integrity) => { js.crossOrigin = "anonymous"; js.type = "text/javascript"; js.id = id; + js.dataset.loader = LOAD_ORIGIN; const e = document.getElementsByTagName("script")[0]; logger.debug("==parent script==", e); logger.debug("==adding script==", js); diff --git a/integrations/SnapPixel/browser.js b/integrations/SnapPixel/browser.js index 7a4102818e..7118b529da 100644 --- a/integrations/SnapPixel/browser.js +++ b/integrations/SnapPixel/browser.js @@ -10,6 +10,7 @@ import { } from "../utils/commonUtils"; import { ecommEventPayload, eventPayload, sendEvent } from "./util"; import { NAME } from "./constants"; +import { LOAD_ORIGIN } from "../ScriptLoader"; class SnapPixel { constructor(config, analytics) { @@ -73,6 +74,7 @@ class SnapPixel { var r = t.createElement(s); r.async = !0; r.src = n; + r.dataset.loader = LOAD_ORIGIN; var u = t.getElementsByTagName(s)[0]; u.parentNode.insertBefore(r, u); })(window, document, "https://sc-static.net/scevent.min.js"); diff --git a/integrations/VWO/browser.js b/integrations/VWO/browser.js index 5f06a59ad2..d3cc18c17a 100644 --- a/integrations/VWO/browser.js +++ b/integrations/VWO/browser.js @@ -2,6 +2,7 @@ /* eslint-disable class-methods-use-this */ /* eslint-disable camelcase */ import logger from "../../utils/logUtil"; +import { LOAD_ORIGIN } from "../ScriptLoader"; import { NAME } from "./constants"; class VWO { @@ -53,6 +54,7 @@ class VWO { const b = d.createElement("script"); b.src = a; b.type = "text/javascript"; + b.dataset.loader = LOAD_ORIGIN; b.innerText; b.onerror = function () { _vwo_code.finish(); diff --git a/metrics/error-report/Bugsnag.js b/metrics/error-report/Bugsnag.js new file mode 100644 index 0000000000..e4c51c5a18 --- /dev/null +++ b/metrics/error-report/Bugsnag.js @@ -0,0 +1,125 @@ +/* eslint-disable no-param-reassign */ +/* eslint-disable import/prefer-default-export */ +/* eslint-disable no-prototype-builtins */ +import ScriptLoader from "../../integrations/ScriptLoader"; +import { configToIntNames } from "../../utils/config_to_integration_names"; +import { MAX_WAIT_FOR_INTEGRATION_LOAD } from "../../utils/constants"; +import { get } from "../../utils/utils"; + +// This SDK meta data will be send along with the error for more insight +const META_DATA = { + SDK: { + name: "JS", + installType: "process.module_type", + }, +}; + +// This API key token is parsed in the CI pipeline +const API_KEY = "{{RS_BUGSNAG_API_KEY}}"; + +// Errors only from Below SDKs are allowed to reach Bugsnag +const SDK_FILE_NAMES = [ + "browser.js", + "rudder-analytics.min.js", + "rudder-analytics-staging.min.js", + "rudder-analytics.js", + ...Object.values(configToIntNames).map((intgName) => `${intgName}.js`), + ...Object.values(configToIntNames).map((intgName) => `${intgName}.min.js`), + ...Object.values(configToIntNames).map( + (intgName) => `${intgName}-staging.min.js` + ), +]; + +/** + * This function will load the Bugsnag native SDK through CDN + * Once loaded it will be available in window.Bugsnag + */ +const load = () => { + const pluginName = "bugsnag"; + if (!window.hasOwnProperty(pluginName)) { + ScriptLoader( + pluginName, + "https://d2wy8f7a9ursnm.cloudfront.net/v7/bugsnag.min.js" + ); + } +}; + +/** + * This function is to initialize the bugsnag with apiKey, SDK meta data + * and custom configuration for onError method. + * After initialization Bugsnag instance will be available in window.rsBugsnagClient + * @param {string} sourceId + */ +function initClient(sourceId) { + if (window.Bugsnag === undefined) return; + + // If the API key token is not parsed yet, don't proceed to initialize the client + // This also prevents unnecessary errors sent to Bugsnag during development phase. + const apiKeyRegex = /{{.+}}/; + if (API_KEY.match(apiKeyRegex) !== null) return; + + window.rsBugsnagClient = window.Bugsnag.start({ + apiKey: API_KEY, + appVersion: "process.package_version", // Set SDK version as the app version + metadata: META_DATA, + onError: (event) => { + const errorOrigin = get(event.errors[0], "stacktrace.0.file"); + // Skip errors that do not have a valid stack trace + if (!errorOrigin || typeof errorOrigin !== "string") return false; + + const srcFileName = errorOrigin.substring( + errorOrigin.lastIndexOf("/") + 1 + ); + if (!SDK_FILE_NAMES.includes(srcFileName)) + // Discard the event if it's not originated at the SDK + return false; + + event.addMetadata("source", { + sourceId, + }); + + const errMsg = event.errors[0].errorMessage; + event.context = errMsg; + // Hack for easily grouping the script load errors + // on the dashboard + if (errMsg.includes("error in script loading")) + event.context = "Script load failures"; + + event.severity = "error"; + return true; + }, + autoTrackSessions: false, // auto tracking sessions is disabled + collectUserIp: false, // collecting user's IP is disabled + enabledBreadcrumbTypes: ["error", "log", "user"], + }); + + window.rsBugsnagClient.releaseStage = "production"; // set the release stage +} + +/** + * The responsibility of this function is to check Bugsnag native SDK + * has been loaded or not in a certain interval. + * If already loaded initialize the SDK. + * @param {*} sourceId + */ +const init = (sourceId) => { + if (window.hasOwnProperty("rsBugsnagClient")) return; // return if already initialized + + if (window.Bugsnag !== undefined) { + initClient(sourceId); + } else { + // Check if Bugsnag is loaded every '100'ms + const interval = setInterval(() => { + if (window.Bugsnag !== undefined) { + clearInterval(interval); + + initClient(sourceId); + } + }, 100); + setTimeout(() => { + clearInterval(interval); + }, MAX_WAIT_FOR_INTEGRATION_LOAD); + } +}; + +export { load, init }; diff --git a/package.json b/package.json index 253ac5bca7..be540d7e43 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,10 @@ "bundle-size-visual": "ENV=prod PROD_DEBUG=true uglify=true visualizer=true rollup -c", "size": "npm run clean && npm run buildProdBrowser && npm run buildAllIntegrations && npm run buildForNpm && size-limit", "changelog": "auto-changelog -p -t keepachangelog -u true -l false --sort-commits date-desc ", - "buildDevIntegration": "NODE_ENV=false uglify=false INTG_NAME=AdobeAnalytics rollup --config rollup.intgConfig.js", - "buildDevIntegrationCLI": "NODE_ENV=false uglify=false INTG_NAME=$npm_config_intg rollup --config rollup.intgConfig.js", - "buildProdIntegration": "NODE_ENV=false ENV=prod uglify=true INTG_NAME=AdobeAnalytics rollup --config rollup.intgConfig.js", - "buildProdIntegrationCLI": "NODE_ENV=false ENV=prod uglify=true INTG_NAME=$npm_config_intg rollup --config rollup.intgConfig.js", + "buildDevIntegration": "PROD_DEBUG=true NODE_ENV=false uglify=false INTG_NAME=AdobeAnalytics rollup --config rollup.intgConfig.js", + "buildDevIntegrationCLI": "PROD_DEBUG=true NODE_ENV=false uglify=false INTG_NAME=$npm_config_intg rollup --config rollup.intgConfig.js", + "buildProdIntegration": "PROD_DEBUG=true NODE_ENV=false ENV=prod uglify=true INTG_NAME=AdobeAnalytics rollup --config rollup.intgConfig.js", + "buildProdIntegrationCLI": "PROD_DEBUG=true NODE_ENV=false ENV=prod uglify=true INTG_NAME=$npm_config_intg rollup --config rollup.intgConfig.js", "buildAllIntegrations": "BUNDLE_SIZE_VISUALIZER=false node -r esm integrationBuildScript.js", "bundle-size-visual-integration": "ENV=prod PROD_DEBUG=true uglify=true visualizer=true INTG_NAME=AdobeAnalytics rollup --config rollup.intgConfig.js", "bundle-size-visual-integration-cli": "ENV=prod PROD_DEBUG=true uglify=true visualizer=true INTG_NAME=$npm_config_intg rollup --config rollup.intgConfig.js", diff --git a/rollup.config.js b/rollup.config.js index c78d0bf6d5..906f17c8e9 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -16,7 +16,7 @@ import * as npmPackage from "./dist/rudder-sdk-js/package.json"; let distFileName = ""; let { version } = webPackage; -let moduleType = "web"; +let moduleType = "cdn"; switch (process.env.ENV) { case "prod": switch (process.env.ENC) { diff --git a/rollup.intgConfig.js b/rollup.intgConfig.js index f51c6444f4..73cba61709 100644 --- a/rollup.intgConfig.js +++ b/rollup.intgConfig.js @@ -18,7 +18,8 @@ import { INTG_SUFFIX } from "./utils/constants"; let distFileName = ""; let { version } = webPackage; -let moduleType = "web"; +let moduleType = "cdn"; + switch (process.env.ENV) { case "prod": switch (process.env.ENC) { diff --git a/utils/EventRepository.js b/utils/EventRepository.js index d212b76bd2..d2e891bda7 100644 --- a/utils/EventRepository.js +++ b/utils/EventRepository.js @@ -3,7 +3,11 @@ import logger from "./logUtil"; import XHRQueue from "./xhrModule"; import BeaconQueue from "./beaconQueue"; -import { getCurrentTimeFormatted, removeTrailingSlashes } from "./utils"; +import { + getCurrentTimeFormatted, + removeTrailingSlashes, + replacer, +} from "./utils"; const MESSAGE_LENGTH = 32 * 1000; // ~32 Kb @@ -68,7 +72,7 @@ class EventRepository { message.sentAt = getCurrentTimeFormatted(); // add this, will get modified when actually being sent // check message size, if greater log an error - if (JSON.stringify(message).length > MESSAGE_LENGTH) { + if (JSON.stringify(message, replacer).length > MESSAGE_LENGTH) { logger.error( "[EventRepository] enqueue:: message length greater 32 Kb ", message diff --git a/utils/beaconQueue.js b/utils/beaconQueue.js index 12b6614b49..18863a7baf 100644 --- a/utils/beaconQueue.js +++ b/utils/beaconQueue.js @@ -2,6 +2,7 @@ /* eslint-disable class-methods-use-this */ // import logger from "../logUtil"; import { Store } from "./storage/store"; +import { replacer } from "./utils"; const defaults = { queue: "queue", @@ -42,27 +43,13 @@ class BeaconQueue { this.storage.set(this.queueName, value); } - /** - * - * Utility method for excluding null and empty values in JSON - * @param {*} _key - * @param {*} value - * @returns - */ - replacer(_key, value) { - if (value === null || value === undefined) { - return undefined; - } - return value; - } - enqueue(message) { let queue = this.getQueue() || []; queue = queue.slice(-(this.maxItems - 1)); queue.push(message); let batch = queue.slice(0); const data = { batch }; - const dataToSend = JSON.stringify(data, this.replacer); + const dataToSend = JSON.stringify(data, replacer); if (dataToSend.length > defaults.maxPayloadSize) { batch = queue.slice(0, queue.length - 1); this.flushQueue(batch); @@ -95,7 +82,7 @@ class BeaconQueue { event.sentAt = new Date().toISOString(); }); const data = { batch }; - const payload = JSON.stringify(data, this.replacer); + const payload = JSON.stringify(data, replacer); const blob = new Blob([payload], { type: "text/plain" }); const isPushed = navigator.sendBeacon( `${this.url}?writeKey=${this.writekey}`, diff --git a/utils/constants.js b/utils/constants.js index 7cdbbe3673..052bfe5934 100644 --- a/utils/constants.js +++ b/utils/constants.js @@ -1,28 +1,31 @@ // Reserved Keywords for properties/traits const RESERVED_KEYS = [ - "anonymous_id", - "id", - "sent_at", - "received_at", - "timestamp", - "original_timestamp", - "event_text", - "event", + 'anonymous_id', + 'id', + 'sent_at', + 'received_at', + 'timestamp', + 'original_timestamp', + 'event_text', + 'event', ]; const CONFIG_URL = - "https://api.rudderlabs.com/sourceConfig/?p=process.module_type&v=process.package_version"; -const CDN_INT_DIR = "js-integrations"; + 'https://api.rudderlabs.com/sourceConfig/?p=process.module_type&v=process.package_version'; +const CDN_INT_DIR = 'js-integrations'; const DEST_SDK_BASE_URL = `https://cdn.rudderlabs.com/v1.1/${CDN_INT_DIR}`; const MAX_WAIT_FOR_INTEGRATION_LOAD = 10000; const INTEGRATION_LOAD_CHECK_INTERVAL = 1000; -const INTG_SUFFIX = "_RS"; +const INTG_SUFFIX = '_RS'; const POLYFILL_URL = - "https://polyfill.io/v3/polyfill.min.js?features=Array.prototype.find%2CArray.prototype.includes%2CPromise%2CString.prototype.endsWith%2CString.prototype.includes%2CString.prototype.startsWith%2CObject.entries"; + 'https://polyfill.io/v3/polyfill.min.js?features=Array.prototype.find%2CArray.prototype.includes%2CPromise%2CString.prototype.endsWith%2CString.prototype.includes%2CString.prototype.startsWith%2CObject.entries%2CObject.values%2CElement.prototype.dataset'; -const GENERIC_TRUE_VALUES = ["true", "True", "TRUE", "t", "T", "1"]; -const GENERIC_FALSE_VALUES = ["false", "False", "FALSE", "f", "F", "0"]; +const DEFAULT_ERROR_REPORT_PROVIDER = 'bugsnag'; +const ERROR_REPORT_PROVIDERS = [DEFAULT_ERROR_REPORT_PROVIDER]; + +const GENERIC_TRUE_VALUES = ['true', 'True', 'TRUE', 't', 'T', '1']; +const GENERIC_FALSE_VALUES = ['false', 'False', 'FALSE', 'f', 'F', '0']; export { RESERVED_KEYS, @@ -33,6 +36,8 @@ export { INTEGRATION_LOAD_CHECK_INTERVAL, INTG_SUFFIX, POLYFILL_URL, + DEFAULT_ERROR_REPORT_PROVIDER, + ERROR_REPORT_PROVIDERS, GENERIC_TRUE_VALUES, GENERIC_FALSE_VALUES, }; diff --git a/utils/utils.js b/utils/utils.js index 4b82a32e67..99259d7c39 100644 --- a/utils/utils.js +++ b/utils/utils.js @@ -1,6 +1,7 @@ // import * as XMLHttpRequestNode from "Xmlhttprequest"; import { parse } from "component-url"; import get from "get-value"; +import { LOAD_ORIGIN } from "../integrations/ScriptLoader"; import logger from "./logUtil"; import { commonNames } from "./integration_cname"; import { clientToServerNames } from "./client_server_name"; @@ -129,29 +130,60 @@ function getJSONTrimmed(context, url, writeKey, callback) { xhr.send(); } +/** + * This function is to add breadcrumbs + * @param {string} breadcrumb Message to add insight of an user's journey before the error occurred + */ +function leaveBreadcrumb(breadcrumb) { + if (window.rsBugsnagClient) { + window.rsBugsnagClient.leaveBreadcrumb(breadcrumb); + } +} + +/** + * This function is to send handled errors to Bugsnag if Bugsnag client is available + * @param {Error} error Error instance from handled error + */ +function notifyError(error) { + if (window.rsBugsnagClient) { + window.rsBugsnagClient.notify(error); + } +} + function handleError(error, analyticsInstance) { - let errorMessage = error.message ? error.message : undefined; - let sampleAdBlockTest; + let errorMessage = error.message; try { if (error instanceof Event) { - if (error.target && error.target.localName == "script") { - errorMessage = `error in script loading:: src:: ${error.target.src} id:: ${error.target.id}`; - if (analyticsInstance && error.target.src.includes("adsbygoogle")) { - sampleAdBlockTest = true; - analyticsInstance.page( - "RudderJS-Initiated", - "ad-block page request", - { path: "/ad-blocked", title: errorMessage }, - analyticsInstance.sendAdblockPageOptions - ); - } + // Discard all the non-script loading errors + if (error.target && error.target.localName !== "script") return; + + // Discard errors of scripts that are not loaded by the SDK + if (error.target.dataset && error.target.dataset.loader !== LOAD_ORIGIN) + return; + + errorMessage = `error in script loading:: src:: ${error.target.src} id:: ${error.target.id}`; + + // SDK triggered ad-blocker script + if (error.target.id === "ad-block") { + analyticsInstance.page( + "RudderJS-Initiated", + "ad-block page request", + { path: "/ad-blocked", title: errorMessage }, + analyticsInstance.sendAdblockPageOptions + ); + // No need to proceed further for Ad-block errors + return; } } - if (errorMessage && !sampleAdBlockTest) { - logger.error("[Util] handleError:: ", errorMessage); - } - } catch (e) { - logger.error("[Util] handleError:: ", e); + + errorMessage = `[handleError]:: "${errorMessage}"`; + logger.error(errorMessage); + let errorObj = error; + if (!(error instanceof Error)) errorObj = new Error(errorMessage); + notifyError(errorObj); + } catch (err) { + logger.error("[handleError] Exception:: ", err); + notifyError(err); } } @@ -743,4 +775,7 @@ export { constructPayload, getConfigUrl, checkSDKUrl, + notifyError, + leaveBreadcrumb, + get, };