voxa-ga
Version:
Integrate Google Universal Analytics into your Alexa apps using the voxa framework
200 lines (168 loc) • 5.84 kB
JavaScript
;
const _ = require("lodash");
const detect = require("./detect");
const EventRider = require("./EventRider");
const defaultConfig = {
ignoreUsers: [],
debug: false,
appName: detect.appName(),
appVersion: detect.appVersion(),
suppressIntents: false,
suppressParams: false,
suppressStates: false,
suppressEventHandler: false,
suppressEventPermissions: false,
suppressEventList: false
};
function register(skill, config) {
const pluginConfig = _.merge({}, defaultConfig, config);
skill.onRequestStarted(event => {
const isHealthCheckEvent = _.get(event, "intent.params.is_health_check");
const platformName = _.get(event, "platform.name");
const trackingId =
_.get(pluginConfig, platformName) || pluginConfig.trackingId;
pluginConfig.trackingId = trackingId;
if (!pluginConfig.trackingId)
throw new Error("trackingId is required in the config file");
// https://developers.google.com/assistant/console/health-checks
if (isHealthCheckEvent) {
_.set(pluginConfig, "suppressSending", true);
}
event.ga = new EventRider(event, pluginConfig);
event.ga.time("Interaction", "Request");
});
skill.onSessionStarted(event => {
event.ga.startSession();
});
skill.onSessionEnded(event => {
event.ga.endSession();
if (event.request.type === "SessionEndedRequest") {
// If this is an external end, we cannot ride on the normal pageview, we have to make one
event.ga.pushPath("SessionEndedExternally");
event.ga.logPath(event);
return send(event);
}
});
skill.onIntentRequest(event => {
const { ga } = event;
if (!pluginConfig.suppressIntents) {
ga.visitor.event("Intents", event.intent.name, undefined, undefined, {
ni: 1
});
}
_.forEach(event.intent.params, (value, name) => {
if (
_.includes(pluginConfig.suppressSlots, name) ||
pluginConfig.suppressParams
)
return; // Suppressed slots don't log
ga.visitor.event(
"Params",
name,
value,
_.isNaN(value) ? undefined : +value,
{
ni: 1
}
);
});
});
skill.onBeforeStateChanged((event, reply, state) => {
event.ga.from = state.name;
event.ga.time("States", state.name);
});
skill.onAfterStateChanged((event, reply, transition) => {
const { ga } = event;
if (!ga.ignoreState || !pluginConfig.suppressStates) {
ga.pushPath(transition.say);
ga.visitor.event("States", ga.from, undefined, undefined, {
ni: 1
});
event.ga.timeEnd("States", ga.from);
} else {
event.ga.timeRemove("States", ga.from);
}
});
skill.onBeforeReplySent(event => {
event.ga.logPath(event);
event.ga.timeEnd("Interaction", "Request");
return send(event);
});
if (_.has(skill, "onAlexaSkillEvent.SkillEnabled")) {
skill["onAlexaSkillEvent.SkillEnabled"](defaultEventHandler);
}
if (_.has(skill, "onAlexaSkillEvent.SkillDisabled")) {
skill["onAlexaSkillEvent.SkillDisabled"](defaultEventHandler);
}
if (_.has(skill, "onAlexaSkillEvent.SkillAccountLinked")) {
skill["onAlexaSkillEvent.SkillAccountLinked"](defaultEventHandler);
}
function defaultEventHandler(event) {
event.ga = new EventRider(event, pluginConfig);
const eventData = event.request.type.split(".");
const category = eventData[0];
const action = eventData[1];
if (!pluginConfig.suppressEventHandler) {
event.ga.visitor.event(category, action);
}
send(event);
}
if (_.has(skill, "onAlexaSkillEvent.SkillPermissionChanged")) {
skill["onAlexaSkillEvent.SkillPermissionChanged"](permissionEventHandler);
}
if (_.has(skill, "onAlexaSkillEvent.SkillPermissionAccepted")) {
skill["onAlexaSkillEvent.SkillPermissionAccepted"](permissionEventHandler);
}
function permissionEventHandler(event) {
event.ga = new EventRider(event, pluginConfig);
const eventData = event.request.type.split(".");
const category = eventData[0];
const action = eventData[1];
event.request.body.acceptedPermissions.forEach(permissions => {
const label = permissions.scope;
if (!pluginConfig.suppressEventPermissions) {
event.ga.visitor.event(category, action, label);
}
});
send(event);
}
if (_.has(skill, "onAlexaHouseholdListEvent.ItemsCreated")) {
skill["onAlexaHouseholdListEvent.ItemsCreated"](listEventHandler);
}
if (_.has(skill, "onAlexaHouseholdListEvent.ItemsUpdated")) {
skill["onAlexaHouseholdListEvent.ItemsUpdated"](listEventHandler);
}
if (_.has(skill, "onAlexaHouseholdListEvent.ItemsDeleted")) {
skill["onAlexaHouseholdListEvent.ItemsDeleted"](listEventHandler);
}
function listEventHandler(event) {
event.ga = new EventRider(event, pluginConfig);
const eventData = event.request.type.split(".");
const category = eventData[0];
const action = eventData[1];
const { listId } = event.request.body;
event.request.body.listItemIds.forEach(listItemId => {
const label = `${listId} - ${listItemId}`;
if (!pluginConfig.suppressEventList) {
event.ga.visitor.event(category, action, label);
}
});
send(event);
}
function send(event) {
if (_.includes(pluginConfig.ignoreUsers, event.user.userId))
return Promise.resolve(null);
if (pluginConfig.suppressSending) return Promise.resolve(null);
return new Promise(resolve => {
event.ga.visitor.send((err, cnt) => {
if (err) {
console.error("Failed to send analytics");
console.error(err.stack || err);
return resolve(); // Analytics errors shouldn't tank the whole process
}
return resolve();
});
});
}
}
module.exports = register;