mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-13 20:57:24 +01:00
Compare commits
21 Commits
qa/turtle
...
phillip/se
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40d5172972 | ||
|
|
ebb58e4470 | ||
|
|
c3ef26b2f3 | ||
|
|
108bd59296 | ||
|
|
5228ed40d1 | ||
|
|
becb6e49f0 | ||
|
|
aa8f0f0c4e | ||
|
|
d1891f4c43 | ||
|
|
2381be8c46 | ||
|
|
5c7545f32a | ||
|
|
ffed5a9a97 | ||
|
|
cd58ce2233 | ||
|
|
a2b5e3621e | ||
|
|
a06dfc9ed8 | ||
|
|
58b0e323a3 | ||
|
|
9ca60d7551 | ||
|
|
6f63583a12 | ||
|
|
d952239d35 | ||
|
|
2c7f6fd9e3 | ||
|
|
ddba450630 | ||
|
|
187238d39a |
13394
package-lock.json
generated
13394
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,6 @@
|
||||
"@babel/core": "^7.22.10",
|
||||
"@babel/preset-env": "^7.22.10",
|
||||
"@babel/register": "^7.22.15",
|
||||
"@google-analytics/data": "^4.12.1",
|
||||
"@google-cloud/trace-agent": "^7.1.2",
|
||||
"@parse/node-apn": "^5.2.3",
|
||||
"@slack/webhook": "^6.1.0",
|
||||
@@ -43,6 +42,7 @@
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"helmet": "^4.6.0",
|
||||
"in-app-purchase": "^1.11.3",
|
||||
"ip-location-api": "^4.0.0",
|
||||
"js2xmlparser": "^5.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jwks-rsa": "^2.1.5",
|
||||
|
||||
112
website/client/package-lock.json
generated
112
website/client/package-lock.json
generated
@@ -12,7 +12,6 @@
|
||||
"@froxz/vite-plugin-s3": "^1.6.0",
|
||||
"@vitejs/plugin-vue2": "^2.3.3",
|
||||
"@vue/test-utils": "1.0.0-beta.29",
|
||||
"amplitude-js": "^8.21.3",
|
||||
"assert": "^2.1.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"axios": "^0.28.0",
|
||||
@@ -68,49 +67,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@amplitude/analytics-connector": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@amplitude/analytics-connector/-/analytics-connector-1.5.0.tgz",
|
||||
"integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g=="
|
||||
},
|
||||
"node_modules/@amplitude/types": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@amplitude/types/-/types-1.10.2.tgz",
|
||||
"integrity": "sha512-I8qenRI7uU6wKNb9LiZrAosSHVoNHziXouKY81CrqxH9xhVTEIJFXeuCV0hbtBr0Al/8ejnGjQRx+S2SvU/pPg==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@amplitude/ua-parser-js": {
|
||||
"version": "0.7.33",
|
||||
"resolved": "https://registry.npmjs.org/@amplitude/ua-parser-js/-/ua-parser-js-0.7.33.tgz",
|
||||
"integrity": "sha512-wKEtVR4vXuPT9cVEIJkYWnlF++Gx3BdLatPBM+SZ1ztVIvnhdGBZR/mn9x/PzyrMcRlZmyi6L56I2J3doVBnjA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ua-parser-js"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/faisalman"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@amplitude/utils": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@amplitude/utils/-/utils-1.10.2.tgz",
|
||||
"integrity": "sha512-tVsHXu61jITEtRjB7NugQ5cVDd4QDzne8T3ifmZye7TiJeUfVRvqe44gDtf55A+7VqhDhyEIIXTA1iVcDGqlEw==",
|
||||
"dependencies": {
|
||||
"@amplitude/types": "^1.10.2",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
|
||||
@@ -1231,6 +1187,7 @@
|
||||
"version": "7.23.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz",
|
||||
"integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
@@ -3050,19 +3007,6 @@
|
||||
"ajv": "^6.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/amplitude-js": {
|
||||
"version": "8.21.9",
|
||||
"resolved": "https://registry.npmjs.org/amplitude-js/-/amplitude-js-8.21.9.tgz",
|
||||
"integrity": "sha512-d0jJH00wbXu7sxKtVwkdSXtVffjqdUrxuACKlnzP7jU5qt9wriXXMgHifdH5Oq+buKmyF8wKL9S02gAykysURA==",
|
||||
"dependencies": {
|
||||
"@amplitude/analytics-connector": "^1.4.6",
|
||||
"@amplitude/ua-parser-js": "0.7.33",
|
||||
"@amplitude/utils": "^1.10.2",
|
||||
"@babel/runtime": "^7.21.0",
|
||||
"blueimp-md5": "^2.19.0",
|
||||
"query-string": "8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-colors": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
|
||||
@@ -3351,11 +3295,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/blueimp-md5": {
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz",
|
||||
"integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w=="
|
||||
},
|
||||
"node_modules/bootstrap": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz",
|
||||
@@ -3768,14 +3707,6 @@
|
||||
"integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/decode-uri-component": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.4.1.tgz",
|
||||
"integrity": "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==",
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-eql": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.1.tgz",
|
||||
@@ -4987,17 +4918,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/filter-obj": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-5.1.0.tgz",
|
||||
"integrity": "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==",
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
||||
@@ -7283,22 +7203,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/query-string": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-8.1.0.tgz",
|
||||
"integrity": "sha512-BFQeWxJOZxZGix7y+SByG3F36dA0AbTy9o6pSmKFcFz7DAj0re9Frkty3saBn3nHo3D0oZJ/+rx3r8H8r8Jbpw==",
|
||||
"dependencies": {
|
||||
"decode-uri-component": "^0.4.1",
|
||||
"filter-obj": "^5.1.0",
|
||||
"split-on-first": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/ramda": {
|
||||
"version": "0.26.1",
|
||||
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz",
|
||||
@@ -7344,7 +7248,8 @@
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/regexp.prototype.flags": {
|
||||
"version": "1.5.1",
|
||||
@@ -7858,17 +7763,6 @@
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/split-on-first": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-3.0.0.tgz",
|
||||
"integrity": "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
"@froxz/vite-plugin-s3": "^1.6.0",
|
||||
"@vitejs/plugin-vue2": "^2.3.3",
|
||||
"@vue/test-utils": "1.0.0-beta.29",
|
||||
"amplitude-js": "^8.21.3",
|
||||
"assert": "^2.1.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"axios": "^0.28.0",
|
||||
|
||||
@@ -445,7 +445,7 @@ export default {
|
||||
hitType: 'event',
|
||||
mirror: newVal,
|
||||
group: this.group._id,
|
||||
}, { trackOnClient: true });
|
||||
});
|
||||
const groupsToMirror = this.user.preferences.tasks.mirrorGroupTasks || [];
|
||||
if (newVal) { // we're turning copy ON for this group
|
||||
groupsToMirror.push(this.group._id);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div
|
||||
class="banner d-flex align-items-center justify-content-between py-3 px-4"
|
||||
id="privacy-banner"
|
||||
v-if="!hidden"
|
||||
id="privacy-banner"
|
||||
class="banner d-flex align-items-center justify-content-between py-3 px-4"
|
||||
>
|
||||
<p
|
||||
class="mr-3 mb-0"
|
||||
|
||||
@@ -546,7 +546,7 @@ export default {
|
||||
eventCategory: 'behavior',
|
||||
demographics: this.upgradedGroup.demographics,
|
||||
type: this.paymentData.group.type,
|
||||
}, { trackOnClient: true });
|
||||
});
|
||||
}
|
||||
this.paymentData = {};
|
||||
this.$root.$emit('bv::hide::modal', 'payments-success-modal');
|
||||
|
||||
@@ -64,9 +64,11 @@
|
||||
<li>sexual orientation; and</li>
|
||||
<li>information collected from a known child.</li>
|
||||
</ul>
|
||||
<p><strong>
|
||||
NOTE: Please do not provide us “sensitive personal information” or “sensitive personal data”, as those terms are defined under applicable privacy laws, unless we directly request that you do so. If you feel, after careful consideration, that it is necessary to provide us certain sensitive personal information or data, please provide us the minimum amount of such information or data that is necessary.
|
||||
</strong></p>
|
||||
<p>
|
||||
<strong>
|
||||
NOTE: Please do not provide us “sensitive personal information” or “sensitive personal data”, as those terms are defined under applicable privacy laws, unless we directly request that you do so. If you feel, after careful consideration, that it is necessary to provide us certain sensitive personal information or data, please provide us the minimum amount of such information or data that is necessary.
|
||||
</strong>
|
||||
</p>
|
||||
<h3 id="section_1_1">
|
||||
1.1 Information You Provide Directly
|
||||
</h3>
|
||||
@@ -617,7 +619,7 @@
|
||||
7. General Audience Services
|
||||
</h2>
|
||||
<p>
|
||||
The Service is intended for users 18 years or older; you are not permitted to access or use the Service if you are younger than 18. We do not knowingly collect personal information from children under the age of 18 through the Service. We encourage parents and legal guardians to monitor their children’s Internet usage and to help enforce our Privacy Policy by instructing their children to never provide personal information without their permission. If you have reason to believe that a child under the age of 18 has provided personal information to us, please contact us at <a href='mailto:privacy@habitica.com'>privacy@habitica.com</a>, and we will delete that information from our databases.
|
||||
The Service is intended for users 18 years or older; you are not permitted to access or use the Service if you are younger than 18. We do not knowingly collect personal information from children under the age of 18 through the Service. We encourage parents and legal guardians to monitor their children’s Internet usage and to help enforce our Privacy Policy by instructing their children to never provide personal information without their permission. If you have reason to believe that a child under the age of 18 has provided personal information to us, please contact us at <a href="mailto:privacy@habitica.com">privacy@habitica.com</a>, and we will delete that information from our databases.
|
||||
</p>
|
||||
|
||||
<h2 id="section_8">
|
||||
@@ -708,7 +710,7 @@
|
||||
|
||||
<p><strong><u>Nevada Residents</u></strong></p>
|
||||
<p>
|
||||
Nevada residents may opt out of the sale of certain “covered information” collected by operators of websites or online services. We currently do not sell covered information, as “sale” is defined by such law, and do not have plans to do so. In accordance with Nevada law, you may submit to us a verified request instructing us not to sell your covered information by sending an email to <a href='mailto:privacy@habitica.com'>privacy@habitica.com</a>.
|
||||
Nevada residents may opt out of the sale of certain “covered information” collected by operators of websites or online services. We currently do not sell covered information, as “sale” is defined by such law, and do not have plans to do so. In accordance with Nevada law, you may submit to us a verified request instructing us not to sell your covered information by sending an email to <a href="mailto:privacy@habitica.com">privacy@habitica.com</a>.
|
||||
</p>
|
||||
<p><strong><u>Notice to United Kingdom/European/Switzerland Residents.</u></strong></p>
|
||||
<p>
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
<router-view />
|
||||
</div>
|
||||
<div
|
||||
id="bottom-background"
|
||||
v-if="loginFlow"
|
||||
id="bottom-background"
|
||||
class="bg-purple-300"
|
||||
>
|
||||
<div class="seamless_mountains_demo_repeat"></div>
|
||||
@@ -31,7 +31,10 @@
|
||||
id="bottom-wrap"
|
||||
class="purple-4"
|
||||
>
|
||||
<div id="bottom-background" v-if="!loginFlow">
|
||||
<div
|
||||
v-if="!loginFlow"
|
||||
id="bottom-background"
|
||||
>
|
||||
<div class="seamless_mountains_demo_repeat"></div>
|
||||
<div class="midground_foreground_extended2"></div>
|
||||
</div>
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
BY PURCHASING PREMIUM YOU EXPRESSLY UNDERSTAND AND AGREE TO OUR REFUND POLICY:
|
||||
</p>
|
||||
<p>
|
||||
YOU CAN REQUEST A REFUND OF YOUR MOST RECENT PAYMENT TO US BY CONTACTING US AT <a href='mailto:admin@habitica.com'>ADMIN@HABITICA.COM</a>. THE AMOUNT OF YOUR REFUND, IF ANY, WILL BE BASED ON (1) THE AMOUNT OF YOUR PURCHASED BUT UNUSED SUBSCRIPTION BENEFITS AND (2) THE TERMS IMPOSED ON US BY OUR PAYMENT PROCESSING VENDORS (E.G., WITH RESPECT TO THE DURATION OF THE REFUND PERIOD).
|
||||
YOU CAN REQUEST A REFUND OF YOUR MOST RECENT PAYMENT TO US BY CONTACTING US AT <a href="mailto:admin@habitica.com">ADMIN@HABITICA.COM</a>. THE AMOUNT OF YOUR REFUND, IF ANY, WILL BE BASED ON (1) THE AMOUNT OF YOUR PURCHASED BUT UNUSED SUBSCRIPTION BENEFITS AND (2) THE TERMS IMPOSED ON US BY OUR PAYMENT PROCESSING VENDORS (E.G., WITH RESPECT TO THE DURATION OF THE REFUND PERIOD).
|
||||
</p>
|
||||
<p>
|
||||
FOR ANY CUSTOMER WHO PURCHASED PREMIUM IN APPLE INC.'s APP STORE ("APP STORE"), PLEASE CONTACT APPLE INC.'s SUPPORT TEAM: <a
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
type="checkbox"
|
||||
:checked="isChecked"
|
||||
:value="value"
|
||||
@change="handleChange"
|
||||
:disabled="disabled"
|
||||
@change="handleChange"
|
||||
>
|
||||
<label
|
||||
class="toggle-switch-label"
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
import forEach from 'lodash/forEach';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import keys from 'lodash/keys';
|
||||
import pick from 'lodash/pick';
|
||||
import amplitude from 'amplitude-js';
|
||||
import { gtag, install } from 'ga-gtag';
|
||||
import Vue from 'vue';
|
||||
import getStore from '@/store';
|
||||
|
||||
const AMPLITUDE_KEY = import.meta.env.AMPLITUDE_KEY;
|
||||
const DEBUG_ENABLED = import.meta.env.DEBUG_ENABLED === 'true';
|
||||
const GA_ID = import.meta.env.GA_ID;
|
||||
const IS_PRODUCTION = import.meta.env.NODE_ENV === 'production';
|
||||
const REQUIRED_FIELDS = ['eventCategory', 'eventAction'];
|
||||
|
||||
let analyticsLoading = false;
|
||||
let analyticsReady = false;
|
||||
|
||||
function _getConsentedUser () {
|
||||
const store = getStore();
|
||||
const user = store.state.user.data;
|
||||
@@ -66,49 +56,24 @@ function _gatherUserStats (properties) {
|
||||
if (user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId;
|
||||
}
|
||||
|
||||
export function safeSetup (userId) {
|
||||
if (analyticsLoading || analyticsReady) return;
|
||||
analyticsLoading = true;
|
||||
install(GA_ID, {
|
||||
debug_mode: DEBUG_ENABLED || !IS_PRODUCTION,
|
||||
user_id: userId,
|
||||
});
|
||||
amplitude.getInstance().init(AMPLITUDE_KEY, userId);
|
||||
analyticsReady = true;
|
||||
analyticsLoading = false;
|
||||
}
|
||||
|
||||
export function track (properties, options = {}) {
|
||||
export function track (properties) {
|
||||
const user = _getConsentedUser();
|
||||
if (!user) return;
|
||||
safeSetup(user._id);
|
||||
// Use nextTick to avoid blocking the UI
|
||||
Vue.nextTick(() => {
|
||||
if (_doesNotHaveRequiredFields(properties)) return;
|
||||
|
||||
const trackOnClient = options && options.trackOnClient === true;
|
||||
// Track events on the server by default
|
||||
if (trackOnClient === true) {
|
||||
amplitude.getInstance().logEvent(properties.eventAction, properties);
|
||||
gtag('event', properties.eventAction, properties);
|
||||
} else {
|
||||
const store = getStore();
|
||||
store.dispatch('analytics:trackEvent', properties);
|
||||
}
|
||||
const store = getStore();
|
||||
store.dispatch('analytics:trackEvent', properties);
|
||||
});
|
||||
}
|
||||
|
||||
export function updateUser (properties = {}) {
|
||||
const user = _getConsentedUser();
|
||||
if (!user) return;
|
||||
safeSetup(user._id);
|
||||
// Use nextTick to avoid blocking the UI
|
||||
Vue.nextTick(() => {
|
||||
_gatherUserStats(properties);
|
||||
gtag('set', 'user_properties', properties);
|
||||
forEach(properties, (value, key) => {
|
||||
const identify = new amplitude.Identify().set(key, value);
|
||||
amplitude.getInstance().identify(identify);
|
||||
});
|
||||
const store = getStore();
|
||||
store.dispatch('analytics:updateUserProperties', properties);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ export default {
|
||||
eventCategory: 'behavior',
|
||||
demographics: appState.newGroup.demographics,
|
||||
type: appState.newGroup.type,
|
||||
}, { trackOnClient: true });
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error while redirecting to Stripe', err); // eslint-disable-line
|
||||
|
||||
@@ -66,7 +66,7 @@ export default {
|
||||
uuid: user._id,
|
||||
taskType: task.type,
|
||||
direction,
|
||||
}, { trackOnClient: true });
|
||||
});
|
||||
if (!tasksScoredCount) {
|
||||
setLocalSetting(CONSTANTS.keyConstants.TASKS_SCORED_COUNT, 1);
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<tr>
|
||||
<td colspan="3"
|
||||
<td
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
colspan="3"
|
||||
>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h3
|
||||
@@ -18,8 +19,9 @@
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="3"
|
||||
<td
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
colspan="3"
|
||||
>
|
||||
<h3
|
||||
v-once
|
||||
@@ -59,8 +61,8 @@
|
||||
{{ $t('performanceAnalytics') }}
|
||||
</label>
|
||||
<toggle-switch
|
||||
class="mb-auto"
|
||||
v-model="user.preferences.analyticsConsent"
|
||||
class="mb-auto"
|
||||
@change="prefToggled()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -128,7 +128,6 @@ import PrivacyBanner from '@/components/header/banners/privacy';
|
||||
import AppFooter from '@/components/appFooter';
|
||||
import notificationsDisplay from '@/components/notifications';
|
||||
import { mapState } from '@/libs/store';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
import BuyModal from '@/components/shops/buyModal.vue';
|
||||
import SelectMembersModal from '@/components/selectMembersModal.vue';
|
||||
import notifications from '@/mixins/notifications';
|
||||
@@ -280,7 +279,6 @@ export default {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Analytics.updateUser();
|
||||
return axios.get(
|
||||
'/api/v4/i18n/browser-script',
|
||||
{
|
||||
|
||||
@@ -320,7 +320,7 @@ router.beforeEach(async (to, from, next) => {
|
||||
eventName: 'View Find Members',
|
||||
eventAction: 'View Find Members',
|
||||
eventCategory: 'behavior',
|
||||
}, { trackOnClient: true });
|
||||
});
|
||||
}
|
||||
|
||||
// Redirect old guild urls
|
||||
|
||||
@@ -2,6 +2,10 @@ import axios from 'axios';
|
||||
|
||||
export async function trackEvent (store, params) {
|
||||
const url = `/analytics/track/${params.eventAction}`;
|
||||
|
||||
await axios.post(url, params);
|
||||
}
|
||||
|
||||
export async function updateUserProperties (store, params) {
|
||||
const url = '/analytics/update';
|
||||
await axios.post(url, params);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import { authAsCredentialsState, LOCALSTORAGE_AUTH_KEY } from '@/libs/auth';
|
||||
|
||||
const GA_ID = import.meta.env.GA_ID;
|
||||
|
||||
function saveLocalDataAuth (store, apiId, apiToken) {
|
||||
const credentialsObj = {
|
||||
auth: {
|
||||
@@ -123,9 +121,6 @@ export async function appleAuth (store, params) {
|
||||
export function logout (store, options = {}) {
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
if (window.gtag) {
|
||||
window.gtag('config', GA_ID, { user_id: null });
|
||||
}
|
||||
const query = options.redirectToLogin === true ? '?redirectToLogin=true' : '';
|
||||
window.location.href = `/logout-server${query}`;
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ export async function create (store, createdTask) {
|
||||
hitType: 'event',
|
||||
uuid,
|
||||
taskType: taskRes.type,
|
||||
}, { trackOnClient: true });
|
||||
});
|
||||
if (!tasksCreatedCount) {
|
||||
setLocalSetting(CONSTANTS.keyConstants.TASKS_CREATED_COUNT, 1);
|
||||
} else {
|
||||
|
||||
@@ -26,11 +26,9 @@ const envVars = [
|
||||
'EMAILS_COMMUNITY_MANAGER_EMAIL',
|
||||
'EMAILS_TECH_ASSISTANCE_EMAIL',
|
||||
'EMAILS_PRESS_ENQUIRY_EMAIL',
|
||||
'GA_ID',
|
||||
'STRIPE_PUB_KEY',
|
||||
'GOOGLE_CLIENT_ID',
|
||||
'APPLE_AUTH_CLIENT_ID',
|
||||
'AMPLITUDE_KEY',
|
||||
'LOGGLY_CLIENT_TOKEN',
|
||||
'TRUSTED_DOMAINS',
|
||||
'TIME_TRAVEL_ENABLED',
|
||||
|
||||
@@ -3,7 +3,6 @@ import isFunction from 'lodash/isFunction';
|
||||
import min from 'lodash/min';
|
||||
import reduce from 'lodash/reduce';
|
||||
import filter from 'lodash/filter';
|
||||
import pick from 'lodash/pick';
|
||||
import pickBy from 'lodash/pickBy';
|
||||
import size from 'lodash/size';
|
||||
import moment from 'moment';
|
||||
@@ -160,7 +159,7 @@ export default function randomDrop (user, options, req = {}, analytics) {
|
||||
|
||||
if (analytics && moment().diff(user.auth.timestamps.created, 'days') < 7) {
|
||||
analytics.track('dropped item', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
itemKey: drop.key,
|
||||
category: 'behavior',
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import pick from 'lodash/pick';
|
||||
|
||||
export function hasCompletedOnboarding (user) {
|
||||
return (
|
||||
user.achievements.createdTask === true
|
||||
@@ -21,7 +19,7 @@ export function checkOnboardingStatus (user, req, analytics) {
|
||||
user.addNotification('ONBOARDING_COMPLETE');
|
||||
if (analytics) {
|
||||
analytics.track('onboarding complete', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable max-classes-per-file */
|
||||
import get from 'lodash/get';
|
||||
import merge from 'lodash/merge';
|
||||
import pick from 'lodash/pick';
|
||||
import i18n from '../../i18n';
|
||||
import {
|
||||
NotAuthorized,
|
||||
@@ -114,7 +113,7 @@ export class AbstractBuyOperation {
|
||||
sendToAnalytics (additionalData = {}) {
|
||||
// spread-operator produces an "unexpected token" error
|
||||
const analyticsData = merge(additionalData, {
|
||||
user: pick(this.user, ['preferences', 'registeredThrough']),
|
||||
user: this.user,
|
||||
uuid: this.user._id,
|
||||
category: 'behavior',
|
||||
headers: this.req.headers,
|
||||
|
||||
@@ -73,7 +73,7 @@ export class BuyArmoireOperation extends AbstractGoldItemOperation { // eslint-d
|
||||
this.analytics.track(
|
||||
'Enchanted Armoire',
|
||||
{
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
itemKey: key,
|
||||
category: 'behavior',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import get from 'lodash/get';
|
||||
import each from 'lodash/each';
|
||||
import pick from 'lodash/pick';
|
||||
import i18n from '../../i18n';
|
||||
import content from '../../content/index';
|
||||
import {
|
||||
@@ -37,7 +36,7 @@ export default async function buyMysterySet (user, req = {}, analytics) {
|
||||
|
||||
if (analytics) {
|
||||
analytics.track('buy', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
itemKey: mysterySet.key,
|
||||
itemType: 'Subscriber Gear',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import get from 'lodash/get';
|
||||
import includes from 'lodash/includes';
|
||||
import keys from 'lodash/keys';
|
||||
import pick from 'lodash/pick';
|
||||
import i18n from '../../i18n';
|
||||
import content from '../../content/index';
|
||||
import {
|
||||
@@ -96,7 +95,7 @@ export default async function purchaseHourglass (user, req = {}, analytics, quan
|
||||
|
||||
if (analytics) {
|
||||
analytics.track('buy', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
itemKey: key,
|
||||
itemType: type,
|
||||
|
||||
@@ -132,7 +132,7 @@ export default async function purchase (user, req = {}, analytics) {
|
||||
/* eslint-enable no-await-in-loop */
|
||||
if (analytics) {
|
||||
analytics.track('buy', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
itemKey: key,
|
||||
itemType: type,
|
||||
|
||||
@@ -70,7 +70,7 @@ export default async function changeClass (user, req = {}, analytics) {
|
||||
|
||||
if (analytics) {
|
||||
analytics.track('change class', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
class: klass,
|
||||
currency: balanceRemoved === 0 ? 'Free' : 'Gems',
|
||||
|
||||
@@ -2,7 +2,6 @@ import forEach from 'lodash/forEach';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
import get from 'lodash/get';
|
||||
import keys from 'lodash/keys';
|
||||
import pick from 'lodash/pick';
|
||||
import upperFirst from 'lodash/upperFirst';
|
||||
import moment from 'moment';
|
||||
import i18n from '../i18n';
|
||||
@@ -143,7 +142,7 @@ export default function feed (user, req = {}, analytics) {
|
||||
|
||||
if (analytics && moment().diff(user.auth.timestamps.created, 'days') < 7) {
|
||||
analytics.track('pet feed', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
foodKey: food.key,
|
||||
petKey: pet.key,
|
||||
|
||||
@@ -2,7 +2,6 @@ import findIndex from 'lodash/findIndex';
|
||||
import forEach from 'lodash/forEach';
|
||||
import get from 'lodash/get';
|
||||
import keys from 'lodash/keys';
|
||||
import pick from 'lodash/pick';
|
||||
import upperFirst from 'lodash/upperFirst';
|
||||
import moment from 'moment';
|
||||
import i18n from '../i18n';
|
||||
@@ -154,7 +153,7 @@ export default function hatch (user, req = {}, analytics) {
|
||||
|
||||
if (analytics && moment().diff(user.auth.timestamps.created, 'days') < 7) {
|
||||
analytics.track('pet hatch', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
petKey: pet,
|
||||
category: 'behavior',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import each from 'lodash/each';
|
||||
import pick from 'lodash/pick';
|
||||
import i18n from '../i18n';
|
||||
import { capByLevel } from '../statHelpers';
|
||||
import { MAX_LEVEL } from '../constants';
|
||||
@@ -23,7 +22,7 @@ export default async function rebirth (user, tasks = [], req = {}, analytics) {
|
||||
|
||||
const analyticsData = {
|
||||
uuid: user._id,
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
category: 'behavior',
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import pick from 'lodash/pick';
|
||||
import content from '../content/index';
|
||||
import { mountMasterProgress } from '../count';
|
||||
import i18n from '../i18n';
|
||||
@@ -44,7 +43,7 @@ export default async function releaseMounts (user, req = {}, analytics) {
|
||||
|
||||
if (analytics) {
|
||||
analytics.track('release mounts', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
currency: 'Gems',
|
||||
gemCost: 4,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import pick from 'lodash/pick';
|
||||
import content from '../content/index';
|
||||
import { beastMasterProgress } from '../count';
|
||||
import i18n from '../i18n';
|
||||
@@ -44,7 +43,7 @@ export default function releasePets (user, req = {}, analytics) {
|
||||
|
||||
if (analytics) {
|
||||
analytics.track('release pets', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
currency: 'Gems',
|
||||
gemCost: 4,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import each from 'lodash/each';
|
||||
import pick from 'lodash/pick';
|
||||
import i18n from '../i18n';
|
||||
import {
|
||||
NotAuthorized,
|
||||
@@ -24,7 +23,7 @@ export default async function reroll (user, tasks = [], req = {}, analytics) {
|
||||
|
||||
if (analytics) {
|
||||
analytics.track('Fortify Potion', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
currency: 'Gems',
|
||||
gemCost: 4,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import merge from 'lodash/merge';
|
||||
import pick from 'lodash/pick';
|
||||
import reduce from 'lodash/reduce';
|
||||
import each from 'lodash/each';
|
||||
import i18n from '../i18n';
|
||||
@@ -112,7 +111,7 @@ export default function revive (user, req = {}, analytics) {
|
||||
|
||||
if (analytics) {
|
||||
analytics.track('Death', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
lostItem,
|
||||
category: 'behavior',
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import pick from 'lodash/pick';
|
||||
|
||||
export function sleep (user, req = {}, analytics) {
|
||||
user.preferences.sleep = !user.preferences.sleep;
|
||||
|
||||
if (analytics) {
|
||||
analytics.track('sleep', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
status: user.preferences.sleep,
|
||||
category: 'behavior',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import get from 'lodash/get';
|
||||
import pick from 'lodash/pick';
|
||||
import setWith from 'lodash/setWith';
|
||||
import i18n from '../i18n';
|
||||
import { NotAuthorized, BadRequest } from '../libs/errors';
|
||||
@@ -318,7 +317,7 @@ export default async function unlock (user, req = {}, analytics) {
|
||||
|
||||
if (analytics) {
|
||||
analytics.track('buy', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
itemKey: path,
|
||||
itemType: 'customization',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import validator from 'validator';
|
||||
import moment from 'moment';
|
||||
import pick from 'lodash/pick';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import nconf from 'nconf';
|
||||
import {
|
||||
@@ -128,7 +127,7 @@ api.loginLocal = {
|
||||
await user.save();
|
||||
|
||||
res.analytics.track('login', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
category: 'behavior',
|
||||
type: 'local',
|
||||
uuid: user._id,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import escapeRegExp from 'lodash/escapeRegExp';
|
||||
import merge from 'lodash/merge';
|
||||
import pick from 'lodash/pick';
|
||||
import reduce from 'lodash/reduce';
|
||||
import times from 'lodash/times';
|
||||
import { authWithHeaders, authWithSession } from '../../middlewares/auth';
|
||||
@@ -291,7 +290,7 @@ api.createChallenge = {
|
||||
response.group = getChallengeGroupResponse(group);
|
||||
|
||||
res.analytics.track('challenge create', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
@@ -360,7 +359,7 @@ api.joinChallenge = {
|
||||
response.leader = chalLeader ? chalLeader.toJSON({ minimize: true }) : null;
|
||||
|
||||
res.analytics.track('challenge join', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
@@ -411,7 +410,7 @@ api.leaveChallenge = {
|
||||
await challenge.unlinkTasks(user, keep);
|
||||
|
||||
res.analytics.track('challenge leave', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
@@ -896,7 +895,7 @@ api.deleteChallenge = {
|
||||
await challenge.closeChal({ broken: 'CHALLENGE_DELETED' });
|
||||
|
||||
res.analytics.track('challenge delete', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
@@ -957,7 +956,7 @@ api.selectChallengeWinner = {
|
||||
await challenge.closeChal({ broken: 'CHALLENGE_CLOSED', winner });
|
||||
|
||||
res.analytics.track('challenge close', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import pick from 'lodash/pick';
|
||||
import moment from 'moment';
|
||||
import nconf from 'nconf';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
@@ -187,7 +186,7 @@ api.postChat = {
|
||||
// Check if account is newer than the minimum age for chat participation
|
||||
if (moment().diff(user.auth.timestamps.created, 'minutes') < ACCOUNT_MIN_CHAT_AGE) {
|
||||
analytics.track('chat age error', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
@@ -239,7 +238,7 @@ api.postChat = {
|
||||
await Promise.all(toSave);
|
||||
|
||||
const analyticsObject = {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
|
||||
@@ -5,7 +5,6 @@ import findIndex from 'lodash/findIndex';
|
||||
import includes from 'lodash/includes';
|
||||
import isArray from 'lodash/isArray';
|
||||
import mergeWith from 'lodash/mergeWith';
|
||||
import pick from 'lodash/pick';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import nconf from 'nconf';
|
||||
import moment from 'moment';
|
||||
@@ -169,7 +168,7 @@ api.createGroup = {
|
||||
};
|
||||
|
||||
const analyticsObject = {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
@@ -220,7 +219,7 @@ api.createGroupPlan = {
|
||||
const savedGroup = results[1];
|
||||
|
||||
res.analytics.track('join group', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
@@ -705,7 +704,7 @@ api.joinGroup = {
|
||||
promises.push(group.save());
|
||||
|
||||
const analyticsObject = {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import escapeRegExp from 'lodash/escapeRegExp';
|
||||
import pick from 'lodash/pick';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
import {
|
||||
model as User,
|
||||
@@ -737,7 +736,7 @@ api.transferGems = {
|
||||
|
||||
if (res.analytics) {
|
||||
res.analytics.track('transfer gems', {
|
||||
user: pick(sender, ['preferences', 'registeredThrough']),
|
||||
user: sender,
|
||||
uuid: sender._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import each from 'lodash/each';
|
||||
import every from 'lodash/every';
|
||||
import isBoolean from 'lodash/isBoolean';
|
||||
import pick from 'lodash/pick';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
import { getAnalyticsServiceByEnvironment } from '../../libs/analyticsService';
|
||||
import {
|
||||
@@ -168,7 +167,7 @@ api.inviteToQuest = {
|
||||
|
||||
// track that the inviting user has accepted the quest
|
||||
analytics.track('quest', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
category: 'behavior',
|
||||
headers: req.headers,
|
||||
@@ -233,7 +232,7 @@ api.acceptQuest = {
|
||||
|
||||
// track that a user has accepted the quest
|
||||
analytics.track('quest', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
category: 'behavior',
|
||||
owner: false,
|
||||
response: 'accept',
|
||||
@@ -298,7 +297,7 @@ api.rejectQuest = {
|
||||
res.respond(200, savedGroup.quest);
|
||||
|
||||
analytics.track('quest', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
category: 'behavior',
|
||||
owner: false,
|
||||
response: 'reject',
|
||||
@@ -362,7 +361,7 @@ api.forceStart = {
|
||||
res.respond(200, savedGroup.quest);
|
||||
|
||||
analytics.track('quest', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
category: 'behavior',
|
||||
owner: user._id === group.quest.leader,
|
||||
response: 'force-start',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import assign from 'lodash/assign';
|
||||
import find from 'lodash/find';
|
||||
import merge from 'lodash/merge';
|
||||
import pick from 'lodash/pick';
|
||||
import moment from 'moment';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
import {
|
||||
@@ -333,7 +332,7 @@ api.createChallengeTasks = {
|
||||
|
||||
tasks.forEach(task => {
|
||||
res.analytics.track('challenge task created', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
@@ -703,7 +702,7 @@ api.updateTask = {
|
||||
|
||||
if (group) {
|
||||
res.analytics.track('task edit', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import pick from 'lodash/pick';
|
||||
import isUUID from 'validator/lib/isUUID';
|
||||
import { authWithHeaders } from '../../../middlewares/auth';
|
||||
import * as Tasks from '../../../models/task';
|
||||
@@ -64,7 +63,7 @@ api.createGroupTasks = {
|
||||
|
||||
tasks.forEach(task => {
|
||||
res.analytics.track('team task created', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
@@ -253,7 +252,7 @@ api.assignTask = {
|
||||
res.respond(200, task);
|
||||
|
||||
res.analytics.track('task assign', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import forEach from 'lodash/forEach';
|
||||
import isFunction from 'lodash/isFunction';
|
||||
import pick from 'lodash/pick';
|
||||
import nconf from 'nconf';
|
||||
import get from 'lodash/get';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
@@ -326,7 +325,7 @@ api.deleteUser = {
|
||||
}
|
||||
|
||||
res.analytics.track('account delete', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import pick from 'lodash/pick';
|
||||
import {
|
||||
NotAuthorized,
|
||||
} from '../../libs/errors';
|
||||
@@ -22,16 +21,17 @@ api.trackEvent = {
|
||||
// we authenticate these requests to make sure they actually came from a real user
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
// As of now only web can track events using this route
|
||||
if (req.headers['x-client'] !== 'habitica-web') {
|
||||
throw new NotAuthorized('Only habitica.com is allowed to track analytics events.');
|
||||
if (req.headers['x-client'] !== 'habitica-web'
|
||||
&& req.headers['x-client'] !== 'habitica-ios'
|
||||
&& req.headers['x-client'] !== 'habitica-android') {
|
||||
throw new NotAuthorized('Only official clients are allowed to track analytics events.');
|
||||
}
|
||||
|
||||
const { user } = res.locals;
|
||||
const eventProperties = req.body;
|
||||
|
||||
res.analytics.track(req.params.eventName, {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
headers: req.headers,
|
||||
category: 'behavior',
|
||||
@@ -44,4 +44,31 @@ api.trackEvent = {
|
||||
},
|
||||
};
|
||||
|
||||
api.updateUserProperties = {
|
||||
method: 'POST',
|
||||
url: '/analytics/update',
|
||||
// we authenticate these requests to make sure they actually came from a real user
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
if (req.headers['x-client'] !== 'habitica-web'
|
||||
&& req.headers['x-client'] !== 'habitica-ios'
|
||||
&& req.headers['x-client'] !== 'habitica-android') {
|
||||
throw new NotAuthorized('Only official clients are allowed to track analytics events.');
|
||||
}
|
||||
|
||||
const { user } = res.locals;
|
||||
const properties = req.body;
|
||||
|
||||
res.analytics.updateUserData({
|
||||
user,
|
||||
uuid: user._id,
|
||||
properties,
|
||||
});
|
||||
|
||||
// not using res.respond
|
||||
// because we don't want to send back notifications and other user-related data
|
||||
res.status(200).send({});
|
||||
},
|
||||
};
|
||||
|
||||
export default api;
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
import nconf from 'nconf';
|
||||
import Amplitude from 'amplitude';
|
||||
import useragent from 'useragent';
|
||||
import validator from 'validator';
|
||||
import { lookup } from 'ip-location-api';
|
||||
import { createHash } from 'crypto';
|
||||
import {
|
||||
omit,
|
||||
toArray,
|
||||
@@ -27,6 +30,36 @@ if (AMPLITUDE_TOKEN) amplitude = new Amplitude(AMPLITUDE_TOKEN);
|
||||
|
||||
const Content = common.content;
|
||||
|
||||
function _hashUUID (uuid) {
|
||||
return createHash('sha256').update(uuid).digest('hex');
|
||||
}
|
||||
|
||||
function _anonymizeProperties (properties) {
|
||||
if (Array.isArray(properties)) {
|
||||
return properties.map(userProp => {
|
||||
if (typeof userProp === 'string' && validator.isEmail(userProp)) {
|
||||
return _hashUUID(userProp);
|
||||
}
|
||||
return userProp;
|
||||
});
|
||||
}
|
||||
if (typeof properties === 'object' && properties !== null) {
|
||||
const anonymizedProps = {};
|
||||
Object.keys(properties).forEach(key => {
|
||||
const value = properties[key];
|
||||
if (typeof value === 'string' && validator.isEmail(value)) {
|
||||
anonymizedProps[key] = _hashUUID(value);
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
anonymizedProps[key] = _anonymizeProperties(value);
|
||||
} else {
|
||||
anonymizedProps[key] = value;
|
||||
}
|
||||
});
|
||||
return anonymizedProps;
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
function _lookUpItemName (itemKey) {
|
||||
if (!itemKey) return null;
|
||||
|
||||
@@ -56,7 +89,7 @@ function _lookUpItemName (itemKey) {
|
||||
return itemName;
|
||||
}
|
||||
|
||||
function _formatUserData (user) {
|
||||
function _formatUserData (user, ipaddress, anonymize = false) {
|
||||
const properties = {};
|
||||
|
||||
if (user.stats) {
|
||||
@@ -72,7 +105,7 @@ function _formatUserData (user) {
|
||||
properties.balanceGemAmount = properties.balance * 4;
|
||||
properties.tutorialComplete = user.flags && user.flags.tour && user.flags.tour.intro === -2;
|
||||
properties.verifiedUsername = user.flags && user.flags.verifiedUsername;
|
||||
if (properties.verifiedUsername && user.auth && user.auth.local) {
|
||||
if (properties.verifiedUsername && user.auth && user.auth.local && !anonymize) {
|
||||
properties.username = user.auth.local.lowerCaseUsername;
|
||||
}
|
||||
|
||||
@@ -89,10 +122,12 @@ function _formatUserData (user) {
|
||||
properties.contributorLevel = user.contributor.level;
|
||||
}
|
||||
|
||||
if (user.purchased && user.purchased.plan.planId) {
|
||||
properties.subscription = user.purchased.plan.planId;
|
||||
} else {
|
||||
properties.subscription = null;
|
||||
if (!anonymize) {
|
||||
if (user.purchased && user.purchased.plan.planId) {
|
||||
properties.subscription = user.purchased.plan.planId;
|
||||
} else {
|
||||
properties.subscription = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (user._ABtests) {
|
||||
@@ -103,6 +138,16 @@ function _formatUserData (user) {
|
||||
properties.loginIncentives = user.loginIncentives;
|
||||
}
|
||||
|
||||
if (ipaddress) {
|
||||
const location = lookup(ipaddress);
|
||||
properties.country = location.country;
|
||||
properties.region = location.region1;
|
||||
}
|
||||
|
||||
if (anonymize) {
|
||||
return _anonymizeProperties(properties);
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
@@ -139,16 +184,20 @@ function _formatUserAgentForAmplitude (platform, agentString) {
|
||||
return formattedAgent;
|
||||
}
|
||||
|
||||
function _formatUUIDForAmplitude (uuid) {
|
||||
function _formatUUIDForAmplitude (uuid, anonymize = false) {
|
||||
if (anonymize) {
|
||||
return _hashUUID(uuid);
|
||||
}
|
||||
return uuid || 'no-user-id-was-provided';
|
||||
}
|
||||
|
||||
function _formatDataForAmplitude (data) {
|
||||
const consented = data.user && data.user.preferences && data.user.preferences.analyticsConsent;
|
||||
const event_properties = omit(data, AMPLITUDE_PROPERTIES_TO_SCRUB);
|
||||
const platform = _formatPlatformForAmplitude(data.headers && data.headers['x-client']);
|
||||
const agent = _formatUserAgentForAmplitude(platform, data.headers && data.headers['user-agent']);
|
||||
const ampData = {
|
||||
user_id: _formatUUIDForAmplitude(data.uuid),
|
||||
user_id: _formatUUIDForAmplitude(data.uuid, !consented),
|
||||
platform,
|
||||
os_name: agent.name,
|
||||
os_version: agent.version,
|
||||
@@ -156,7 +205,12 @@ function _formatDataForAmplitude (data) {
|
||||
};
|
||||
|
||||
if (data.user) {
|
||||
ampData.user_properties = _formatUserData(data.user);
|
||||
const ipaddress = data.ipaddress || (data.headers && data.headers['x-forwarded-for']);
|
||||
ampData.user_properties = _formatUserData(data.user, ipaddress, !consented);
|
||||
}
|
||||
|
||||
if (!consented) {
|
||||
ampData.event_properties = _anonymizeProperties(ampData.event_properties);
|
||||
}
|
||||
|
||||
const itemName = _lookUpItemName(data.itemKey);
|
||||
@@ -215,12 +269,18 @@ function _setOnce (dataToSetOnce, uuid) {
|
||||
.catch(err => logger.error(err, 'Error while sending data to Amplitude.'));
|
||||
}
|
||||
|
||||
// There's no error handling directly here because it's handled inside _sendDataTo{Amplitude|Google}
|
||||
function _updateProperties (properties, uuid) {
|
||||
return amplitude
|
||||
.identify({
|
||||
user_id: _formatUUIDForAmplitude(uuid),
|
||||
user_properties: properties,
|
||||
})
|
||||
.catch(err => logger.error(err, 'Error while sending data to Amplitude.'));
|
||||
}
|
||||
|
||||
// There's no error handling directly here because it's handled inside _sendDataToAmplitude
|
||||
async function track (eventType, data, loggerOnly = false) {
|
||||
const { user } = data;
|
||||
if (!user || !user.preferences || !user.preferences.analyticsConsent) {
|
||||
return null;
|
||||
}
|
||||
const promises = [
|
||||
_sendDataToAmplitude(eventType, data, loggerOnly),
|
||||
];
|
||||
@@ -234,29 +294,37 @@ async function track (eventType, data, loggerOnly = false) {
|
||||
}
|
||||
|
||||
// There's no error handling directly here because
|
||||
// it's handled inside _sendPurchaseDataTo{Amplitude|Google}
|
||||
// it's handled inside _sendPurchaseDataToAmplitude
|
||||
async function trackPurchase (data) {
|
||||
const { user } = data;
|
||||
if (!user || !user.preferences || !user.preferences.analyticsConsent) {
|
||||
return null;
|
||||
}
|
||||
return Promise.all([
|
||||
_sendPurchaseDataToAmplitude(data),
|
||||
]);
|
||||
}
|
||||
|
||||
async function updateUserData (data) {
|
||||
const { user, properties } = data;
|
||||
const toUpdate = {
|
||||
..._formatUserData(user, data.ipaddress),
|
||||
...properties,
|
||||
};
|
||||
|
||||
return _updateProperties(toUpdate, user._id);
|
||||
}
|
||||
|
||||
// Stub for non-prod environments
|
||||
const mockAnalyticsService = {
|
||||
track: () => { },
|
||||
trackPurchase: () => { },
|
||||
updateUserData: () => { },
|
||||
};
|
||||
|
||||
// Return the production or mock service based on the current environment
|
||||
function getServiceByEnvironment () {
|
||||
if (nconf.get('IS_PROD') || (nconf.get('DEBUG_ENABLED') && !nconf.get('BASE_URL').includes('localhost'))) {
|
||||
if (nconf.get('IS_PROD') || nconf.get('USE_PROD_ANALYTICS') || (nconf.get('DEBUG_ENABLED') && !nconf.get('BASE_URL').includes('localhost'))) {
|
||||
return {
|
||||
track,
|
||||
trackPurchase,
|
||||
updateUserData,
|
||||
};
|
||||
}
|
||||
return mockAnalyticsService;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import moment from 'moment';
|
||||
import pick from 'lodash/pick';
|
||||
import {
|
||||
BadRequest,
|
||||
NotAuthorized,
|
||||
@@ -219,7 +218,7 @@ async function registerLocal (req, res, { isV3 = false }) {
|
||||
|
||||
if (!existingUser) {
|
||||
res.analytics.track('register', {
|
||||
user: pick(savedUser, ['preferences', 'registeredThrough']),
|
||||
user: savedUser,
|
||||
category: 'acquisition',
|
||||
type: 'local',
|
||||
uuid: savedUser._id,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import pick from 'lodash/pick';
|
||||
import passport from 'passport';
|
||||
import common from '../../../common';
|
||||
import { BadRequest, NotAuthorized, NotFound } from '../errors';
|
||||
@@ -158,7 +157,7 @@ export async function loginSocial (req, res) { // eslint-disable-line import/pre
|
||||
|
||||
if (!existingUser) {
|
||||
res.analytics.track('register', {
|
||||
user: pick(savedUser, ['preferences', 'registeredThrough']),
|
||||
user: savedUser,
|
||||
uuid: savedUser._id,
|
||||
category: 'acquisition',
|
||||
type: network,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import moment from 'moment';
|
||||
import mongoose from 'mongoose';
|
||||
import pick from 'lodash/pick';
|
||||
import nconf from 'nconf';
|
||||
import { model as User } from '../models/user';
|
||||
import * as Tasks from '../models/task';
|
||||
@@ -104,7 +103,7 @@ function trackCronAnalytics (analytics, user, _progress, options) {
|
||||
analytics.track('Cron', {
|
||||
category: 'behavior',
|
||||
uuid: user._id,
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
resting: user.preferences.sleep,
|
||||
cronCount: user.flags.cronCount,
|
||||
progressUp: Math.min(_progress.up, 900),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import find from 'lodash/find';
|
||||
import includes from 'lodash/includes';
|
||||
import pick from 'lodash/pick';
|
||||
|
||||
import { encrypt } from '../encryption';
|
||||
import { sendNotification as sendPushNotification } from '../pushNotifications';
|
||||
@@ -144,11 +143,10 @@ async function inviteByUUID (uuid, group, inviter, req, res) {
|
||||
}
|
||||
|
||||
const analyticsObject = {
|
||||
user: pick(inviter, ['preferences', 'registeredThrough']),
|
||||
user: inviter,
|
||||
uuid: inviter._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
invitee: uuid,
|
||||
groupId: group._id,
|
||||
groupType: group.type,
|
||||
headers: req.headers,
|
||||
@@ -209,11 +207,10 @@ async function inviteByEmail (invite, group, inviter, req, res) {
|
||||
if (!userIsUnsubscribed) sendTxnEmail(invite, `invite-friend${groupLabel}`, variables);
|
||||
|
||||
const analyticsObject = {
|
||||
user: pick(inviter, ['preferences', 'registeredThrough']),
|
||||
user: inviter,
|
||||
uuid: inviter._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
invitee: 'email',
|
||||
groupId: group._id,
|
||||
groupType: group.type,
|
||||
headers: req.headers,
|
||||
@@ -247,11 +244,10 @@ async function inviteByUserName (username, group, inviter, req, res) {
|
||||
}
|
||||
|
||||
const analyticsObject = {
|
||||
user: pick(inviter, ['preferences', 'registeredThrough']),
|
||||
user: inviter,
|
||||
uuid: inviter._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
invitee: userToInvite._id,
|
||||
groupId: group._id,
|
||||
groupType: group.type,
|
||||
headers: req.headers,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import find from 'lodash/find';
|
||||
import pick from 'lodash/pick';
|
||||
import { getAnalyticsServiceByEnvironment } from '../analyticsService';
|
||||
import { getCurrentEventList } from '../worldState'; // eslint-disable-line import/no-cycle
|
||||
import { // eslint-disable-line import/no-cycle
|
||||
@@ -115,7 +114,7 @@ export async function buyGems (data) {
|
||||
if (!data.gift) txnEmail(data.user, 'donation');
|
||||
|
||||
analytics.trackPurchase({
|
||||
user: pick(data.user, ['preferences', 'registeredThrough']),
|
||||
user: data.user,
|
||||
uuid: data.user._id,
|
||||
itemPurchased: 'Gems',
|
||||
sku: `${data.paymentMethod.toLowerCase()}-checkout`,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import pick from 'lodash/pick';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
BadRequest,
|
||||
@@ -32,7 +31,7 @@ async function buyGryphatrice (data) {
|
||||
data.user.purchased.txnCount += 1;
|
||||
|
||||
analytics.trackPurchase({
|
||||
user: pick(data.user, ['preferences', 'registeredThrough']),
|
||||
user: data.user,
|
||||
uuid: data.user._id,
|
||||
itemPurchased: 'Gryphatrice',
|
||||
sku: `${data.paymentMethod.toLowerCase()}-checkout`,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import defaults from 'lodash/defaults';
|
||||
import each from 'lodash/each';
|
||||
import find from 'lodash/find';
|
||||
import pick from 'lodash/pick';
|
||||
import moment from 'moment';
|
||||
|
||||
import { getAnalyticsServiceByEnvironment } from '../analyticsService';
|
||||
@@ -465,7 +464,7 @@ async function cancelSubscription (data) {
|
||||
|
||||
analytics.track(cancelType, {
|
||||
uuid: data.user._id,
|
||||
user: pick(data.user, ['preferences', 'registeredThrough']),
|
||||
user: data.user,
|
||||
groupId,
|
||||
paymentMethod: data.paymentMethod,
|
||||
headers: data.headers,
|
||||
|
||||
@@ -3,7 +3,6 @@ import cloneDeep from 'lodash/cloneDeep';
|
||||
import compact from 'lodash/compact';
|
||||
import forEach from 'lodash/forEach';
|
||||
import keys from 'lodash/keys';
|
||||
import pick from 'lodash/pick';
|
||||
import remove from 'lodash/remove';
|
||||
import validator from 'validator';
|
||||
import {
|
||||
@@ -516,7 +515,7 @@ async function scoreTask (user, task, direction, req, res) {
|
||||
role = 'member';
|
||||
}
|
||||
res.analytics.track('team task scored', {
|
||||
user: pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
|
||||
@@ -117,7 +117,7 @@ export async function update (req, res, { isV3 = false }) {
|
||||
user.invitations.party = {};
|
||||
user.invitations.parties = [];
|
||||
res.analytics.track('Starts Looking for Party', {
|
||||
user: _.pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
@@ -201,7 +201,7 @@ export async function update (req, res, { isV3 = false }) {
|
||||
if (key === 'party.seeking' && val === null) {
|
||||
user.party.seeking = undefined;
|
||||
res.analytics.track('Leaves Looking for Party', {
|
||||
user: _.pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
@@ -292,7 +292,7 @@ export async function reset (req, res, { isV3 = false }) {
|
||||
]);
|
||||
|
||||
res.analytics.track('account reset', {
|
||||
user: _.pick(user, ['preferences', 'registeredThrough']),
|
||||
user,
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
|
||||
Reference in New Issue
Block a user