Merge branch 'remove-metrics' into 'master'
Remove metrics Closes #4 See merge request timvisee/send!11
This commit is contained in:
commit
d3f9b82672
@ -53,7 +53,7 @@ Thanks [Mozilla][mozilla] for building this amazing tool!
|
||||
|
||||
---
|
||||
|
||||
**Docs:** [FAQ](docs/faq.md), [Encryption](docs/encryption.md), [Build](docs/build.md), [Docker](docs/docker.md), [Metrics](docs/metrics.md), [More](docs/)
|
||||
**Docs:** [FAQ](docs/faq.md), [Encryption](docs/encryption.md), [Build](docs/build.md), [Docker](docs/docker.md), [More](docs/)
|
||||
|
||||
---
|
||||
|
||||
|
@ -4,7 +4,6 @@ import html from 'choo/html';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
|
||||
import { setApiUrlPrefix, getConstants } from '../app/api';
|
||||
import metrics from '../app/metrics';
|
||||
//import assets from '../common/assets';
|
||||
import Archive from '../app/archive';
|
||||
import Header from '../app/ui/header';
|
||||
@ -83,7 +82,6 @@ function body(main) {
|
||||
state.user = new User(storage, LIMITS);
|
||||
state.sentry = Sentry;
|
||||
});
|
||||
app.use(metrics);
|
||||
app.route('/', body(home));
|
||||
app.route('/upload', upload);
|
||||
app.route('/share/:id', share);
|
||||
|
11
app/api.js
11
app/api.js
@ -420,17 +420,6 @@ export async function setFileList(bearerToken, kid, data) {
|
||||
return response.ok;
|
||||
}
|
||||
|
||||
export function sendMetrics(blob) {
|
||||
if (!navigator.sendBeacon) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
navigator.sendBeacon(getApiUrl('/api/metrics'), blob);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getConstants() {
|
||||
const response = await fetch(getApiUrl('/config'));
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import * as metrics from './metrics';
|
||||
import FileReceiver from './fileReceiver';
|
||||
import FileSender from './fileSender';
|
||||
import copyDialog from './ui/copyDialog';
|
||||
@ -54,7 +53,6 @@ export default function(state, emitter) {
|
||||
|
||||
emitter.on('logout', async () => {
|
||||
await state.user.logout();
|
||||
metrics.loggedOut({ trigger: 'button' });
|
||||
emitter.emit('pushState', '/');
|
||||
});
|
||||
|
||||
@ -68,14 +66,6 @@ export default function(state, emitter) {
|
||||
|
||||
emitter.on('delete', async ownedFile => {
|
||||
try {
|
||||
metrics.deletedUpload({
|
||||
size: ownedFile.size,
|
||||
time: ownedFile.time,
|
||||
speed: ownedFile.speed,
|
||||
type: ownedFile.type,
|
||||
ttl: ownedFile.expiresAt - Date.now(),
|
||||
location
|
||||
});
|
||||
state.storage.remove(ownedFile.id);
|
||||
await ownedFile.del();
|
||||
} catch (e) {
|
||||
@ -123,7 +113,7 @@ export default function(state, emitter) {
|
||||
source: query.utm_source,
|
||||
term: query.utm_term
|
||||
});
|
||||
state.modal = signupDialog(source);
|
||||
state.modal = signupDialog();
|
||||
render();
|
||||
});
|
||||
|
||||
@ -159,12 +149,9 @@ export default function(state, emitter) {
|
||||
|
||||
const links = openLinksInNewTab();
|
||||
await delay(200);
|
||||
const start = Date.now();
|
||||
try {
|
||||
const ownedFile = await sender.upload(archive, state.user.bearerToken);
|
||||
state.storage.totalUploads += 1;
|
||||
const duration = Date.now() - start;
|
||||
metrics.completedUpload(archive, duration);
|
||||
faviconProgressbar.updateFavicon(0);
|
||||
|
||||
state.storage.addFile(ownedFile);
|
||||
@ -181,7 +168,6 @@ export default function(state, emitter) {
|
||||
} catch (err) {
|
||||
if (err.message === '0') {
|
||||
//cancelled. do nothing
|
||||
metrics.cancelledUpload(archive, err.duration);
|
||||
render();
|
||||
} else if (err.message === '401') {
|
||||
const refreshed = await state.user.refresh();
|
||||
@ -197,7 +183,6 @@ export default function(state, emitter) {
|
||||
scope.setExtra('size', err.size);
|
||||
state.sentry.captureException(err);
|
||||
});
|
||||
metrics.stoppedUpload(archive, err.duration);
|
||||
emitter.emit('pushState', '/error');
|
||||
}
|
||||
} finally {
|
||||
@ -249,13 +234,11 @@ export default function(state, emitter) {
|
||||
render();
|
||||
});
|
||||
|
||||
emitter.on('download', async file => {
|
||||
emitter.on('download', async () => {
|
||||
state.transfer.on('progress', updateProgress);
|
||||
state.transfer.on('decrypting', render);
|
||||
state.transfer.on('complete', render);
|
||||
const links = openLinksInNewTab();
|
||||
const size = file.size;
|
||||
const start = Date.now();
|
||||
try {
|
||||
const dl = state.transfer.download({
|
||||
stream: state.capabilities.streamDownload
|
||||
@ -263,12 +246,6 @@ export default function(state, emitter) {
|
||||
render();
|
||||
await dl;
|
||||
state.storage.totalDownloads += 1;
|
||||
const duration = Date.now() - start;
|
||||
metrics.completedDownload({
|
||||
size,
|
||||
duration,
|
||||
password_protected: file.requiresPassword
|
||||
});
|
||||
faviconProgressbar.updateFavicon(0);
|
||||
} catch (err) {
|
||||
if (err.message === '0') {
|
||||
@ -286,12 +263,6 @@ export default function(state, emitter) {
|
||||
scope.setExtra('progress', err.progress);
|
||||
state.sentry.captureException(err);
|
||||
});
|
||||
const duration = Date.now() - start;
|
||||
metrics.stoppedDownload({
|
||||
size,
|
||||
duration,
|
||||
password_protected: file.requiresPassword
|
||||
});
|
||||
}
|
||||
emitter.emit('pushState', location);
|
||||
}
|
||||
@ -302,7 +273,6 @@ export default function(state, emitter) {
|
||||
|
||||
emitter.on('copy', ({ url }) => {
|
||||
copyToClipboard(url);
|
||||
// metrics.copiedLink({ location });
|
||||
});
|
||||
|
||||
emitter.on('closeModal', () => {
|
||||
|
@ -10,7 +10,6 @@ import controller from './controller';
|
||||
import dragManager from './dragManager';
|
||||
import pasteManager from './pasteManager';
|
||||
import storage from './storage';
|
||||
import metrics from './metrics';
|
||||
import experiments from './experiments';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import './main.css';
|
||||
@ -68,7 +67,6 @@ if (process.env.NODE_ENV === 'production') {
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
window.app = app;
|
||||
app.use(experiments);
|
||||
app.use(metrics);
|
||||
app.use(controller);
|
||||
app.use(dragManager);
|
||||
app.use(pasteManager);
|
||||
|
186
app/metrics.js
186
app/metrics.js
@ -1,186 +0,0 @@
|
||||
import storage from './storage';
|
||||
import { platform, locale } from './utils';
|
||||
import { sendMetrics } from './api';
|
||||
|
||||
let appState = null;
|
||||
let experiment = null;
|
||||
const HOUR = 1000 * 60 * 60;
|
||||
const events = [];
|
||||
let session_id = Date.now();
|
||||
const lang = locale();
|
||||
|
||||
export default function initialize(state, emitter) {
|
||||
appState = state;
|
||||
|
||||
emitter.on('DOMContentLoaded', () => {
|
||||
experiment = storage.enrolled;
|
||||
if (!appState.user.firstAction) {
|
||||
appState.user.firstAction =
|
||||
appState.route === '/' ? 'upload' : 'download';
|
||||
}
|
||||
const query = appState.query;
|
||||
addEvent('client_visit', {
|
||||
entrypoint: appState.route === '/' ? 'upload' : 'download',
|
||||
referrer: document.referrer,
|
||||
utm_campaign: query.utm_campaign,
|
||||
utm_content: query.utm_content,
|
||||
utm_medium: query.utm_medium,
|
||||
utm_source: query.utm_source,
|
||||
utm_term: query.utm_term
|
||||
});
|
||||
});
|
||||
emitter.on('experiment', experimentEvent);
|
||||
window.addEventListener('unload', submitEvents);
|
||||
}
|
||||
|
||||
function sizeOrder(n) {
|
||||
return Math.floor(Math.log10(n));
|
||||
}
|
||||
|
||||
function submitEvents() {
|
||||
if (navigator.doNotTrack === '1') {
|
||||
return;
|
||||
}
|
||||
sendMetrics(
|
||||
new Blob(
|
||||
[
|
||||
JSON.stringify({
|
||||
now: Date.now(),
|
||||
session_id,
|
||||
lang,
|
||||
platform: platform(),
|
||||
events
|
||||
})
|
||||
],
|
||||
{ type: 'text/plain' } // see http://crbug.com/490015
|
||||
)
|
||||
);
|
||||
events.splice(0);
|
||||
}
|
||||
|
||||
async function addEvent(event_type, event_properties) {
|
||||
const user_id = await appState.user.metricId();
|
||||
const device_id = await appState.user.deviceId();
|
||||
const ab_id = Object.keys(experiment)[0];
|
||||
if (ab_id) {
|
||||
event_properties.experiment = ab_id;
|
||||
event_properties.variant = experiment[ab_id];
|
||||
}
|
||||
events.push({
|
||||
device_id,
|
||||
event_properties,
|
||||
event_type,
|
||||
time: Date.now(),
|
||||
user_id,
|
||||
user_properties: {
|
||||
anonymous: !appState.user.loggedIn,
|
||||
first_action: appState.user.firstAction,
|
||||
active_count: storage.files.length
|
||||
}
|
||||
});
|
||||
if (events.length === 25) {
|
||||
submitEvents();
|
||||
}
|
||||
}
|
||||
|
||||
function cancelledUpload(archive, duration) {
|
||||
return addEvent('client_upload', {
|
||||
download_limit: archive.dlimit,
|
||||
duration: sizeOrder(duration),
|
||||
file_count: archive.numFiles,
|
||||
password_protected: !!archive.password,
|
||||
size: sizeOrder(archive.size),
|
||||
status: 'cancel',
|
||||
time_limit: archive.timeLimit
|
||||
});
|
||||
}
|
||||
|
||||
function completedUpload(archive, duration) {
|
||||
return addEvent('client_upload', {
|
||||
download_limit: archive.dlimit,
|
||||
duration: sizeOrder(duration),
|
||||
file_count: archive.numFiles,
|
||||
password_protected: !!archive.password,
|
||||
size: sizeOrder(archive.size),
|
||||
status: 'ok',
|
||||
time_limit: archive.timeLimit
|
||||
});
|
||||
}
|
||||
|
||||
function stoppedUpload(archive, duration = 0) {
|
||||
return addEvent('client_upload', {
|
||||
download_limit: archive.dlimit,
|
||||
duration: sizeOrder(duration),
|
||||
file_count: archive.numFiles,
|
||||
password_protected: !!archive.password,
|
||||
size: sizeOrder(archive.size),
|
||||
status: 'error',
|
||||
time_limit: archive.timeLimit
|
||||
});
|
||||
}
|
||||
|
||||
function stoppedDownload(params) {
|
||||
return addEvent('client_download', {
|
||||
duration: sizeOrder(params.duration),
|
||||
password_protected: params.password_protected,
|
||||
size: sizeOrder(params.size),
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
|
||||
function completedDownload(params) {
|
||||
return addEvent('client_download', {
|
||||
duration: sizeOrder(params.duration),
|
||||
password_protected: params.password_protected,
|
||||
size: sizeOrder(params.size),
|
||||
status: 'ok'
|
||||
});
|
||||
}
|
||||
|
||||
function deletedUpload(ownedFile) {
|
||||
return addEvent('client_delete', {
|
||||
age: Math.floor((Date.now() - ownedFile.createdAt) / HOUR),
|
||||
downloaded: ownedFile.dtotal > 0,
|
||||
status: 'ok'
|
||||
});
|
||||
}
|
||||
|
||||
function experimentEvent(params) {
|
||||
return addEvent('client_experiment', params);
|
||||
}
|
||||
|
||||
function submittedSignup(params) {
|
||||
return addEvent('client_login', {
|
||||
status: 'ok',
|
||||
trigger: params.trigger
|
||||
});
|
||||
}
|
||||
|
||||
function canceledSignup(params) {
|
||||
return addEvent('client_login', {
|
||||
status: 'cancel',
|
||||
trigger: params.trigger
|
||||
});
|
||||
}
|
||||
|
||||
function loggedOut(params) {
|
||||
addEvent('client_logout', {
|
||||
status: 'ok',
|
||||
trigger: params.trigger
|
||||
});
|
||||
// flush events and start new anon session
|
||||
submitEvents();
|
||||
session_id = Date.now();
|
||||
}
|
||||
|
||||
export {
|
||||
cancelledUpload,
|
||||
stoppedUpload,
|
||||
completedUpload,
|
||||
deletedUpload,
|
||||
stoppedDownload,
|
||||
completedDownload,
|
||||
submittedSignup,
|
||||
canceledSignup,
|
||||
loggedOut
|
||||
};
|
@ -580,7 +580,7 @@ module.exports.preview = function(state, emit) {
|
||||
function download(event) {
|
||||
event.preventDefault();
|
||||
event.target.disabled = true;
|
||||
emit('download', archive);
|
||||
emit('download');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -100,7 +100,7 @@ module.exports = function(state, emit) {
|
||||
);
|
||||
break;
|
||||
case 'download':
|
||||
emit('download', archive);
|
||||
emit('download');
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
|
@ -1,9 +1,8 @@
|
||||
const html = require('choo/html');
|
||||
const assets = require('../../common/assets');
|
||||
const { bytes } = require('../utils');
|
||||
const { canceledSignup, submittedSignup } = require('../metrics');
|
||||
|
||||
module.exports = function(trigger) {
|
||||
module.exports = function() {
|
||||
return function(state, emit, close) {
|
||||
const DAYS = Math.floor(state.LIMITS.MAX_EXPIRE_SECONDS / 86400);
|
||||
let submitting = false;
|
||||
@ -72,7 +71,6 @@ module.exports = function(trigger) {
|
||||
}
|
||||
|
||||
function cancel(event) {
|
||||
canceledSignup({ trigger });
|
||||
close(event);
|
||||
}
|
||||
|
||||
@ -85,7 +83,6 @@ module.exports = function(trigger) {
|
||||
|
||||
const el = document.getElementById('email-input');
|
||||
const email = el.value;
|
||||
submittedSignup({ trigger });
|
||||
emit('login', emailish(email) ? email : null);
|
||||
}
|
||||
};
|
||||
|
19
app/user.js
19
app/user.js
@ -109,28 +109,9 @@ export default class User {
|
||||
async startAuthFlow(trigger, utms = {}) {
|
||||
this.utms = utms;
|
||||
this.trigger = trigger;
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
entrypoint: `send-${trigger}`,
|
||||
form_type: 'email',
|
||||
utm_source: utms.source || 'send',
|
||||
utm_campaign: utms.campaign || 'none'
|
||||
});
|
||||
const res = await fetch(
|
||||
`${this.authConfig.issuer}/metrics-flow?${params.toString()}`,
|
||||
{
|
||||
mode: 'cors'
|
||||
}
|
||||
);
|
||||
const { flowId, flowBeginTime } = await res.json();
|
||||
this.flowId = flowId;
|
||||
this.flowBeginTime = flowBeginTime;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.flowId = null;
|
||||
this.flowBeginTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
async login(email) {
|
||||
const state = arrayToB64(crypto.getRandomValues(new Uint8Array(16)));
|
||||
|
128
docs/metrics.md
128
docs/metrics.md
@ -1,128 +0,0 @@
|
||||
# Send V2 Metrics Definitions
|
||||
|
||||
## Key Value Prop
|
||||
|
||||
Quickly and privately transfer large files from any device to any device.
|
||||
|
||||
## Key Business Question to Answer
|
||||
|
||||
Is the value proposition of a large encrypted file transfer service enough to drive Firefox Account relationships for non-Firefox users.
|
||||
|
||||
## Hypotheses to Test
|
||||
|
||||
### Primary - In support of Relationships KPI
|
||||
|
||||
We believe that a privacy-respecting file transfer service can drive Firefox Accounts beyond the Firefox Browser.
|
||||
|
||||
We will know this to be true when we see 250k Firefox Account creations from non-Firefox contexts w/in six months of launch.
|
||||
|
||||
### Secondary - In support of Revenue KPI
|
||||
|
||||
We believe that a privacy respecting service accessible beyond the reach of Firefox will provide a valuable platform to research, communicate with, and market to conscious choosers we have traditionally found hard to reach.
|
||||
|
||||
We will know this to be true when we can conduct six research tasks (surveys, A/B tests, fake doors, etc) in support of premium services KPIs in the first six months after launch.
|
||||
|
||||
## Overview of Key Measures
|
||||
|
||||
* Number of people using the service to send and receive files
|
||||
* Why: measure of service size. Important for understanding addressable market size
|
||||
* Percent of users who have or create an FxAccount via Send
|
||||
* Why: representation of % of any service users who might be amenable to an upsell
|
||||
* % of downloaders who convert into uploaders
|
||||
* Why: represents a measure of our key growth-loop potential
|
||||
* Count of uploads and size
|
||||
* Why: Represents cost of service on a running basis
|
||||
|
||||
## Key Funnels
|
||||
* App Open or Visit `--- DESIRED OUTCOME --->` Successful Upload
|
||||
* Download UI Visit `--- DESIRED OUTCOME --->` Successful Download
|
||||
* FxA UI Engagement `--- DESIRED OUTCOME --->` Authenticate
|
||||
* **STRETCH** App Open or Visit `--- DESIRED OUTCOME --->` Successful Download
|
||||
|
||||
## Amplitude Schema
|
||||
|
||||
Please see, **See Amplitude HTTP API**(https://amplitude.zendesk.com/hc/en-us/articles/204771828) for HTTP API reference.
|
||||
|
||||
## Metric Events
|
||||
|
||||
In support of our KPIs we collect events from two separate contexts, server and client. The events are designed to have minimal correlation between contexts.
|
||||
|
||||
Server events collect lifecycle information about individual uploads but no user information; also time precision is truncated to hour increments. Client events collect information about how users interact with the UI but no upload identifiers.
|
||||
|
||||
### Server Events
|
||||
|
||||
Server events allow us to aggregate data about file lifecycle without collecting data about individual users. In this context `user_id` and `user_properties` describe the uploaded archive.
|
||||
|
||||
* `session_id` -1 (not part of a session)
|
||||
* `user_id` hash of (archive_id + owner_id)
|
||||
* `app_version` package.json version
|
||||
* `time` timestamp truncated to hour precision
|
||||
* `country`
|
||||
* `region`
|
||||
* `event_type` [server_upload | server_download | server_delete]
|
||||
* `user_properties`
|
||||
* `download_limit` set number of downloads
|
||||
* `time_limit` set expiry duration
|
||||
* `size` approximate size (log10)
|
||||
* `anonymous` true if anonymous, false if fxa
|
||||
* `event_properties`
|
||||
* `download_count` downloads completed
|
||||
* `ttl` time remaining before expiry truncated to hour
|
||||
* `agent` the browser name or first 6 characters of the user agent that made the request
|
||||
|
||||
### Client Events
|
||||
|
||||
Client events allow us to aggregate data about how the user interface is being used without tracking the lifecycle of individual files. In this context `user_id` and `user_properties` describe the user. The `user_id` and `device_id` change for all users at the beginning of each month.
|
||||
|
||||
* `session_id` timestamp
|
||||
* `user_id` hash of (fxa_id + Date.year + Date.month)
|
||||
* `device_id` hash of (localStorage random id + Date.year + Date.month)
|
||||
* `platform` [web | android]
|
||||
* `country`
|
||||
* `region`
|
||||
* `language`
|
||||
* `time` timestamp
|
||||
* `os_name`
|
||||
* `event_type` [client_visit | client_upload | client_download | client_delete | client_login | client_logout]
|
||||
* `event_properties`
|
||||
* `browser`
|
||||
* `browser_version`
|
||||
* `status` [ ok | error | cancel ]
|
||||
* Event specific properties (see below)
|
||||
* `user_properties`
|
||||
* `active_count` number of active uploads
|
||||
* `anonymous` true if anonymous, false if fxa
|
||||
* `experiments` list of experiment ids the user is participating in
|
||||
* `first_action` how this use came to Send the first time [ upload | download ]
|
||||
|
||||
#### Visit Event
|
||||
|
||||
* `entrypoint` [ upload | download ]
|
||||
|
||||
#### Upload Event
|
||||
|
||||
* `download_limit` download limit
|
||||
* `file_count` number of files
|
||||
* `password_protected` boolean
|
||||
* `size` approximate size (log10)
|
||||
* `time_limit` time limit
|
||||
* `duration` approximate transfer duration (log10)
|
||||
|
||||
#### Download Event
|
||||
|
||||
* `password_protected` boolean
|
||||
* `size` approximate size (log10)
|
||||
* `duration` approximate transfer duration (log10)
|
||||
|
||||
#### Delete Event
|
||||
|
||||
* `age` hours since uploaded
|
||||
* `downloaded` downloaded at least once
|
||||
|
||||
#### Login Event
|
||||
|
||||
* `trigger` [button | time | count | size]
|
||||
|
||||
#### Logout Event
|
||||
|
||||
* `trigger` [button | timeout]
|
@ -1,171 +0,0 @@
|
||||
const crypto = require('crypto');
|
||||
const fetch = require('node-fetch');
|
||||
const config = require('./config');
|
||||
const pkg = require('../package.json');
|
||||
|
||||
const HOUR = 1000 * 60 * 60;
|
||||
|
||||
function truncateToHour(timestamp) {
|
||||
return Math.floor(timestamp / HOUR) * HOUR;
|
||||
}
|
||||
|
||||
function orderOfMagnitude(n) {
|
||||
return Math.floor(Math.log10(n));
|
||||
}
|
||||
|
||||
function userId(fileId, ownerId) {
|
||||
const hash = crypto.createHash('sha256');
|
||||
hash.update(fileId);
|
||||
hash.update(ownerId);
|
||||
return hash.digest('hex').substring(32);
|
||||
}
|
||||
|
||||
function statUploadEvent(data) {
|
||||
const event = {
|
||||
session_id: -1,
|
||||
country: data.country,
|
||||
region: data.state,
|
||||
user_id: userId(data.id, data.owner),
|
||||
app_version: pkg.version,
|
||||
time: truncateToHour(Date.now()),
|
||||
event_type: 'server_upload',
|
||||
user_properties: {
|
||||
download_limit: data.dlimit,
|
||||
time_limit: data.timeLimit,
|
||||
size: orderOfMagnitude(data.size),
|
||||
anonymous: data.anonymous
|
||||
},
|
||||
event_properties: {
|
||||
agent: data.agent
|
||||
},
|
||||
event_id: 0
|
||||
};
|
||||
return sendBatch([event]);
|
||||
}
|
||||
|
||||
function statDownloadEvent(data) {
|
||||
const event = {
|
||||
session_id: -1,
|
||||
country: data.country,
|
||||
region: data.state,
|
||||
user_id: userId(data.id, data.owner),
|
||||
app_version: pkg.version,
|
||||
time: truncateToHour(Date.now()),
|
||||
event_type: 'server_download',
|
||||
event_properties: {
|
||||
agent: data.agent,
|
||||
download_count: data.download_count,
|
||||
ttl: data.ttl
|
||||
},
|
||||
event_id: data.download_count
|
||||
};
|
||||
return sendBatch([event]);
|
||||
}
|
||||
|
||||
function statDeleteEvent(data) {
|
||||
const event = {
|
||||
session_id: -1,
|
||||
country: data.country,
|
||||
region: data.state,
|
||||
user_id: userId(data.id, data.owner),
|
||||
app_version: pkg.version,
|
||||
time: truncateToHour(Date.now()),
|
||||
event_type: 'server_delete',
|
||||
event_properties: {
|
||||
agent: data.agent,
|
||||
download_count: data.download_count,
|
||||
ttl: data.ttl
|
||||
},
|
||||
event_id: data.download_count + 1
|
||||
};
|
||||
return sendBatch([event]);
|
||||
}
|
||||
|
||||
function clientEvent(
|
||||
event,
|
||||
ua,
|
||||
language,
|
||||
session_id,
|
||||
deltaT,
|
||||
platform,
|
||||
country,
|
||||
state
|
||||
) {
|
||||
const ep = event.event_properties || {};
|
||||
const up = event.user_properties || {};
|
||||
const event_properties = {
|
||||
browser: ua.browser.name,
|
||||
browser_version: ua.browser.version,
|
||||
status: ep.status,
|
||||
|
||||
age: ep.age,
|
||||
downloaded: ep.downloaded,
|
||||
download_limit: ep.download_limit,
|
||||
duration: ep.duration,
|
||||
entrypoint: ep.entrypoint,
|
||||
file_count: ep.file_count,
|
||||
password_protected: ep.password_protected,
|
||||
referrer: ep.referrer,
|
||||
size: ep.size,
|
||||
time_limit: ep.time_limit,
|
||||
trigger: ep.trigger,
|
||||
ttl: ep.ttl,
|
||||
utm_campaign: ep.utm_campaign,
|
||||
utm_content: ep.utm_content,
|
||||
utm_medium: ep.utm_medium,
|
||||
utm_source: ep.utm_source,
|
||||
utm_term: ep.utm_term,
|
||||
experiment: ep.experiment,
|
||||
variant: ep.variant
|
||||
};
|
||||
const user_properties = {
|
||||
active_count: up.active_count,
|
||||
anonymous: up.anonymous,
|
||||
experiments: up.experiments,
|
||||
first_action: up.first_action
|
||||
};
|
||||
return {
|
||||
app_version: pkg.version,
|
||||
country: country,
|
||||
device_id: event.device_id,
|
||||
event_properties,
|
||||
event_type: event.event_type,
|
||||
language,
|
||||
os_name: ua.os.name,
|
||||
os_version: ua.os.version,
|
||||
platform,
|
||||
region: state,
|
||||
session_id,
|
||||
time: event.time + deltaT,
|
||||
user_id: event.user_id,
|
||||
user_properties
|
||||
};
|
||||
}
|
||||
|
||||
async function sendBatch(events, timeout = 1000) {
|
||||
if (!config.amplitude_id) {
|
||||
return 200;
|
||||
}
|
||||
try {
|
||||
const result = await fetch('https://api.amplitude.com/batch', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
api_key: config.amplitude_id,
|
||||
events
|
||||
}),
|
||||
timeout
|
||||
});
|
||||
return result.status;
|
||||
} catch (e) {
|
||||
return 500;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
statUploadEvent,
|
||||
statDownloadEvent,
|
||||
statDeleteEvent,
|
||||
clientEvent,
|
||||
sendBatch
|
||||
};
|
@ -100,16 +100,6 @@ const conf = convict({
|
||||
arg: 'port',
|
||||
env: 'PORT'
|
||||
},
|
||||
amplitude_id: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'AMPLITUDE_ID'
|
||||
},
|
||||
analytics_id: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'GOOGLE_ANALYTICS_ID'
|
||||
},
|
||||
sentry_id: {
|
||||
format: String,
|
||||
default: '',
|
||||
|
@ -1,23 +1,10 @@
|
||||
const storage = require('../storage');
|
||||
const { statDeleteEvent } = require('../amplitude');
|
||||
|
||||
module.exports = async function(req, res) {
|
||||
try {
|
||||
const id = req.params.id;
|
||||
const meta = req.meta;
|
||||
const ttl = await storage.ttl(id);
|
||||
await storage.del(id);
|
||||
res.sendStatus(200);
|
||||
statDeleteEvent({
|
||||
id,
|
||||
ip: req.ip,
|
||||
country: req.geo.country,
|
||||
state: req.geo.state,
|
||||
owner: meta.owner,
|
||||
download_count: meta.dl,
|
||||
ttl,
|
||||
agent: req.ua.browser.name || req.ua.ua.substring(0, 6)
|
||||
});
|
||||
} catch (e) {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
const storage = require('../storage');
|
||||
const mozlog = require('../log');
|
||||
const log = mozlog('send.download');
|
||||
const { statDownloadEvent } = require('../amplitude');
|
||||
|
||||
module.exports = async function(req, res) {
|
||||
const id = req.params.id;
|
||||
@ -27,17 +26,6 @@ module.exports = async function(req, res) {
|
||||
|
||||
const dl = meta.dl + 1;
|
||||
const dlimit = meta.dlimit;
|
||||
const ttl = await storage.ttl(id);
|
||||
statDownloadEvent({
|
||||
id,
|
||||
ip: req.ip,
|
||||
country: req.geo.country,
|
||||
state: req.geo.state,
|
||||
owner: meta.owner,
|
||||
download_count: dl,
|
||||
ttl,
|
||||
agent: req.ua.browser.name || req.ua.ua.substring(0, 6)
|
||||
});
|
||||
try {
|
||||
if (dl >= dlimit) {
|
||||
await storage.del(id);
|
||||
|
@ -112,7 +112,6 @@ module.exports = function(app) {
|
||||
require('./params')
|
||||
);
|
||||
app.post(`/api/info/:id${ID_REGEX}`, auth.owner, require('./info'));
|
||||
app.post('/api/metrics', require('./metrics'));
|
||||
app.get('/__version__', function(req, res) {
|
||||
// eslint-disable-next-line node/no-missing-require
|
||||
res.sendFile(require.resolve('../../dist/version.json'));
|
||||
|
@ -1,24 +0,0 @@
|
||||
const { sendBatch, clientEvent } = require('../amplitude');
|
||||
|
||||
module.exports = async function(req, res) {
|
||||
try {
|
||||
const data = JSON.parse(req.body); // see http://crbug.com/490015
|
||||
const deltaT = Date.now() - data.now;
|
||||
const events = data.events.map(e =>
|
||||
clientEvent(
|
||||
e,
|
||||
req.ua,
|
||||
data.lang,
|
||||
data.session_id + deltaT,
|
||||
deltaT,
|
||||
data.platform,
|
||||
req.geo.country,
|
||||
req.geo.state
|
||||
)
|
||||
);
|
||||
const status = await sendBatch(events);
|
||||
res.sendStatus(status);
|
||||
} catch (e) {
|
||||
res.sendStatus(500);
|
||||
}
|
||||
};
|
@ -4,7 +4,6 @@ const config = require('../config');
|
||||
const mozlog = require('../log');
|
||||
const Limiter = require('../limiter');
|
||||
const fxa = require('../fxa');
|
||||
const { statUploadEvent } = require('../amplitude');
|
||||
const { encryptedSize } = require('../../app/utils');
|
||||
|
||||
const { Transform } = require('stream');
|
||||
@ -108,18 +107,6 @@ module.exports = function(ws, req) {
|
||||
// in order to avoid having to check socket state and clean
|
||||
// up storage, possibly with an exception that we can catch.
|
||||
ws.send(JSON.stringify({ ok: true }));
|
||||
statUploadEvent({
|
||||
id: newId,
|
||||
ip: req.ip,
|
||||
country: req.geo.country,
|
||||
state: req.geo.state,
|
||||
owner,
|
||||
dlimit,
|
||||
timeLimit,
|
||||
anonymous: !user,
|
||||
size: limiter.length,
|
||||
agent: req.ua.browser.name || req.ua.ua.substring(0, 6)
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log.error('upload', e);
|
||||
|
Loading…
Reference in New Issue
Block a user