plugins/browser-plugin-debugger/src/index.ts
/*
* Copyright (c) 2022 Snowplow Analytics Ltd, 2010 Anthon Pang
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import { BrowserPlugin, BrowserTracker } from '@snowplow/browser-tracker-core';
import {
EventJson,
EventJsonWithKeys,
JsonProcessor,
Logger,
LOG_LEVEL,
Payload,
PayloadBuilder,
payloadJsonProcessor,
version,
getSchemaParts,
SelfDescribingJson,
} from '@snowplow/tracker-core';
import randomcolor from 'randomcolor';
/**
* Adds advertisement tracking functions
*/
export function DebuggerPlugin(logLevel: LOG_LEVEL = LOG_LEVEL.debug): BrowserPlugin {
let LOG: Logger;
let tracker: BrowserTracker;
let eventColour: string;
const colours = {
event: () => `color: White; background: ${eventColour}; font-weight: bold; padding: 1px 4px; border-radius: 2px;`,
snowplowPurple: 'color: White; background: #6638B8; font-weight: bold; padding: 1px 4px; border-radius: 2px;',
activated: 'color: White; background: #9E62DD; font-weight: bold; padding: 1px 4px; border-radius: 2px;',
payload: 'color: White; background: #3748B8; font-weight: bold; padding: 1px 4px; border-radius: 2px;',
json: 'color: White; background: #388AB8; font-weight: bold; padding: 1px 4px; border-radius: 2px;',
schema: 'color: White; background: #268047; font-weight: bold; padding: 1px 4px; border-radius: 2px;',
schemaVersion: 'color: White; background: #80265F; font-weight: bold; padding: 1px 4px; border-radius: 2px;',
};
const debug = (style: string, message: string, extra?: [string, ...unknown[]]) => {
const [extraMessage, ...rest] = extra ?? [''];
LOG.debug(
`v${version} %c${tracker.namespace}%c %c%s` + extraMessage,
colours.snowplowPurple,
'',
style,
message,
...rest
);
};
function jsonInterceptor(encodeBase64: boolean): JsonProcessor {
const log = (jsonType: string, data: SelfDescribingJson) => {
const schemaParts = getSchemaParts(data['schema']);
debug(colours.event(), 'Event', [
'%c%s%c%s%c%s\n%o',
colours.json,
`${jsonType}: ${schemaParts ? schemaParts[1] : 'Unknown Schema'}`,
colours.schemaVersion,
schemaParts ? `Version: ${schemaParts[2]}-${schemaParts[3]}-${schemaParts[4]}` : 'Unknown Schema Version',
colours.schema,
schemaParts ? `Vendor: ${schemaParts[0]}` : 'Unknown Vendor',
data['data'],
]);
};
return (
payloadBuilder: PayloadBuilder,
jsonForProcessing: EventJson,
contextEntitiesForProcessing: SelfDescribingJson[]
) => {
if (jsonForProcessing.length) {
for (const json of jsonForProcessing) {
const data = json.json['data'] as SelfDescribingJson;
if (Array.isArray(data)) {
data.forEach((d) => {
log(getJsonType(json), d);
});
} else {
log(getJsonType(json), data);
}
}
}
if (contextEntitiesForProcessing.length) {
for (const entity of contextEntitiesForProcessing) {
log('Context', entity);
}
}
return payloadJsonProcessor(encodeBase64)(payloadBuilder, jsonForProcessing, contextEntitiesForProcessing);
};
}
return {
logger: (logger) => {
LOG = logger;
logger.setLogLevel(logLevel);
},
activateBrowserPlugin: (t) => {
tracker = t;
debug(colours.activated, 'Tracker Activated');
},
beforeTrack: (payloadBuilder: PayloadBuilder) => {
eventColour = randomcolor({ luminosity: 'dark' });
payloadBuilder.withJsonProcessor(jsonInterceptor(tracker.core.getBase64Encoding()));
debug(colours.event(), 'Event', ['%c%s', colours.snowplowPurple, getEventType(payloadBuilder)]);
payloadBuilder.build();
},
afterTrack: (payload: Payload) => {
debug(colours.event(), 'Event', ['%c%s\n%o', colours.payload, 'Payload', payload]);
},
};
}
function getJsonType(json: EventJsonWithKeys) {
switch (json.keyIfEncoded) {
case 'cx':
return 'Context';
case 'ue_px':
return 'Self Describing';
default:
return `${json.keyIfEncoded}, ${json.keyIfNotEncoded}`;
}
}
function getEventType(payloadBuilder: PayloadBuilder) {
const payload = payloadBuilder.getPayload();
switch (payload['e']) {
case 'pv':
return 'Page View';
case 'pp':
return 'Page Ping';
case 'tr':
return 'Ecommerce Transaction';
case 'ti':
return 'Ecommerce Transaction Item';
case 'se':
return 'Structured Event';
case 'ue':
return 'Self Describing';
default:
return typeof payload['e'] === 'string' ? payload['e'] : 'Invalid';
}
}