/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ var loop = loop || {}; loop.store = loop.store || {}; /** * The standalone metrics store is used to log activities to * analytics. * * Where possible we log events via receiving actions. However, some * combinations of actions and events require us to listen directly to * changes in the activeRoomStore so that we gain the benefit of the logic * in that store. */ loop.store.StandaloneMetricsStore = (function() { "use strict"; var ROOM_STATES = loop.store.ROOM_STATES; var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS; loop.store.metrics = loop.store.metrics || {}; var METRICS_GA_CATEGORY = loop.store.METRICS_GA_CATEGORY = { general: "/conversation/ interactions", download: "Firefox Downloads" }; var METRICS_GA_ACTIONS = loop.store.METRICS_GA_ACTIONS = { audioMute: "audio mute", button: "button click", download: "download button click", faceMute: "face mute", failed: "failed", linkClick: "link click", pageLoad: "page load messages", success: "success", support: "support link click" }; var StandaloneMetricsStore = loop.store.createStore({ actions: [ "connectedToSdkServers", "connectionFailure", "gotMediaPermission", "metricsLogJoinRoom", "joinedRoom", "leaveRoom", "mediaConnected", "recordClick", "remotePeerConnected", "retryAfterRoomFailure", "tileShown", "windowUnload" ], /** * Initializes the store and starts listening to the activeRoomStore. * * @param {Object} options Options for the store, should include a * reference to the activeRoomStore. */ initialize: function(options) { options = options || {}; if (!options.activeRoomStore) { throw new Error("Missing option activeRoomStore"); } // Don't bother listening if we're not storing metrics. // I'd love for ga to be an option, but that messes up the function // working properly. if (window.ga) { this.activeRoomStore = options.activeRoomStore; this.listenTo(options.activeRoomStore, "change", this._onActiveRoomStoreChange.bind(this)); } }, /** * Returns initial state data for this store. These are mainly reflections * of activeRoomStore so we can match initial states for change tracking * purposes. * * @return {Object} The initial store state. */ getInitialStoreState: function() { return { audioMuted: false, roomState: ROOM_STATES.INIT, videoMuted: false }; }, /** * Saves an event to ga. * * @param {String} category The category for the event. * @param {String} action The type of action. * @param {String} label The label detailing the action. */ _storeEvent: function(category, action, label) { // ga might not be defined if donottrack is enabled, see index.html. if (!window.ga) { return; } // For now all we need to do is forward onto ga. window.ga("send", "event", category, action, label); }, /** * Handles notification that we've connected to the sdk servers. */ connectedToSdkServers: function() { this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.success, "Joined sdk servers"); }, /** * Handles connection failures for various reasons. * * @param {sharedActions.ConnectionFailure} actionData */ connectionFailure: function(actionData) { switch(actionData.reason) { case FAILURE_DETAILS.MEDIA_DENIED: this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.failed, "Media denied"); break; case FAILURE_DETAILS.NO_MEDIA: this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.failed, "No media"); break; case FAILURE_DETAILS.ROOM_ALREADY_OPEN: this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.failed, "Room already open"); break; } }, /** * Handles media permssion being obtained. */ gotMediaPermission: function() { this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.success, "Media granted"); }, /** * Handles the user clicking the join room button. * * @param {sharedActions.MetricsLogJoinRoom} actionData */ metricsLogJoinRoom: function(actionData) { var label; if (actionData.userAgentHandledRoom) { label = actionData.ownRoom ? "Joined own room in Firefox" : "Joined in Firefox"; } else { label = "Join the conversation"; } this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button, label); }, /** * Handles the room having been joined on the loop-server. */ joinedRoom: function() { this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.success, "Joined room"); }, /** * Handles the user clicking the leave room button. */ leaveRoom: function() { this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button, "Leave conversation"); }, /** * Handles notification that two-way media has been achieved. */ mediaConnected: function() { this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.success, "Media connected"); }, /** * Handles recording link clicks. * * @param {sharedActions.RecordClick} actionData The data associated with * the link. */ recordClick: function(actionData) { this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.linkClick, actionData.linkInfo); }, /** * Handles notification that the remote peer is connected. */ remotePeerConnected: function() { this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.success, "Remote peer connected"); }, /** * Handles when the user retrys room activity after its failed initially * (e.g. on first load). */ retryAfterRoomFailure: function() { this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button, "Retry failed room"); }, /** * Handles when a tile was finally shown (potentially after a delay) */ tileShown: function() { this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.pageLoad, "Tile shown"); }, /** * Handles notifications that the activeRoomStore has changed, updating * the metrics for room state and mute state as necessary. */ _onActiveRoomStoreChange: function() { var roomStore = this.activeRoomStore.getStoreState(); this._checkRoomState(roomStore.roomState, roomStore.failureReason); this._checkMuteState("audio", roomStore.audioMuted); this._checkMuteState("video", roomStore.videoMuted); }, /** * Handles checking of the room state to look for events we need to send. * * @param {String} roomState The new room state. * @param {String} failureReason Optional, if the room is in the failure * state, this should contain the reason for * the failure. */ _checkRoomState: function(roomState, failureReason) { if (this._storeState.roomState === roomState) { return; } this._storeState.roomState = roomState; if (roomState === ROOM_STATES.FAILED && failureReason === FAILURE_DETAILS.EXPIRED_OR_INVALID) { this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.pageLoad, "Link expired or invalid"); } if (roomState === ROOM_STATES.FULL) { this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.pageLoad, "Room full"); } }, /** * Handles check of the mute state to look for events we need to send. * * @param {String} type The type of mute being adjusted, i.e. "audio" or * "video". * @param {Boolean} muted The new state of mute */ _checkMuteState: function(type, muted) { var muteItem = type + "Muted"; if (this._storeState[muteItem] === muted) { return; } this._storeState[muteItem] = muted; var muteType = type === "audio" ? METRICS_GA_ACTIONS.audioMute : METRICS_GA_ACTIONS.faceMute; var muteState = muted ? "mute" : "unmute"; this._storeEvent(METRICS_GA_CATEGORY.general, muteType, muteState); }, /** * Called when the window is unloaded, either by code, or by the user * explicitly closing it. Expected to do any necessary housekeeping, such * as shutting down the call cleanly and adding any relevant telemetry data. */ windowUnload: function() { if (this.activeRoomStore) { this.stopListening(this.activeRoomStore); } } }); return StandaloneMetricsStore; })();