lbry-desktop/ui/analytics.js
Sean Yesmunt ad19495702 odysee
Naomi

comment websockets

increase slow mode time to 5 seconds

fix to prevent duplicate comments

update livestream details

fix channel

pin electron boom

fix rebase

prune unused icons

updating meme

updating meme

update livestream for naomi

fix rebase

DigitalCashNetwork

remove electroboom pin

Slavguns

Joel

So he can edit his claims

add streamTypes param to claimTilesDiscover so following section can search for all types of content

fix typo
2021-03-24 16:09:50 -04:00

338 lines
10 KiB
JavaScript

// @flow
import { Lbryio } from 'lbryinc';
import * as Sentry from '@sentry/browser';
import MatomoTracker from '@datapunt/matomo-tracker-js';
import { history } from './store';
import { SDK_API_PATH } from './index';
// @if TARGET='app'
import Native from 'native';
import ElectronCookies from '@exponent/electron-cookies';
import { generateInitialUrl } from 'util/url';
// @endif
import { MATOMO_ID, MATOMO_URL, LBRY_WEB_BUFFER_API } from 'config';
const isProduction = process.env.NODE_ENV === 'production';
const devInternalApis = process.env.LBRY_API_URL;
export const SHARE_INTERNAL = 'shareInternal';
const SHARE_THIRD_PARTY = 'shareThirdParty';
// @if TARGET='app'
if (isProduction) {
ElectronCookies.enable({
origin: 'https://lbry.tv',
});
}
// @endif
type Analytics = {
error: (string) => Promise<any>,
sentryError: ({} | string, {}) => Promise<any>,
pageView: (string, ?string) => void,
setUser: (Object) => void,
toggleInternal: (boolean, ?boolean) => void,
apiLogView: (string, string, string, ?number, ?() => void) => Promise<any>,
apiLogPublish: (ChannelClaim | StreamClaim) => void,
apiSyncTags: ({}) => void,
tagFollowEvent: (string, boolean, ?string) => void,
playerLoadedEvent: (?boolean) => void,
playerStartedEvent: (?boolean) => void,
videoFetchDuration: (string, number) => void,
videoStartEvent: (string, number) => void,
adsFetchedEvent: () => void,
adsReceivedEvent: (any) => void,
adsErrorEvent: (any) => void,
videoBufferEvent: (
StreamClaim,
{
timeAtBuffer: number,
bufferDuration: number,
bitRate: number,
duration: number,
userId: string,
playerPoweredBy: string,
readyState: number,
}
) => void,
emailProvidedEvent: () => void,
emailVerifiedEvent: () => void,
rewardEligibleEvent: () => void,
startupEvent: () => void,
purchaseEvent: (number) => void,
readyEvent: (number) => void,
openUrlEvent: (string) => void,
};
type LogPublishParams = {
uri: string,
claim_id: string,
outpoint: string,
channel_claim_id?: string,
};
let internalAnalyticsEnabled: boolean = IS_WEB || false;
// let thirdPartyAnalyticsEnabled: boolean = IS_WEB || false;
// @if TARGET='app'
if (window.localStorage.getItem(SHARE_INTERNAL) === 'true') internalAnalyticsEnabled = true;
// if (window.localStorage.getItem(SHARE_THIRD_PARTY) === 'true') thirdPartyAnalyticsEnabled = true;
// @endif
const analytics: Analytics = {
error: (message) => {
return new Promise((resolve) => {
if (internalAnalyticsEnabled && isProduction) {
return Lbryio.call('event', 'desktop_error', { error_message: message }).then(() => {
resolve(true);
});
} else {
resolve(false);
}
});
},
sentryError: (error, errorInfo) => {
return new Promise((resolve) => {
if (internalAnalyticsEnabled && isProduction) {
Sentry.withScope((scope) => {
scope.setExtras(errorInfo);
const eventId = Sentry.captureException(error);
resolve(eventId);
});
} else {
resolve(null);
}
});
},
pageView: (path, search) => {
if (internalAnalyticsEnabled) {
const params: { href: string, customDimensions?: Array<{ id: number, value: ?string }> } = { href: `${path}` };
const dimensions = [];
const searchParams = search && new URLSearchParams(search);
if (searchParams && searchParams.get('src')) {
dimensions.push({ id: 1, value: searchParams.get('src') });
}
if (dimensions.length) {
params['customDimensions'] = dimensions;
}
MatomoInstance.trackPageView(params);
}
},
setUser: (userId) => {
if (internalAnalyticsEnabled && userId) {
window._paq.push(['setUserId', String(userId)]);
// @if TARGET='app'
Native.getAppVersionInfo().then(({ localVersion }) => {
sendMatomoEvent('Version', 'Desktop-Version', localVersion);
});
// @endif
}
},
toggleInternal: (enabled: boolean): void => {
// Always collect analytics on lbry.tv
// @if TARGET='app'
internalAnalyticsEnabled = enabled;
window.localStorage.setItem(SHARE_INTERNAL, enabled);
// @endif
},
toggleThirdParty: (enabled: boolean): void => {
// Always collect analytics on lbry.tv
// @if TARGET='app'
// thirdPartyAnalyticsEnabled = enabled;
window.localStorage.setItem(SHARE_THIRD_PARTY, enabled);
// @endif
},
apiLogView: (uri, outpoint, claimId, timeToStart) => {
return new Promise((resolve, reject) => {
if (internalAnalyticsEnabled && (isProduction || devInternalApis)) {
const params: {
uri: string,
outpoint: string,
claim_id: string,
time_to_start?: number,
} = {
uri,
outpoint,
claim_id: claimId,
};
// lbry.tv streams from AWS so we don't care about the time to start
if (timeToStart && !IS_WEB) {
params.time_to_start = timeToStart;
}
resolve(Lbryio.call('file', 'view', params));
} else {
resolve();
}
});
},
apiLogSearch: () => {
if (internalAnalyticsEnabled && isProduction) {
Lbryio.call('event', 'search');
}
},
apiLogPublish: (claimResult: ChannelClaim | StreamClaim) => {
// Don't check if this is production so channels created on localhost are still linked to user
if (internalAnalyticsEnabled) {
const { permanent_url: uri, claim_id: claimId, txid, nout, signing_channel: signingChannel } = claimResult;
let channelClaimId;
if (signingChannel) {
channelClaimId = signingChannel.claim_id;
}
const outpoint = `${txid}:${nout}`;
const params: LogPublishParams = { uri, claim_id: claimId, outpoint };
if (channelClaimId) {
params['channel_claim_id'] = channelClaimId;
}
Lbryio.call('event', 'publish', params);
}
},
apiSyncTags: (params) => {
if (internalAnalyticsEnabled && isProduction) {
Lbryio.call('content_tags', 'sync', params);
}
},
apiSearchFeedback: (query, vote) => {
if (isProduction) {
// We don't need to worry about analytics enabled here because users manually click on the button to provide feedback
Lbryio.call('feedback', 'search', { query, vote });
}
},
videoFetchDuration: (source, duration) => {
sendPromMetric('time_to_fetch', duration);
sendMatomoEvent('Media', 'TimeToFetch', source, duration);
},
videoStartEvent: (claimId, duration) => {
sendPromMetric('time_to_start', duration);
sendMatomoEvent('Media', 'TimeToStart', claimId, duration);
},
videoBufferEvent: (claim, data) => {
sendMatomoEvent('Media', 'BufferTimestamp', claim.claim_id, data.timeAtBuffer);
if (LBRY_WEB_BUFFER_API) {
fetch(LBRY_WEB_BUFFER_API, {
method: 'post',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
device: 'web',
type: 'buffering',
client: data.userId,
data: {
url: claim.canonical_url,
position: data.timeAtBuffer,
duration: data.bufferDuration,
player: data.playerPoweredBy,
readyState: data.readyState,
stream_duration: data.duration,
stream_bitrate: data.bitRate,
},
}),
});
}
},
playerLoadedEvent: (embedded) => {
sendMatomoEvent('Player', 'Loaded', embedded ? 'embedded' : 'onsite');
},
playerStartedEvent: (embedded) => {
sendMatomoEvent('Player', 'Started', embedded ? 'embedded' : 'onsite');
},
adsFetchedEvent: () => {
sendMatomoEvent('Media', 'AdsFetched');
},
adsReceivedEvent: (response) => {
sendMatomoEvent('Media', 'AdsReceived', JSON.stringify(response));
},
adsErrorEvent: (response) => {
sendMatomoEvent('Media', 'AdsError', JSON.stringify(response));
},
tagFollowEvent: (tag, following) => {
sendMatomoEvent('Tag', following ? 'Tag-Follow' : 'Tag-Unfollow', tag);
},
channelBlockEvent: (uri, blocked, location) => {
sendMatomoEvent(blocked ? 'Channel-Hidden' : 'Channel-Unhidden', uri);
},
emailProvidedEvent: () => {
sendMatomoEvent('Engagement', 'Email-Provided');
},
emailVerifiedEvent: () => {
sendMatomoEvent('Engagement', 'Email-Verified');
},
rewardEligibleEvent: () => {
sendMatomoEvent('Engagement', 'Reward-Eligible');
},
openUrlEvent: (url: string) => {
sendMatomoEvent('Engagement', 'Open-Url', url);
},
trendingAlgorithmEvent: (trendingAlgorithm: string) => {
sendMatomoEvent('Engagement', 'Trending-Algorithm', trendingAlgorithm);
},
startupEvent: () => {
sendMatomoEvent('Startup', 'Startup');
},
readyEvent: (timeToReady: number) => {
sendMatomoEvent('Startup', 'App-Ready', 'Time', timeToReady);
},
purchaseEvent: (purchaseInt: number) => {
sendMatomoEvent('Purchase', 'Purchase-Complete', 'someLabel', purchaseInt);
},
};
function sendMatomoEvent(category, action, name, value) {
if (internalAnalyticsEnabled) {
const event = { category, action, name, value };
MatomoInstance.trackEvent(event);
}
}
function sendPromMetric(name: string, value?: number) {
if (IS_WEB) {
let url = new URL(SDK_API_PATH + '/metric/ui');
const params = { name: name, value: value ? value.toString() : '' };
url.search = new URLSearchParams(params).toString();
return fetch(url, { method: 'post' });
}
}
const MatomoInstance = new MatomoTracker({
urlBase: MATOMO_URL,
siteId: MATOMO_ID, // optional, default value: `1`
// heartBeat: { // optional, enabled by default
// active: true, // optional, default value: true
// seconds: 10 // optional, default value: `15
// },
// linkTracking: false // optional, default value: true
});
// Manually call the first page view
// React Router doesn't include this on `history.listen`
// @if TARGET='web'
analytics.pageView(window.location.pathname + window.location.search, window.location.search);
// @endif
// @if TARGET='app'
analytics.pageView(
window.location.pathname.split('.html')[1] + window.location.search || generateInitialUrl(window.location.hash)
);
// @endif;
// Listen for url changes and report
// This will include search queries
history.listen((location) => {
const { pathname, search } = location;
const page = `${pathname}${search}`;
analytics.pageView(page, search);
});
export default analytics;