Przeglądaj źródła

feat(deep_linking): add analytics

In order to be able to add analytics to the deep-linking pages the
lib-jitsi-meet initialization has been moved so it happens earlier.

The introduced `initPromise` will eventually disappear, once conference is
migrated into React and / or support for Temasys is dropped. At that stage, it
can be turned into a sync function which all platforms share.
j8
hristoterezov 7 lat temu
rodzic
commit
f14095ecfc

+ 8
- 43
conference.js Wyświetl plik

@@ -21,7 +21,6 @@ import {
21 21
     createSelectParticipantFailedEvent,
22 22
     createStreamSwitchDelayEvent,
23 23
     createTrackMutedEvent,
24
-    initAnalytics,
25 24
     sendAnalytics
26 25
 } from './react/features/analytics';
27 26
 import {
@@ -49,7 +48,6 @@ import {
49 48
 } from './react/features/base/conference';
50 49
 import { updateDeviceList } from './react/features/base/devices';
51 50
 import {
52
-    isAnalyticsEnabled,
53 51
     isFatalJitsiConnectionError,
54 52
     JitsiConferenceErrors,
55 53
     JitsiConferenceEvents,
@@ -685,53 +683,20 @@ export default {
685 683
     /**
686 684
      * Open new connection and join to the conference.
687 685
      * @param {object} options
688
-     * @param {string} roomName name of the conference
686
+     * @param {string} roomName - The name of the conference.
689 687
      * @returns {Promise}
690 688
      */
691 689
     init(options) {
692 690
         this.roomName = options.roomName;
693 691
 
694
-        // attaches global error handler, if there is already one, respect it
695
-        if (JitsiMeetJS.getGlobalOnErrorHandler) {
696
-            const oldOnErrorHandler = window.onerror;
697
-
698
-            // eslint-disable-next-line max-params
699
-            window.onerror = (message, source, lineno, colno, error) => {
700
-                JitsiMeetJS.getGlobalOnErrorHandler(
701
-                    message, source, lineno, colno, error);
702
-
703
-                if (oldOnErrorHandler) {
704
-                    oldOnErrorHandler(message, source, lineno, colno, error);
705
-                }
706
-            };
707
-
708
-            const oldOnUnhandledRejection = window.onunhandledrejection;
709
-
710
-            window.onunhandledrejection = function(event) {
711
-                JitsiMeetJS.getGlobalOnErrorHandler(
712
-                    null, null, null, null, event.reason);
713
-
714
-                if (oldOnUnhandledRejection) {
715
-                    oldOnUnhandledRejection(event);
716
-                }
717
-            };
718
-        }
719
-
720 692
         return (
721
-            JitsiMeetJS.init({
722
-                enableAnalyticsLogging: isAnalyticsEnabled(APP.store),
723
-                ...config
724
-            }).then(() => {
725
-                initAnalytics(APP.store);
726
-
727
-                return this.createInitialLocalTracksAndConnect(
728
-                    options.roomName, {
729
-                        startAudioOnly: config.startAudioOnly,
730
-                        startScreenSharing: config.startScreenSharing,
731
-                        startWithAudioMuted: config.startWithAudioMuted,
732
-                        startWithVideoMuted: config.startWithVideoMuted
733
-                    });
734
-            })
693
+            this.createInitialLocalTracksAndConnect(
694
+                options.roomName, {
695
+                    startAudioOnly: config.startAudioOnly,
696
+                    startScreenSharing: config.startScreenSharing,
697
+                    startWithAudioMuted: config.startWithAudioMuted,
698
+                    startWithVideoMuted: config.startWithVideoMuted
699
+                })
735 700
             .then(([ tracks, con ]) => {
736 701
                 tracks.forEach(track => {
737 702
                     if ((track.isAudioTrack() && this.isLocalAudioMuted())

+ 19
- 0
react/features/analytics/AnalyticsEvents.js Wyświetl plik

@@ -97,6 +97,25 @@ export function createAudioOnlyChangedEvent(enabled) {
97 97
     };
98 98
 }
99 99
 
100
+/**
101
+ * Creates an event for an action on the deep linking page.
102
+ *
103
+ * @param {string} action - The action that the event represents.
104
+ * @param {string} actionSubject - The subject that was acted upon.
105
+ * @param {boolean} attributes - Additional attributes to attach to the event.
106
+ * @returns {Object} The event in a format suitable for sending via
107
+ * sendAnalytics.
108
+ */
109
+export function createDeepLinkingPageEvent(
110
+        action, actionSubject, attributes = {}) {
111
+    return {
112
+        action,
113
+        actionSubject,
114
+        source: 'deepLinkingPage',
115
+        attributes
116
+    };
117
+}
118
+
100 119
 /**
101 120
  * Creates an event which indicates that a device was changed.
102 121
  *

+ 16
- 14
react/features/base/connection/actions.web.js Wyświetl plik

@@ -30,24 +30,26 @@ export function connect() {
30 30
 
31 31
         // XXX Lib-jitsi-meet does not accept uppercase letters.
32 32
         const room = state['features/base/conference'].room.toLowerCase();
33
+        const { initPromise } = state['features/base/lib-jitsi-meet'];
33 34
 
34 35
         // XXX For web based version we use conference initialization logic
35 36
         // from the old app (at the moment of writing).
36
-        return APP.conference.init({ roomName: room })
37
-            .catch(error => {
38
-                APP.API.notifyConferenceLeft(APP.conference.roomName);
39
-                logger.error(error);
37
+        return initPromise.then(() => APP.conference.init({
38
+            roomName: room
39
+        })).catch(error => {
40
+            APP.API.notifyConferenceLeft(APP.conference.roomName);
41
+            logger.error(error);
40 42
 
41
-                // TODO The following are in fact Errors raised by
42
-                // JitsiMeetJS.init() which should be taken care of in
43
-                // features/base/lib-jitsi-meet but we are not there yet on the
44
-                // Web at the time of this writing.
45
-                switch (error.name) {
46
-                case WEBRTC_NOT_READY:
47
-                case WEBRTC_NOT_SUPPORTED:
48
-                    dispatch(libInitError(error));
49
-                }
50
-            });
43
+            // TODO The following are in fact Errors raised by
44
+            // JitsiMeetJS.init() which should be taken care of in
45
+            // features/base/lib-jitsi-meet but we are not there yet on the
46
+            // Web at the time of this writing.
47
+            switch (error.name) {
48
+            case WEBRTC_NOT_READY:
49
+            case WEBRTC_NOT_SUPPORTED:
50
+                dispatch(libInitError(error));
51
+            }
52
+        });
51 53
     };
52 54
 }
53 55
 

+ 10
- 0
react/features/base/lib-jitsi-meet/actionTypes.js Wyświetl plik

@@ -27,6 +27,16 @@ export const LIB_DID_INIT = Symbol('LIB_DID_INIT');
27 27
  */
28 28
 export const LIB_INIT_ERROR = Symbol('LIB_INIT_ERROR');
29 29
 
30
+/**
31
+ * Action to dispatch the promise returned by JitsiMeetJS.init.
32
+ *
33
+ * {
34
+ *     type: LIB_INIT_PROMISE_CREATED,
35
+ *     initPromise: Promise
36
+ * }
37
+ */
38
+export const LIB_INIT_PROMISE_CREATED = Symbol('LIB_INIT_PROMISE_CREATED');
39
+
30 40
 /**
31 41
  * The type of Redux action which signals that {@link JitsiMeetJS} will be
32 42
  * disposed.

+ 17
- 11
react/features/base/lib-jitsi-meet/actions.js Wyświetl plik

@@ -7,6 +7,7 @@ import {
7 7
     LIB_DID_DISPOSE,
8 8
     LIB_DID_INIT,
9 9
     LIB_INIT_ERROR,
10
+    LIB_INIT_PROMISE_CREATED,
10 11
     LIB_WILL_DISPOSE,
11 12
     LIB_WILL_INIT,
12 13
     SET_WEBRTC_READY
@@ -44,22 +45,27 @@ export function initLib() {
44 45
             throw new Error('Cannot init lib-jitsi-meet without config');
45 46
         }
46 47
 
47
-        // FIXME Until the logic of conference.js is rewritten into the React
48
-        // app we, JitsiMeetJS.init is to not be used for the React app.
49
-        if (typeof APP !== 'undefined') {
50
-            return Promise.resolve();
51
-        }
52
-
53 48
         dispatch({ type: LIB_WILL_INIT });
54 49
 
50
+        const initPromise = JitsiMeetJS.init({
51
+            enableAnalyticsLogging: isAnalyticsEnabled(getState),
52
+            ...config
53
+        });
54
+
55
+        dispatch({
56
+            type: LIB_INIT_PROMISE_CREATED,
57
+            initPromise
58
+        });
59
+
55 60
         return (
56
-            JitsiMeetJS.init({
57
-                enableAnalyticsLogging: isAnalyticsEnabled(getState),
58
-                ...config
59
-            })
61
+            initPromise
60 62
                 .then(() => dispatch({ type: LIB_DID_INIT }))
61 63
                 .catch(error => {
62
-                    dispatch(libInitError(error));
64
+                    // TODO: See the comment in the connect action in
65
+                    // base/connection/actions.web.js.
66
+                    if (typeof APP === 'undefined') {
67
+                        dispatch(libInitError(error));
68
+                    }
63 69
 
64 70
                     // TODO Handle LIB_INIT_ERROR error somewhere instead.
65 71
                     console.error('lib-jitsi-meet failed to init:', error);

+ 51
- 2
react/features/base/lib-jitsi-meet/middleware.js Wyświetl plik

@@ -5,10 +5,13 @@ import { setLoggingConfig } from '../logging';
5 5
 import { PARTICIPANT_LEFT } from '../participants';
6 6
 import { MiddlewareRegistry } from '../redux';
7 7
 
8
+import JitsiMeetJS from './_';
8 9
 import { disposeLib, initLib, setWebRTCReady } from './actions';
9
-import { LIB_DID_INIT, LIB_INIT_ERROR } from './actionTypes';
10
+import { LIB_DID_INIT, LIB_INIT_ERROR, LIB_WILL_INIT } from './actionTypes';
10 11
 import { WEBRTC_NOT_READY, WEBRTC_NOT_SUPPORTED } from './constants';
11 12
 
13
+declare var APP: Object;
14
+
12 15
 /**
13 16
  * Middleware that captures PARTICIPANT_LEFT action for a local participant
14 17
  * (which signalizes that we finally left the app) and disposes lib-jitsi-meet.
@@ -21,8 +24,21 @@ import { WEBRTC_NOT_READY, WEBRTC_NOT_SUPPORTED } from './constants';
21 24
  */
22 25
 MiddlewareRegistry.register(store => next => action => {
23 26
     switch (action.type) {
27
+    case LIB_WILL_INIT:
28
+        // Moved from conference.js init method. It appears the error handlers
29
+        // are not used for mobile.
30
+        if (typeof APP !== 'undefined') {
31
+            _setErrorHandlers();
32
+        }
33
+        break;
24 34
     case LIB_DID_INIT:
25
-        store.dispatch(setWebRTCReady(true));
35
+        // FIXME: The web version doesn't need this action during initialization
36
+        // because it is still using the old logic from conference.js. We still
37
+        // have to reactify the old logic from conference.js and then maybe
38
+        // we'll need this action for web too.
39
+        if (typeof APP === 'undefined') {
40
+            store.dispatch(setWebRTCReady(true));
41
+        }
26 42
         break;
27 43
 
28 44
     case LIB_INIT_ERROR:
@@ -119,3 +135,36 @@ function _setConfig({ dispatch, getState }, next, action) {
119 135
 
120 136
     return result;
121 137
 }
138
+
139
+/**
140
+ * Attaches our custom error handlers to the window object.
141
+ *
142
+ * @returns {void}
143
+ */
144
+function _setErrorHandlers() {
145
+    // attaches global error handler, if there is already one, respect it
146
+    if (JitsiMeetJS.getGlobalOnErrorHandler) {
147
+        const oldOnErrorHandler = window.onerror;
148
+
149
+        // eslint-disable-next-line max-params
150
+        window.onerror = (message, source, lineno, colno, error) => {
151
+            JitsiMeetJS.getGlobalOnErrorHandler(
152
+                message, source, lineno, colno, error);
153
+
154
+            if (oldOnErrorHandler) {
155
+                oldOnErrorHandler(message, source, lineno, colno, error);
156
+            }
157
+        };
158
+
159
+        const oldOnUnhandledRejection = window.onunhandledrejection;
160
+
161
+        window.onunhandledrejection = function(event) {
162
+            JitsiMeetJS.getGlobalOnErrorHandler(
163
+                null, null, null, null, event.reason);
164
+
165
+            if (oldOnUnhandledRejection) {
166
+                oldOnUnhandledRejection(event);
167
+            }
168
+        };
169
+    }
170
+}

+ 9
- 1
react/features/base/lib-jitsi-meet/reducer.js Wyświetl plik

@@ -6,6 +6,7 @@ import {
6 6
     LIB_DID_DISPOSE,
7 7
     LIB_DID_INIT,
8 8
     LIB_INIT_ERROR,
9
+    LIB_INIT_PROMISE_CREATED,
9 10
     SET_WEBRTC_READY
10 11
 } from './actionTypes';
11 12
 
@@ -34,7 +35,14 @@ ReducerRegistry.register(
34 35
             return {
35 36
                 ...state,
36 37
                 initError: action.error,
37
-                initialized: false
38
+                initialized: false,
39
+                initPromise: undefined
40
+            };
41
+
42
+        case LIB_INIT_PROMISE_CREATED:
43
+            return {
44
+                ...state,
45
+                initPromise: action.initPromise
38 46
             };
39 47
 
40 48
         case SET_WEBRTC_READY:

+ 5
- 1
react/features/base/logging/middleware.js Wyświetl plik

@@ -114,7 +114,11 @@ function _initLogging(loggingConfig, isTestingEnabled) {
114 114
  * specified {@code action}.
115 115
  */
116 116
 function _libWillInit({ getState }, next, action) {
117
-    _setLogLevels(JitsiMeetJS, getState()['features/base/logging'].config);
117
+    // Adding the if in order to preserve the logic for web after enabling
118
+    // LIB_WILL_INIT action for web in initLib action.
119
+    if (typeof APP === 'undefined') {
120
+        _setLogLevels(JitsiMeetJS, getState()['features/base/logging'].config);
121
+    }
118 122
 
119 123
     return next(action);
120 124
 }

+ 10
- 0
react/features/deep-linking/components/DeepLinkingDesktopPage.js Wyświetl plik

@@ -5,6 +5,7 @@ import { AtlasKitThemeProvider } from '@atlaskit/theme';
5 5
 import React, { Component } from 'react';
6 6
 import { connect } from 'react-redux';
7 7
 
8
+import { createDeepLinkingPageEvent, sendAnalytics } from '../../analytics';
8 9
 import { translate } from '../../base/i18n';
9 10
 
10 11
 import {
@@ -60,6 +61,9 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
60 61
      */
61 62
     componentDidMount() {
62 63
         this._openDesktopApp();
64
+        sendAnalytics(
65
+            createDeepLinkingPageEvent(
66
+                'displayed', 'DeepLinkingDesktop', { isMobileBrowser: false }));
63 67
     }
64 68
 
65 69
     /**
@@ -147,6 +151,9 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
147 151
      * @returns {void}
148 152
      */
149 153
     _onTryAgain() {
154
+        sendAnalytics(
155
+            createDeepLinkingPageEvent(
156
+                'clicked', 'tryAgainButton', { isMobileBrowser: false }));
150 157
         this._openDesktopApp();
151 158
     }
152 159
 
@@ -158,6 +165,9 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
158 165
      * @returns {void}
159 166
      */
160 167
     _onLaunchWeb() {
168
+        sendAnalytics(
169
+            createDeepLinkingPageEvent(
170
+                'clicked', 'launchWebButton', { isMobileBrowser: false }));
161 171
         this.props.dispatch(openWebApp());
162 172
     }
163 173
 }

+ 57
- 2
react/features/deep-linking/components/DeepLinkingMobilePage.js Wyświetl plik

@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
4 4
 import React, { Component } from 'react';
5 5
 import { connect } from 'react-redux';
6 6
 
7
+import { createDeepLinkingPageEvent, sendAnalytics } from '../../analytics';
7 8
 import { translate, translateToHTML } from '../../base/i18n';
8 9
 import { HideNotificationBarStyle, Platform } from '../../base/react';
9 10
 import { DialInSummary } from '../../invite';
@@ -63,6 +64,20 @@ class DeepLinkingMobilePage extends Component<*, *> {
63 64
         t: PropTypes.func
64 65
     };
65 66
 
67
+    /**
68
+     * Initializes a new {@code DeepLinkingMobilePage} instance.
69
+     *
70
+     * @param {Object} props - The read-only React {@code Component} props with
71
+     * which the new instance is to be initialized.
72
+     */
73
+    constructor(props) {
74
+        super(props);
75
+
76
+        // Bind event handlers so they are only bound once per instance.
77
+        this._onDownloadApp = this._onDownloadApp.bind(this);
78
+        this._onOpenApp = this._onOpenApp.bind(this);
79
+    }
80
+
66 81
     /**
67 82
      * Initializes the text and URL of the `Start a conference` / `Join the
68 83
      * conversation` button which takes the user to the mobile app.
@@ -75,6 +90,17 @@ class DeepLinkingMobilePage extends Component<*, *> {
75 90
         });
76 91
     }
77 92
 
93
+    /**
94
+     * Implements the Component's componentDidMount method.
95
+     *
96
+     * @inheritdoc
97
+     */
98
+    componentDidMount() {
99
+        sendAnalytics(
100
+            createDeepLinkingPageEvent(
101
+                'displayed', 'DeepLinkingMobile', { isMobileBrowser: true }));
102
+    }
103
+
78 104
     /**
79 105
      * Implements React's {@link Component#render()}.
80 106
      *
@@ -110,14 +136,17 @@ class DeepLinkingMobilePage extends Component<*, *> {
110 136
                                 { app: NATIVE_APP_NAME })
111 137
                         }
112 138
                     </p>
113
-                    <a href = { _URLS[Platform.OS] }>
139
+                    <a
140
+                        href = { _URLS[Platform.OS] }
141
+                        onClick = { this._onDownloadApp } >
114 142
                         <button className = { downloadButtonClassName }>
115 143
                             { t(`${_TNS}.downloadApp`) }
116 144
                         </button>
117 145
                     </a>
118 146
                     <a
119 147
                         className = { `${_SNS}__href` }
120
-                        href = { this.state.joinURL }>
148
+                        href = { this.state.joinURL }
149
+                        onClick = { this._onOpenApp }>
121 150
                         {/* <button className = { `${_SNS}__button` }> */}
122 151
                         { t(`${_TNS}.openApp`) }
123 152
                         {/* </button> */}
@@ -131,6 +160,32 @@ class DeepLinkingMobilePage extends Component<*, *> {
131 160
             </div>
132 161
         );
133 162
     }
163
+
164
+    _onDownloadApp: () => {};
165
+
166
+    /**
167
+     * Handles download app button clicks.
168
+     *
169
+     * @returns {void}
170
+     */
171
+    _onDownloadApp() {
172
+        sendAnalytics(
173
+            createDeepLinkingPageEvent(
174
+                'clicked', 'downloadAppButton', { isMobileBrowser: true }));
175
+    }
176
+
177
+    _onOpenApp: () => {};
178
+
179
+    /**
180
+     * Handles open app button clicks.
181
+     *
182
+     * @returns {void}
183
+     */
184
+    _onOpenApp() {
185
+        sendAnalytics(
186
+            createDeepLinkingPageEvent(
187
+                'clicked', 'openAppButton', { isMobileBrowser: true }));
188
+    }
134 189
 }
135 190
 
136 191
 /**

+ 12
- 0
react/features/deep-linking/components/NoMobileApp.js Wyświetl plik

@@ -2,6 +2,7 @@
2 2
 
3 3
 import React, { Component } from 'react';
4 4
 
5
+import { createDeepLinkingPageEvent, sendAnalytics } from '../../analytics';
5 6
 import { HideNotificationBarStyle } from '../../base/react';
6 7
 
7 8
 declare var interfaceConfig: Object;
@@ -12,6 +13,17 @@ declare var interfaceConfig: Object;
12 13
  * @class NoMobileApp
13 14
  */
14 15
 export default class NoMobileApp extends Component<*> {
16
+    /**
17
+     * Implements the Component's componentDidMount method.
18
+     *
19
+     * @inheritdoc
20
+     */
21
+    componentDidMount() {
22
+        sendAnalytics(
23
+            createDeepLinkingPageEvent(
24
+                'displayed', 'noMobileApp', { isMobileBrowser: true }));
25
+    }
26
+
15 27
     /**
16 28
      * Renders the component.
17 29
      *

+ 1
- 14
react/features/welcome/components/WelcomePage.web.js Wyświetl plik

@@ -1,4 +1,4 @@
1
-/* global APP, config, interfaceConfig, JitsiMeetJS */
1
+/* global interfaceConfig */
2 2
 
3 3
 import Button from '@atlaskit/button';
4 4
 import { FieldTextStateless } from '@atlaskit/field-text';
@@ -6,9 +6,7 @@ import { AtlasKitThemeProvider } from '@atlaskit/theme';
6 6
 import React from 'react';
7 7
 import { connect } from 'react-redux';
8 8
 
9
-import { initAnalytics } from '../../analytics';
10 9
 import { translate } from '../../base/i18n';
11
-import { isAnalyticsEnabled } from '../../base/lib-jitsi-meet';
12 10
 import { HideNotificationBarStyle, Watermarks } from '../../base/react';
13 11
 
14 12
 import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
@@ -71,17 +69,6 @@ class WelcomePage extends AbstractWelcomePage {
71 69
     componentDidMount() {
72 70
         document.body.classList.add('welcome-page');
73 71
 
74
-        // FIXME: This is not the best place for this logic. Ideally we should
75
-        // use features/base/lib-jitsi-meet#initLib() action for this use case.
76
-        // But currently lib-jitsi-meet#initLib()'s logic works for mobile only
77
-        // (on web it ends up with infinite loop over initLib).
78
-        JitsiMeetJS.init({
79
-            enableAnalyticsLogging: isAnalyticsEnabled(APP.store),
80
-            ...config
81
-        }).then(() => {
82
-            initAnalytics(APP.store);
83
-        });
84
-
85 72
         if (this.state.generateRoomnames) {
86 73
             this._updateRoomname();
87 74
         }

Ładowanie…
Anuluj
Zapisz