From 946ec4e2ae5a7c8bc36d644ff0d456fad3a93c9e Mon Sep 17 00:00:00 2001 From: Alex Brazier Date: Wed, 22 Feb 2017 23:31:20 +0000 Subject: [PATCH 01/11] Add support for Google Analytics --- .meteor/packages | 2 +- .meteor/versions | 2 +- .../README.md | 20 ++- .../rocketchat-analytics/client/loadScript.js | 44 ++++++ .../client/trackEvents.js | 137 ++++++++++++++++++ .../package.js | 6 +- .../rocketchat-analytics/server/settings.js | 59 ++++++++ packages/rocketchat-i18n/i18n/en.i18n.json | 2 + .../rocketchat-piwik/client/loadScript.js | 29 ---- .../rocketchat-piwik/client/trackEvents.js | 126 ---------------- packages/rocketchat-piwik/server/settings.js | 33 ----- 11 files changed, 260 insertions(+), 200 deletions(-) rename packages/{rocketchat-piwik => rocketchat-analytics}/README.md (84%) create mode 100644 packages/rocketchat-analytics/client/loadScript.js create mode 100644 packages/rocketchat-analytics/client/trackEvents.js rename packages/{rocketchat-piwik => rocketchat-analytics}/package.js (80%) create mode 100644 packages/rocketchat-analytics/server/settings.js delete mode 100644 packages/rocketchat-piwik/client/loadScript.js delete mode 100644 packages/rocketchat-piwik/client/trackEvents.js delete mode 100644 packages/rocketchat-piwik/server/settings.js diff --git a/.meteor/packages b/.meteor/packages index 43602babb6b..16225b4b507 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -39,6 +39,7 @@ standard-minifier-js tracker rocketchat:action-links +rocketchat:analytics rocketchat:api rocketchat:assets rocketchat:authorization @@ -89,7 +90,6 @@ rocketchat:migrations rocketchat:oauth2-server-config rocketchat:oembed rocketchat:otr -rocketchat:piwik rocketchat:push-notifications rocketchat:reactions rocketchat:sandstorm diff --git a/.meteor/versions b/.meteor/versions index a412929b769..ab646790ad1 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -120,6 +120,7 @@ reactive-var@1.0.11 reload@1.1.11 retry@1.0.9 rocketchat:action-links@0.0.1 +rocketchat:analytics@0.0.2 rocketchat:api@0.0.1 rocketchat:assets@0.0.1 rocketchat:authorization@0.0.1 @@ -175,7 +176,6 @@ rocketchat:oauth2-server@2.0.0 rocketchat:oauth2-server-config@1.0.0 rocketchat:oembed@0.0.1 rocketchat:otr@0.0.1 -rocketchat:piwik@0.0.1 rocketchat:push-notifications@0.0.1 rocketchat:reactions@0.0.1 rocketchat:sandstorm@0.0.1 diff --git a/packages/rocketchat-piwik/README.md b/packages/rocketchat-analytics/README.md similarity index 84% rename from packages/rocketchat-piwik/README.md rename to packages/rocketchat-analytics/README.md index d1e6a965dc8..4381e889f80 100644 --- a/packages/rocketchat-piwik/README.md +++ b/packages/rocketchat-analytics/README.md @@ -1,23 +1,29 @@ -# Piwik Analytics Tracking +# Analytics Tracking +## Google Analytics + +### Settings +* **Tracking ID**: Google Analytics tracking id found on analytics admin page - looks like UA-xxxxxxxx-x + +## Piwik Rocket.Chat supports adding Piwik url and site id to track the analytics of your server. Through this you will be able to see details analytics of per user, including how many messages a session they send via custom events in Piwik and how many channels they interact with. -## Piwik & Google Chrome +### Piwik & Google Chrome Google Chrome has a setting which sends a Do Not Track with each request and by default Piwik respects that and you have to manually disable that feature inside of Piwik. [Piwik has great documentation on how to disable this feature.](http://piwik.org/docs/privacy/#step-4-respect-donottrack-preference) -## Piwik Settings in Rocket.Chat -Settings -> Piwik - -### General +### Settings * **URL**: The url where your piwik is located. This is used for generating the tracking code and is required. Recommended format is: `//rocketchat.piwikpro.com/` * **Client ID**: The client id which this website is. This is a number without any decimals, example: `1` -### Features Enabled +## Settings in Rocket.Chat +Settings -> Analytics + +## Features Enabled * **Messages**: `true/false` determines whether to use custom events to track how many times a user does something with a message. This ranges from sending messages, editing messages, reacting to messages, pinning, starring, and etc. * **Rooms**: `true/false` determines whether to use custom events to track how many times a user does actions related to a room (channel, direct message, group). This ranges from creating, leaving, archiving, renaming, and etc. * **Users**: `true/false` determines whether to use custom events to track how many times a user does actions related to users. This ranges from resetting passwords, creating new users, updating profile pictures, etc. diff --git a/packages/rocketchat-analytics/client/loadScript.js b/packages/rocketchat-analytics/client/loadScript.js new file mode 100644 index 00000000000..a22e8afe317 --- /dev/null +++ b/packages/rocketchat-analytics/client/loadScript.js @@ -0,0 +1,44 @@ +Template.body.onRendered(() => { + Tracker.autorun((c) => { + const piwikUrl = RocketChat.settings.get('PiwikAnalytics_enabled') && RocketChat.settings.get('PiwikAnalytics_url'); + const piwikSiteId = piwikUrl && RocketChat.settings.get('PiwikAnalytics_siteId'); + const googleId = RocketChat.settings.get('GoogleAnalytics_enabled') && RocketChat.settings.get('GoogleAnalytics_ID'); + if (piwikSiteId || googleId) { + c.stop(); + + if (piwikSiteId) { + window._paq = window._paq || []; + if (Meteor.userId()) { + window._paq.push(['setUserId', Meteor.userId()]); + } + + window._paq.push(['trackPageView']); + window._paq.push(['enableLinkTracking']); + (() => { + window._paq.push(['setTrackerUrl', piwikUrl + 'piwik.php']); + window._paq.push(['setSiteId', Number.parseInt(piwikSiteId)]); + const d = document; + const g = d.createElement('script'); + const s = d.getElementsByTagName('script')[0]; + g.type = 'text/javascript'; + g.async = true; + g.defer = true; + g.src = piwikUrl + 'piwik.js'; + s.parentNode.insertBefore(g, s); + })(); + } + + if (googleId) { + /*eslint-disable */ + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); + + ga('create', googleId, 'auto'); + ga('send', 'pageview'); + /*eslint-enable */ + } + } + }); +}); diff --git a/packages/rocketchat-analytics/client/trackEvents.js b/packages/rocketchat-analytics/client/trackEvents.js new file mode 100644 index 00000000000..c672e8bb73d --- /dev/null +++ b/packages/rocketchat-analytics/client/trackEvents.js @@ -0,0 +1,137 @@ +function trackEvent(category, action, label) { + if (window._paq) { + window._paq.push(['trackEvent', category, action, label]); + } + if (window.ga) { + window.ga('send', 'event', category, action, label); + } +} + +if (!window._paq || window.ga) { + //Trigger the trackPageView manually as the page views don't seem to be tracked + FlowRouter.triggers.enter([(route) => { + if (window._paq) { + const http = location.protocol; + const slashes = http.concat('//'); + const host = slashes.concat(window.location.hostname); + window._paq.push(['setCustomUrl', host + route.path]); + window._paq.push(['trackPageView']); + } + if (window.ga) { + window.ga('send', 'pageview', route.path); + } + }]); + + //Login page has manual switches + RocketChat.callbacks.add('loginPageStateChange', (state) => { + trackEvent('Navigation', 'Login Page State Change', state); + }, RocketChat.callbacks.priority.MEDIUM, 'analytics-login-state-change'); + + //Messsages + RocketChat.callbacks.add('afterSaveMessage', (message) => { + if ((window._paq || window.ga) && RocketChat.settings.get('Analytics_features_messages')) { + const room = ChatRoom.findOne({ _id: message.rid }); + trackEvent('Message', 'Send', room.name + ' (' + room._id + ')'); + } + }, 2000, 'trackEvents'); + + //Rooms + RocketChat.callbacks.add('afterCreateChannel', (owner, room) => { + if (RocketChat.settings.get('Analytics_features_rooms')) { + trackEvent('Room', 'Create', room.name + ' (' + room._id + ')'); + } + }, RocketChat.callbacks.priority.MEDIUM, 'analytics-after-create-channel'); + + RocketChat.callbacks.add('roomNameChanged', (room) => { + if (RocketChat.settings.get('Analytics_features_rooms')) { + trackEvent('Room', 'Changed Name', room.name + ' (' + room._id + ')'); + } + }, RocketChat.callbacks.priority.MEDIUM, 'analytics-room-name-changed'); + + RocketChat.callbacks.add('roomTopicChanged', (room) => { + if (RocketChat.settings.get('Analytics_features_rooms')) { + trackEvent('Room', 'Changed Topic', room.name + ' (' + room._id + ')'); + } + }, RocketChat.callbacks.priority.MEDIUM, 'analytics-room-topic-changed'); + + RocketChat.callbacks.add('roomTypeChanged', (room) => { + if (RocketChat.settings.get('Analytics_features_rooms')) { + trackEvent('Room', 'Changed Room Type', room.name + ' (' + room._id + ')'); + } + }, RocketChat.callbacks.priority.MEDIUM, 'analytics-room-type-changed'); + + RocketChat.callbacks.add('archiveRoom', (room) => { + if (RocketChat.settings.get('Analytics_features_rooms')) { + trackEvent('Room', 'Archived', room.name + ' (' + room._id + ')'); + } + }, RocketChat.callbacks.priority.MEDIUM, 'analytics-archive-room'); + + RocketChat.callbacks.add('unarchiveRoom', (room) => { + if (RocketChat.settings.get('Analytics_features_rooms')) { + trackEvent('Room', 'Unarchived', room.name + ' (' + room._id + ')'); + } + }, RocketChat.callbacks.priority.MEDIUM, 'analytics-unarchive-room'); + + //Users + //Track logins and associate user ids with piwik + (() => { + let oldUserId = null; + + Meteor.autorun(() => { + const newUserId = Meteor.userId(); + if (oldUserId === null && newUserId) { + if (window._paq && RocketChat.settings.get('Analytics_features_users')) { + trackEvent('User', 'Login', newUserId); + window._paq.push(['setUserId', newUserId]); + } + } else if (newUserId === null && oldUserId) { + if (window._paq && RocketChat.settings.get('Analytics_features_users')) { + trackEvent('User', 'Logout', oldUserId); + } + } + oldUserId = Meteor.userId(); + }); + })(); + + RocketChat.callbacks.add('userRegistered', () => { + if (RocketChat.settings.get('Analytics_features_users')) { + trackEvent('User', 'Registered'); + } + }, RocketChat.callbacks.priority.MEDIUM, 'piwik-user-resitered'); + + RocketChat.callbacks.add('usernameSet', () => { + if (RocketChat.settings.get('Analytics_features_users')) { + trackEvent('User', 'Username Set'); + } + }, RocketChat.callbacks.priority.MEDIUM, 'piweik-username-set'); + + RocketChat.callbacks.add('userPasswordReset', () => { + if (RocketChat.settings.get('Analytics_features_users')) { + trackEvent('User', 'Reset Password'); + } + }, RocketChat.callbacks.priority.MEDIUM, 'piwik-user-password-reset'); + + RocketChat.callbacks.add('userConfirmationEmailRequested', () => { + if (RocketChat.settings.get('Analytics_features_users')) { + trackEvent('User', 'Confirmation Email Requested'); + } + }, RocketChat.callbacks.priority.MEDIUM, 'piwik-user-confirmation-email-requested'); + + RocketChat.callbacks.add('userForgotPasswordEmailRequested', () => { + if (RocketChat.settings.get('Analytics_features_users')) { + trackEvent('User', 'Forgot Password Email Requested'); + } + }, RocketChat.callbacks.priority.MEDIUM, 'piwik-user-forgot-password-email-requested'); + + RocketChat.callbacks.add('userStatusManuallySet', (status) => { + if (RocketChat.settings.get('Analytics_features_users')) { + trackEvent('User', 'Status Manually Changed', status); + } + }, RocketChat.callbacks.priority.MEDIUM, 'analytics-user-status-manually-set'); + + RocketChat.callbacks.add('userAvatarSet', (service) => { + if (RocketChat.settings.get('Analytics_features_users')) { + trackEvent('User', 'Avatar Changed', service); + } + }, RocketChat.callbacks.priority.MEDIUM, 'analytics-user-avatar-set'); +} diff --git a/packages/rocketchat-piwik/package.js b/packages/rocketchat-analytics/package.js similarity index 80% rename from packages/rocketchat-piwik/package.js rename to packages/rocketchat-analytics/package.js index 821e1f6e1d6..913a11aa748 100644 --- a/packages/rocketchat-piwik/package.js +++ b/packages/rocketchat-analytics/package.js @@ -1,7 +1,7 @@ Package.describe({ - name: 'rocketchat:piwik', - version: '0.0.1', - summary: 'Piwik integeration for Rocket.Chat', + name: 'rocketchat:analytics', + version: '0.0.2', + summary: 'Analytics integeration for Rocket.Chat', git: '' }); diff --git a/packages/rocketchat-analytics/server/settings.js b/packages/rocketchat-analytics/server/settings.js new file mode 100644 index 00000000000..e639cf8eafe --- /dev/null +++ b/packages/rocketchat-analytics/server/settings.js @@ -0,0 +1,59 @@ +RocketChat.settings.addGroup('Analytics', function addSettings() { + this.section('Piwik', function() { + const enableQuery = {_id: 'PiwikAnalytics_enabled', value: true}; + this.add('PiwikAnalytics_enabled', false, { + type: 'boolean', + public: true, + i18nLabel: 'Enable' + }); + this.add('PiwikAnalytics_url', '', { + type: 'string', + public: true, + i18nLabel: 'URL', + enableQuery + }); + this.add('PiwikAnalytics_siteId', '', { + type: 'string', + public: true, + i18nLabel: 'Client_ID', + enableQuery + }); + }); + + this.section('Analytics_Google', function() { + const enableQuery = {_id: 'GoogleAnalytics_enabled', value: true}; + this.add('GoogleAnalytics_enabled', false, { + type: 'boolean', + public: true, + i18nLabel: 'Enable' + }); + + this.add('GoogleAnalytics_ID', '', { + type: 'string', + public: true, + i18nLabel: 'Analytics_Google_id', + enableQuery + }); + }); + + this.section('Analytics_features_enabled', function addFeaturesEnabledSettings() { + this.add('Analytics_features_messages', true, { + type: 'boolean', + public: true, + i18nLabel: 'Messages', + i18nDescription: 'Analytics_features_messages_Description' + }); + this.add('Analytics_features_rooms', true, { + type: 'boolean', + public: true, + i18nLabel: 'Rooms', + i18nDescription: 'Analytics_features_rooms_Description' + }); + this.add('Analytics_features_users', true, { + type: 'boolean', + public: true, + i18nLabel: 'Users', + i18nDescription: 'Analytics_features_users_Description' + }); + }); +}); diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 571c573e2d1..8eb1c5ec278 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -154,6 +154,8 @@ "Analytics_features_messages_Description": "Tracks custom events related to actions a user does on messages.", "Analytics_features_rooms_Description": "Tracks custom events related to actions on a channel or group (create, leave, delete).", "Analytics_features_users_Description": "Tracks custom events related to actions related to users (password reset times, profile picture change, etc).", + "Analytics_Google": "Google Analytics", + "Analytics_Google_id": "Tracking ID", "and": "and", "And_more": "And __length__ more", "Animals_and_Nature": "Animals & Nature", diff --git a/packages/rocketchat-piwik/client/loadScript.js b/packages/rocketchat-piwik/client/loadScript.js deleted file mode 100644 index 5c39be4e24b..00000000000 --- a/packages/rocketchat-piwik/client/loadScript.js +++ /dev/null @@ -1,29 +0,0 @@ -Template.body.onRendered(() => { - Tracker.autorun((c) => { - const url = RocketChat.settings.get('PiwikAnalytics_url'); - const siteId = RocketChat.settings.get('PiwikAnalytics_siteId'); - - if (Match.test(url, String) && url.trim() !== '' && Match.test(siteId, String) && siteId.trim() !== '') { - c.stop(); - window._paq = window._paq || []; - if (Meteor.userId()) { - window._paq.push(['setUserId', Meteor.userId()]); - } - - window._paq.push(['trackPageView']); - window._paq.push(['enableLinkTracking']); - (() => { - window._paq.push(['setTrackerUrl', url + 'piwik.php']); - window._paq.push(['setSiteId', Number.parseInt(siteId)]); - const d = document; - const g = d.createElement('script'); - const s = d.getElementsByTagName('script')[0]; - g.type = 'text/javascript'; - g.async = true; - g.defer = true; - g.src = url + 'piwik.js'; - s.parentNode.insertBefore(g, s); - })(); - } - }); -}); diff --git a/packages/rocketchat-piwik/client/trackEvents.js b/packages/rocketchat-piwik/client/trackEvents.js deleted file mode 100644 index cd66c66251c..00000000000 --- a/packages/rocketchat-piwik/client/trackEvents.js +++ /dev/null @@ -1,126 +0,0 @@ -//Trigger the trackPageView manually as the page views don't seem to be tracked -FlowRouter.triggers.enter([(route) => { - if (window._paq) { - const http = location.protocol; - const slashes = http.concat('//'); - const host = slashes.concat(window.location.hostname); - - window._paq.push(['setCustomUrl', host + route.path]); - window._paq.push(['trackPageView']); - } -}]); - -//Login page has manual switches -RocketChat.callbacks.add('loginPageStateChange', (state) => { - if (window._paq) { - window._paq.push(['trackEvent', 'Navigation', 'Login Page State Change', state]); - } -}, RocketChat.callbacks.priority.MEDIUM, 'piwik-login-state-change'); - -//Messsages -RocketChat.callbacks.add('afterSaveMessage', (message) => { - if (window._paq && RocketChat.settings.get('PiwikAnalytics_features_messages')) { - const room = ChatRoom.findOne({ _id: message.rid }); - window._paq.push(['trackEvent', 'Message', 'Send', room.name + ' (' + room._id + ')' ]); - } -}, 2000, 'trackEvents'); - -//Rooms -RocketChat.callbacks.add('afterCreateChannel', (owner, room) => { - if (window._paq && RocketChat.settings.get('PiwikAnalytics_features_rooms')) { - window._paq.push(['trackEvent', 'Room', 'Create', room.name + ' (' + room._id + ')' ]); - } -}, RocketChat.callbacks.priority.MEDIUM, 'piwik-after-create-channel'); - -RocketChat.callbacks.add('roomNameChanged', (room) => { - if (window._paq && RocketChat.settings.get('PiwikAnalytics_features_rooms')) { - window._paq.push(['trackEvent', 'Room', 'Changed Name', room.name + ' (' + room._id + ')' ]); - } -}, RocketChat.callbacks.priority.MEDIUM, 'piwik-room-name-changed'); - -RocketChat.callbacks.add('roomTopicChanged', (room) => { - if (window._paq && RocketChat.settings.get('PiwikAnalytics_features_rooms')) { - window._paq.push(['trackEvent', 'Room', 'Changed Topic', room.name + ' (' + room._id + ')' ]); - } -}, RocketChat.callbacks.priority.MEDIUM, 'piwik-room-topic-changed'); - -RocketChat.callbacks.add('roomTypeChanged', (room) => { - if (window._paq && RocketChat.settings.get('PiwikAnalytics_features_rooms')) { - window._paq.push(['trackEvent', 'Room', 'Changed Room Type', room.name + ' (' + room._id + ')' ]); - } -}, RocketChat.callbacks.priority.MEDIUM, 'piwik-room-type-changed'); - -RocketChat.callbacks.add('archiveRoom', (room) => { - if (window._paq && RocketChat.settings.get('PiwikAnalytics_features_rooms')) { - window._paq.push(['trackEvent', 'Room', 'Archived', room.name + ' (' + room._id + ')' ]); - } -}, RocketChat.callbacks.priority.MEDIUM, 'piwik-archive-room'); - -RocketChat.callbacks.add('unarchiveRoom', (room) => { - if (window._paq && RocketChat.settings.get('PiwikAnalytics_features_rooms')) { - window._paq.push(['trackEvent', 'Room', 'Unarchived', room.name + ' (' + room._id + ')' ]); - } -}, RocketChat.callbacks.priority.MEDIUM, 'piwik-unarchive-room'); - -//Users -//Track logins and associate user ids with piwik -(() => { - let oldUserId = null; - - Meteor.autorun(() => { - const newUserId = Meteor.userId(); - if (oldUserId === null && newUserId) { - if (window._paq && RocketChat.settings.get('PiwikAnalytics_features_users')) { - window._paq.push(['trackEvent', 'User', 'Login', newUserId ]); - window._paq.push(['setUserId', newUserId]); - } - } else if (newUserId === null && oldUserId) { - if (window._paq && RocketChat.settings.get('PiwikAnalytics_features_users')) { - window._paq.push(['trackEvent', 'User', 'Logout', oldUserId ]); - } - } - oldUserId = Meteor.userId(); - }); -})(); - -RocketChat.callbacks.add('userRegistered', () => { - if (window._paq && RocketChat.settings.get('PiwikAnalytics_features_users')) { - window._paq.push(['trackEvent', 'User', 'Registered']); - } -}, RocketChat.callbacks.priority.MEDIUM, 'piwik-user-resitered'); - -RocketChat.callbacks.add('usernameSet', () => { - if (window._paq && RocketChat.settings.get('PiwikAnalytics_features_users')) { - window._paq.push(['trackEvent', 'User', 'Username Set']); - } -}, RocketChat.callbacks.priority.MEDIUM, 'piweik-username-set'); - -RocketChat.callbacks.add('userPasswordReset', () => { - if (window._paq && RocketChat.settings.get('PiwikAnalytics_features_users')) { - window._paq.push(['trackEvent', 'User', 'Reset Password']); - } -}, RocketChat.callbacks.priority.MEDIUM, 'piwik-user-password-reset'); - -RocketChat.callbacks.add('userConfirmationEmailRequested', () => { - if (window._paq && RocketChat.settings.get('PiwikAnalytics_features_users')) { - window._paq.push(['trackEvent', 'User', 'Confirmation Email Requested']); - } -}, RocketChat.callbacks.priority.MEDIUM, 'piwik-user-confirmation-email-requested'); - -RocketChat.callbacks.add('userForgotPasswordEmailRequested', () => { - if (window._paq && RocketChat.settings.get('PiwikAnalytics_features_users')) { - window._paq.push(['trackEvent', 'User', 'Forgot Password Email Requested']); - } -}, RocketChat.callbacks.priority.MEDIUM, 'piwik-user-forgot-password-email-requested'); - -RocketChat.callbacks.add('userStatusManuallySet', (status) => { - if (window._paq && RocketChat.settings.get('PiwikAnalytics_features_users')) { - window._paq.push(['trackEvent', 'User', 'Status Manually Changed', status]); - } -}, RocketChat.callbacks.priority.MEDIUM, 'piwik-user-status-manually-set'); - -RocketChat.callbacks.add('userAvatarSet', (service) => { - if (window._paq && RocketChat.settings.get('PiwikAnalytics_features_users')) { - window._paq.push(['trackEvent', 'User', 'Avatar Changed', service]); - } -}, RocketChat.callbacks.priority.MEDIUM, 'piwik-user-avatar-set'); diff --git a/packages/rocketchat-piwik/server/settings.js b/packages/rocketchat-piwik/server/settings.js deleted file mode 100644 index eb6e96f9655..00000000000 --- a/packages/rocketchat-piwik/server/settings.js +++ /dev/null @@ -1,33 +0,0 @@ -RocketChat.settings.addGroup('Piwik', function addSettings() { - this.add('PiwikAnalytics_url', '', { - type: 'string', - public: true, - i18nLabel: 'URL' - }); - this.add('PiwikAnalytics_siteId', '', { - type: 'string', - public: true, - i18nLabel: 'Client_ID' - }); - - this.section('Analytics_features_enabled', function addFeaturesEnabledSettings() { - this.add('PiwikAnalytics_features_messages', true, { - type: 'boolean', - public: true, - i18nLabel: 'Messages', - i18nDescription: 'Analytics_features_messages_Description' - }); - this.add('PiwikAnalytics_features_rooms', true, { - type: 'boolean', - public: true, - i18nLabel: 'Rooms', - i18nDescription: 'Analytics_features_rooms_Description' - }); - this.add('PiwikAnalytics_features_users', true, { - type: 'boolean', - public: true, - i18nLabel: 'Users', - i18nDescription: 'Analytics_features_users_Description' - }); - }); -}); From 24cabcf313ffb32cc20c02f32e8df335e0948551 Mon Sep 17 00:00:00 2001 From: Alex Brazier Date: Fri, 24 Feb 2017 23:03:03 +0000 Subject: [PATCH 02/11] Add migration for analytics --- server/startup/migrations/v090.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 server/startup/migrations/v090.js diff --git a/server/startup/migrations/v090.js b/server/startup/migrations/v090.js new file mode 100644 index 00000000000..df469f86155 --- /dev/null +++ b/server/startup/migrations/v090.js @@ -0,0 +1,12 @@ +RocketChat.Migrations.add({ + version: 90, + up() { + RocketChat.models.Settings.remove({ _id: 'Piwik', type: 'group' }); + + const settings = RocketChat.models.Settings.find({$or: [{_id: 'PiwikAnalytics_url', value: {$ne: null}}, {_id: 'PiwikAnalytics_siteId', value: {$ne: null}}]}).fetch(); + + if (settings && settings.length === 2) { + RocketChat.models.Settings.upsert({_id: 'PiwikAnalytics_enabled'}, {$set: {value: true}}); + } + } +}); From 69c607152b32ca4beb7e407666a1e5ee5aabdd13 Mon Sep 17 00:00:00 2001 From: Daniel Bressan Date: Tue, 28 Feb 2017 20:07:14 +0000 Subject: [PATCH 03/11] fix jitsi video chat on mobile --- .../client/views/videoFlexTab.js | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/packages/rocketchat-videobridge/client/views/videoFlexTab.js b/packages/rocketchat-videobridge/client/views/videoFlexTab.js index af5294e8a5a..e0ffbe4ee9a 100644 --- a/packages/rocketchat-videobridge/client/views/videoFlexTab.js +++ b/packages/rocketchat-videobridge/client/views/videoFlexTab.js @@ -3,7 +3,11 @@ Template.videoFlexTab.helpers({ openInNewWindow() { - return RocketChat.settings.get('Jitsi_Open_New_Window'); + if (Meteor.isCordova) { + return true; + } else { + return RocketChat.settings.get('Jitsi_Open_New_Window'); + } } }); @@ -16,13 +20,13 @@ Template.videoFlexTab.onRendered(function() { let timeOut = null; - const width = 'auto'; - const height = 500; + let width = 'auto'; + let height = 500; - const configOverwrite = { + let configOverwrite = { desktopSharingChromeExtId: RocketChat.settings.get('Jitsi_Chrome_Extension') }; - const interfaceConfigOverwrite = {}; + let interfaceConfigOverwrite = {}; let jitsiRoomActive = null; @@ -40,11 +44,11 @@ Template.videoFlexTab.onRendered(function() { this.autorun(() => { if (RocketChat.settings.get('Jitsi_Enabled')) { if (this.tabBar.getState() === 'opened') { - const roomId = Session.get('openedRoom'); + let roomId = Session.get('openedRoom'); - const domain = RocketChat.settings.get('Jitsi_Domain'); - const jitsiRoom = RocketChat.settings.get('Jitsi_URL_Room_Prefix') + CryptoJS.MD5(RocketChat.settings.get('uniqueID') + roomId).toString(); - const noSsl = RocketChat.settings.get('Jitsi_SSL') ? false : true; + let domain = RocketChat.settings.get('Jitsi_Domain'); + let jitsiRoom = RocketChat.settings.get('Jitsi_URL_Room_Prefix') + CryptoJS.MD5(RocketChat.settings.get('uniqueID') + roomId).toString(); + let noSsl = RocketChat.settings.get('Jitsi_SSL') ? false : true; if (jitsiRoomActive !== null && jitsiRoomActive !== jitsiRoom) { jitsiRoomActive = null; @@ -61,21 +65,28 @@ Template.videoFlexTab.onRendered(function() { RocketChat.TabBar.updateButton('video', { class: 'red' }); - if (RocketChat.settings.get('Jitsi_Open_New_Window')) { + if (RocketChat.settings.get('Jitsi_Open_New_Window') || Meteor.isCordova) { Meteor.call('jitsi:updateTimeout', roomId); timeOut = Meteor.setInterval(() => Meteor.call('jitsi:updateTimeout', roomId), 10*1000); - const newWindow = window.open((noSsl ? 'http://' : 'https://') + domain + '/' + jitsiRoom, jitsiRoom); + if (Meteor.isCordova) { + const newWindow = window.open((noSsl ? 'http://' : 'https://') + domain + '/' + jitsiRoom, '_system'); + closePanel(); + clearInterval(timeOut); + } else { + const newWindow = window.open((noSsl ? 'http://' : 'https://') + domain + '/' + jitsiRoom, jitsiRoom); + let closeInterval = setInterval(() => { + if (newWindow.closed !== false) { + closePanel(); + clearInterval(closeInterval); + clearInterval(timeOut); + } + }, 300); + } newWindow.focus(); - const closeInterval = setInterval(() => { - if (newWindow.closed !== false) { - closePanel(); - clearInterval(closeInterval); - clearInterval(timeOut); - } - }, 300); + // Lets make sure its loaded before we try to show it. } else if (typeof JitsiMeetExternalAPI !== 'undefined') { @@ -114,3 +125,4 @@ Template.videoFlexTab.onRendered(function() { } }); }); + From d87d7d25e5aa2f758101d0864e9ae90cbc9e15dc Mon Sep 17 00:00:00 2001 From: Daniel Bressan Date: Tue, 28 Feb 2017 20:30:01 +0000 Subject: [PATCH 04/11] change const back --- .../client/views/videoFlexTab.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/rocketchat-videobridge/client/views/videoFlexTab.js b/packages/rocketchat-videobridge/client/views/videoFlexTab.js index e0ffbe4ee9a..f3b198eb70d 100644 --- a/packages/rocketchat-videobridge/client/views/videoFlexTab.js +++ b/packages/rocketchat-videobridge/client/views/videoFlexTab.js @@ -20,13 +20,13 @@ Template.videoFlexTab.onRendered(function() { let timeOut = null; - let width = 'auto'; - let height = 500; + const width = 'auto'; + const height = 500; - let configOverwrite = { + const configOverwrite = { desktopSharingChromeExtId: RocketChat.settings.get('Jitsi_Chrome_Extension') }; - let interfaceConfigOverwrite = {}; + const interfaceConfigOverwrite = {}; let jitsiRoomActive = null; @@ -44,11 +44,11 @@ Template.videoFlexTab.onRendered(function() { this.autorun(() => { if (RocketChat.settings.get('Jitsi_Enabled')) { if (this.tabBar.getState() === 'opened') { - let roomId = Session.get('openedRoom'); + const roomId = Session.get('openedRoom'); - let domain = RocketChat.settings.get('Jitsi_Domain'); - let jitsiRoom = RocketChat.settings.get('Jitsi_URL_Room_Prefix') + CryptoJS.MD5(RocketChat.settings.get('uniqueID') + roomId).toString(); - let noSsl = RocketChat.settings.get('Jitsi_SSL') ? false : true; + const domain = RocketChat.settings.get('Jitsi_Domain'); + const jitsiRoom = RocketChat.settings.get('Jitsi_URL_Room_Prefix') + CryptoJS.MD5(RocketChat.settings.get('uniqueID') + roomId).toString(); + const noSsl = RocketChat.settings.get('Jitsi_SSL') ? false : true; if (jitsiRoomActive !== null && jitsiRoomActive !== jitsiRoom) { jitsiRoomActive = null; From 466d9b0c040bb6456b3eddf16706bbbee6fe2184 Mon Sep 17 00:00:00 2001 From: Daniel Bressan Date: Thu, 2 Mar 2017 21:06:46 +0000 Subject: [PATCH 05/11] fix lint errors --- .../rocketchat-videobridge/client/views/videoFlexTab.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/rocketchat-videobridge/client/views/videoFlexTab.js b/packages/rocketchat-videobridge/client/views/videoFlexTab.js index f3b198eb70d..b5f2764c6a8 100644 --- a/packages/rocketchat-videobridge/client/views/videoFlexTab.js +++ b/packages/rocketchat-videobridge/client/views/videoFlexTab.js @@ -69,14 +69,14 @@ Template.videoFlexTab.onRendered(function() { Meteor.call('jitsi:updateTimeout', roomId); timeOut = Meteor.setInterval(() => Meteor.call('jitsi:updateTimeout', roomId), 10*1000); - + const newWindow; if (Meteor.isCordova) { - const newWindow = window.open((noSsl ? 'http://' : 'https://') + domain + '/' + jitsiRoom, '_system'); + newWindow = window.open((noSsl ? 'http://' : 'https://') + domain + '/' + jitsiRoom, '_system'); closePanel(); clearInterval(timeOut); } else { - const newWindow = window.open((noSsl ? 'http://' : 'https://') + domain + '/' + jitsiRoom, jitsiRoom); - let closeInterval = setInterval(() => { + newWindow = window.open((noSsl ? 'http://' : 'https://') + domain + '/' + jitsiRoom, jitsiRoom); + const closeInterval = setInterval(() => { if (newWindow.closed !== false) { closePanel(); clearInterval(closeInterval); From 6fdc70aac842ec7406aec774bb6e3d1ecd7a4fee Mon Sep 17 00:00:00 2001 From: Daniel Bressan Date: Thu, 2 Mar 2017 21:16:51 +0000 Subject: [PATCH 06/11] fix lint errors again --- .../rocketchat-videobridge/client/views/videoFlexTab.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/rocketchat-videobridge/client/views/videoFlexTab.js b/packages/rocketchat-videobridge/client/views/videoFlexTab.js index b5f2764c6a8..770c36f06cd 100644 --- a/packages/rocketchat-videobridge/client/views/videoFlexTab.js +++ b/packages/rocketchat-videobridge/client/views/videoFlexTab.js @@ -69,7 +69,7 @@ Template.videoFlexTab.onRendered(function() { Meteor.call('jitsi:updateTimeout', roomId); timeOut = Meteor.setInterval(() => Meteor.call('jitsi:updateTimeout', roomId), 10*1000); - const newWindow; + const newWindow = null; if (Meteor.isCordova) { newWindow = window.open((noSsl ? 'http://' : 'https://') + domain + '/' + jitsiRoom, '_system'); closePanel(); @@ -84,7 +84,9 @@ Template.videoFlexTab.onRendered(function() { } }, 300); } - newWindow.focus(); + if (newWindow) { + newWindow.focus(); + } From 9316e09740864f646a1ec16d4aaee2364de8fd04 Mon Sep 17 00:00:00 2001 From: Bradley Hilton Date: Thu, 2 Mar 2017 18:33:04 -0300 Subject: [PATCH 07/11] Fix incorrect isObject check on the ldap server sync, closes #5973 --- packages/rocketchat-ldap/server/sync.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rocketchat-ldap/server/sync.js b/packages/rocketchat-ldap/server/sync.js index 24456f7ac9a..8e75229e584 100644 --- a/packages/rocketchat-ldap/server/sync.js +++ b/packages/rocketchat-ldap/server/sync.js @@ -74,7 +74,7 @@ getDataToSyncUserData = function getDataToSyncUserData(ldapUser, user) { switch (userField) { case 'email': - if (_.isObject(ldapUser.object[ldapField] === 'object')) { + if (_.isObject(ldapUser.object[ldapField])) { _.map(ldapUser.object[ldapField], function(item) { emailList.push({ address: item, verified: true }); }); From 9e54f99f761d07556930f8294016b98c53f47af7 Mon Sep 17 00:00:00 2001 From: Daniel Bressan Date: Thu, 2 Mar 2017 21:38:00 +0000 Subject: [PATCH 08/11] change const to var for lint --- packages/rocketchat-videobridge/client/views/videoFlexTab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rocketchat-videobridge/client/views/videoFlexTab.js b/packages/rocketchat-videobridge/client/views/videoFlexTab.js index 770c36f06cd..fd5642cc686 100644 --- a/packages/rocketchat-videobridge/client/views/videoFlexTab.js +++ b/packages/rocketchat-videobridge/client/views/videoFlexTab.js @@ -69,7 +69,7 @@ Template.videoFlexTab.onRendered(function() { Meteor.call('jitsi:updateTimeout', roomId); timeOut = Meteor.setInterval(() => Meteor.call('jitsi:updateTimeout', roomId), 10*1000); - const newWindow = null; + var newWindow = null; if (Meteor.isCordova) { newWindow = window.open((noSsl ? 'http://' : 'https://') + domain + '/' + jitsiRoom, '_system'); closePanel(); From d60dca1220a4a27fb60960f06bfb69fd4c0d3774 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 3 Mar 2017 16:43:29 -0300 Subject: [PATCH 09/11] Allow login via OAuth access token. Only for Google only now --- packages/rocketchat-lib/package.js | 3 ++ .../rocketchat-lib/server/oauth/google.js | 53 +++++++++++++++++++ packages/rocketchat-lib/server/oauth/oauth.js | 52 ++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 packages/rocketchat-lib/server/oauth/google.js create mode 100644 packages/rocketchat-lib/server/oauth/oauth.js diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index 652f2de2b5c..c59e21755f7 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -110,6 +110,9 @@ Package.onUse(function(api) { api.addFiles('server/models/Uploads.coffee', 'server'); api.addFiles('server/models/Users.coffee', 'server'); + api.addFiles('server/oauth/oauth.js', 'server'); + api.addFiles('server/oauth/google.js', 'server'); + api.addFiles('server/startup/statsTracker.js', 'server'); // CACHE diff --git a/packages/rocketchat-lib/server/oauth/google.js b/packages/rocketchat-lib/server/oauth/google.js new file mode 100644 index 00000000000..41036da1d08 --- /dev/null +++ b/packages/rocketchat-lib/server/oauth/google.js @@ -0,0 +1,53 @@ +/* globals Google */ + +function getIdentity(accessToken) { + console.log('accessToken', accessToken); + try { + return HTTP.get( + 'https://www.googleapis.com/oauth2/v1/userinfo', + {params: {access_token: accessToken}}).data; + } catch (err) { + throw _.extend(new Error('Failed to fetch identity from Google. ' + err.message), {response: err.response}); + } +} + +function getScopes(accessToken) { + try { + return HTTP.get( + 'https://www.googleapis.com/oauth2/v1/tokeninfo', + {params: {access_token: accessToken}}).data.scope.split(' '); + } catch (err) { + throw _.extend(new Error('Failed to fetch tokeninfo from Google. ' + err.message), {response: err.response}); + } +} + + +RocketChat.registerAccessTokenService('google', function(options) { + const identity = options.identity || getIdentity(options.accessToken); + + const serviceData = { + accessToken: options.accessToken, + idToken: options.idToken, + expiresAt: (+new Date) + (1000 * parseInt(options.expiresIn, 10)), + scope: options.scopes || getScopes(options.accessToken) + }; + + const fields = _.pick(identity, Google.whitelistedFields); + _.extend(serviceData, fields); + + // only set the token in serviceData if it's there. this ensures + // that we don't lose old ones (since we only get this on the first + // log in attempt) + if (options.refreshToken) { + serviceData.refreshToken = options.refreshToken; + } + + return { + serviceData: serviceData, + options: { + profile: { + name: identity.name + } + } + }; +}); diff --git a/packages/rocketchat-lib/server/oauth/oauth.js b/packages/rocketchat-lib/server/oauth/oauth.js new file mode 100644 index 00000000000..3a72ad03377 --- /dev/null +++ b/packages/rocketchat-lib/server/oauth/oauth.js @@ -0,0 +1,52 @@ +const AccessTokenServices = {}; + +RocketChat.registerAccessTokenService = function(serviceName, handleAccessTokenRequest) { + AccessTokenServices[serviceName] = { + serviceName: serviceName, + handleAccessTokenRequest: handleAccessTokenRequest + }; +}; + +// Listen to calls to `login` with an oauth option set. This is where +// users actually get logged in to meteor via oauth. +Accounts.registerLoginHandler(function(options) { + if (!options.accessToken) { + return undefined; // don't handle + } + + check(options.oauth, { + serviceName: String + }); + + const service = AccessTokenServices[options.serviceName]; + + // Skip everything if there's no service set by the oauth middleware + if (!service) { + throw new Error(`Unexpected AccessToken service ${options.serviceName}`); + } + + // Make sure we're configured + if (!ServiceConfiguration.configurations.findOne({service: service.serviceName})) { + throw new ServiceConfiguration.ConfigError(); + } + + if (!_.contains(Accounts.oauth.serviceNames(), service.serviceName)) { + // serviceName was not found in the registered services list. + // This could happen because the service never registered itself or + // unregisterService was called on it. + return { + type: 'oauth', + error: new Meteor.Error( + Accounts.LoginCancelledError.numericError, + `No registered oauth service found for: ${service.serviceName}` + ) + }; + } + + const oauthResult = service.handleAccessTokenRequest(options); + + return Accounts.updateOrCreateUserFromExternalService(service.serviceName, oauthResult.serviceData, oauthResult.options); +}); + + + From ea14a8def7ece49f25c1c6cf326bfe2d56e51017 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 3 Mar 2017 18:30:07 -0300 Subject: [PATCH 10/11] Remove log and check params --- packages/rocketchat-lib/server/oauth/google.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/rocketchat-lib/server/oauth/google.js b/packages/rocketchat-lib/server/oauth/google.js index 41036da1d08..a2771fce8d1 100644 --- a/packages/rocketchat-lib/server/oauth/google.js +++ b/packages/rocketchat-lib/server/oauth/google.js @@ -1,7 +1,6 @@ /* globals Google */ function getIdentity(accessToken) { - console.log('accessToken', accessToken); try { return HTTP.get( 'https://www.googleapis.com/oauth2/v1/userinfo', @@ -23,6 +22,14 @@ function getScopes(accessToken) { RocketChat.registerAccessTokenService('google', function(options) { + check(options, { + accessToken: String, + idToken: String, + expiresAt: Match.Integer, + scope: Match.Maybe(String), + identity: Match.Maybe(Object) + }); + const identity = options.identity || getIdentity(options.accessToken); const serviceData = { From 16ccbd4ff6c62e6376ec9206aed1b99596739aa5 Mon Sep 17 00:00:00 2001 From: Gabriel Engel Date: Fri, 3 Mar 2017 18:40:36 -0300 Subject: [PATCH 11/11] Code formatting fix --- packages/rocketchat-lib/server/methods/restartServer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rocketchat-lib/server/methods/restartServer.js b/packages/rocketchat-lib/server/methods/restartServer.js index 63da91d7776..100cf528155 100644 --- a/packages/rocketchat-lib/server/methods/restartServer.js +++ b/packages/rocketchat-lib/server/methods/restartServer.js @@ -10,7 +10,7 @@ Meteor.methods({ Meteor.setTimeout(() => { Meteor.setTimeout(() => { - console.warn("process.exit() timed out, aborting..."); + console.warn('Call to process.exit() timed out, aborting.'); process.abort(); } , 1000);