Analytics fixes (closes #386) (#387)

* Only send analytics events when the user is logged in

* Only send analytics events when the user is logged in (web)

* Add analytics identify() call
zio/stable
Paul Frazee 2023-04-05 11:15:22 -05:00 committed by GitHub
parent 92b80ff048
commit 8e28d3c6be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 108 additions and 30 deletions

View File

@ -21,7 +21,7 @@
"e2e:run": "detox test --configuration ios.sim.debug --take-screenshots all" "e2e:run": "detox test --configuration ios.sim.debug --take-screenshots all"
}, },
"dependencies": { "dependencies": {
"@atproto/api": "0.2.1", "@atproto/api": "0.2.3",
"@bam.tech/react-native-image-resizer": "^3.0.4", "@bam.tech/react-native-image-resizer": "^3.0.4",
"@expo/webpack-config": "^18.0.1", "@expo/webpack-config": "^18.0.1",
"@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/fontawesome-svg-core": "^6.1.1",

View File

@ -1,19 +1,56 @@
import React from 'react' import React from 'react'
import {AppState, AppStateStatus} from 'react-native' import {AppState, AppStateStatus} from 'react-native'
import {createClient, AnalyticsProvider} from '@segment/analytics-react-native' import {
createClient,
AnalyticsProvider,
useAnalytics as useAnalyticsOrig,
} from '@segment/analytics-react-native'
import {RootStoreModel, AppInfo} from 'state/models/root-store' import {RootStoreModel, AppInfo} from 'state/models/root-store'
import {useStores} from 'state/models/root-store'
import {sha256} from 'js-sha256'
const segmentClient = createClient({ const segmentClient = createClient({
writeKey: '8I6DsgfiSLuoONyaunGoiQM7A6y2ybdI', writeKey: '8I6DsgfiSLuoONyaunGoiQM7A6y2ybdI',
trackAppLifecycleEvents: false, trackAppLifecycleEvents: false,
}) })
export const track = segmentClient?.track?.bind?.(segmentClient)
export {useAnalytics} from '@segment/analytics-react-native' export function useAnalytics() {
const store = useStores()
const methods = useAnalyticsOrig()
return React.useMemo(() => {
if (store.session.hasSession) {
return methods
}
// dont send analytics pings for anonymous users
return {
screen: () => {},
track: () => {},
identify: () => {},
flush: () => {},
group: () => {},
alias: () => {},
reset: () => {},
}
}, [store, methods])
}
export function init(store: RootStoreModel) { export function init(store: RootStoreModel) {
store.onSessionLoaded(() => {
const sess = store.session.currentSession
if (sess) {
if (sess.email) {
store.log.debug('Ping w/hash')
const email_hashed = sha256(sess.email)
segmentClient.identify(email_hashed, {email_hashed})
} else {
store.log.debug('Ping w/o hash')
segmentClient.identify()
}
}
})
// NOTE // NOTE
// this method is a copy of segment's own lifecycle event tracking // this is a copy of segment's own lifecycle event tracking
// we handle it manually to ensure that it never fires while the app is backgrounded // we handle it manually to ensure that it never fires while the app is backgrounded
// -prf // -prf
segmentClient.isReady.onChange(() => { segmentClient.isReady.onChange(() => {
@ -33,11 +70,14 @@ export function init(store: RootStoreModel) {
store.log.debug('Recording app info', {new: newAppInfo, old: oldAppInfo}) store.log.debug('Recording app info', {new: newAppInfo, old: oldAppInfo})
if (typeof oldAppInfo === 'undefined') { if (typeof oldAppInfo === 'undefined') {
if (store.session.hasSession) {
segmentClient.track('Application Installed', { segmentClient.track('Application Installed', {
version: newAppInfo.version, version: newAppInfo.version,
build: newAppInfo.build, build: newAppInfo.build,
}) })
}
} else if (newAppInfo.version !== oldAppInfo.version) { } else if (newAppInfo.version !== oldAppInfo.version) {
if (store.session.hasSession) {
segmentClient.track('Application Updated', { segmentClient.track('Application Updated', {
version: newAppInfo.version, version: newAppInfo.version,
build: newAppInfo.build, build: newAppInfo.build,
@ -45,11 +85,14 @@ export function init(store: RootStoreModel) {
previous_build: oldAppInfo.build, previous_build: oldAppInfo.build,
}) })
} }
}
if (store.session.hasSession) {
segmentClient.track('Application Opened', { segmentClient.track('Application Opened', {
from_background: false, from_background: false,
version: newAppInfo.version, version: newAppInfo.version,
build: newAppInfo.build, build: newAppInfo.build,
}) })
}
}) })
let lastState: AppStateStatus = AppState.currentState let lastState: AppStateStatus = AppState.currentState

View File

@ -1,6 +1,12 @@
import React from 'react' import React from 'react'
import {createClient, AnalyticsProvider} from '@segment/analytics-react' import {
createClient,
AnalyticsProvider,
useAnalytics as useAnalyticsOrig,
} from '@segment/analytics-react'
import {RootStoreModel} from 'state/models/root-store' import {RootStoreModel} from 'state/models/root-store'
import {useStores} from 'state/models/root-store'
import {sha256} from 'js-sha256'
const segmentClient = createClient( const segmentClient = createClient(
{ {
@ -16,10 +22,40 @@ const segmentClient = createClient(
) )
export const track = segmentClient?.track?.bind?.(segmentClient) export const track = segmentClient?.track?.bind?.(segmentClient)
export {useAnalytics} from '@segment/analytics-react' export function useAnalytics() {
const store = useStores()
const methods = useAnalyticsOrig()
return React.useMemo(() => {
if (store.session.hasSession) {
return methods
}
// dont send analytics pings for anonymous users
return {
screen: () => {},
track: () => {},
identify: () => {},
flush: () => {},
group: () => {},
alias: () => {},
reset: () => {},
}
}, [store, methods])
}
export function init(_store: RootStoreModel) { export function init(store: RootStoreModel) {
// no init needed on web store.onSessionLoaded(() => {
const sess = store.session.currentSession
if (sess) {
if (sess.email) {
store.log.debug('Ping w/hash')
const email_hashed = sha256(sess.email)
segmentClient.identify(email_hashed, {email_hashed})
} else {
store.log.debug('Ping w/o hash')
segmentClient.identify()
}
}
})
} }
export function Provider({children}: React.PropsWithChildren<{}>) { export function Provider({children}: React.PropsWithChildren<{}>) {

View File

@ -25,6 +25,7 @@ export const accountData = z.object({
accessJwt: z.string().optional(), accessJwt: z.string().optional(),
handle: z.string(), handle: z.string(),
did: z.string(), did: z.string(),
email: z.string().optional(),
displayName: z.string().optional(), displayName: z.string().optional(),
aviUrl: z.string().optional(), aviUrl: z.string().optional(),
}) })
@ -201,6 +202,7 @@ export class SessionModel {
accessJwt, accessJwt,
handle: session?.handle || existingAccount?.handle || '', handle: session?.handle || existingAccount?.handle || '',
email: session?.email || existingAccount?.email || '',
displayName: addedInfo displayName: addedInfo
? addedInfo.displayName ? addedInfo.displayName
: existingAccount?.displayName || '', : existingAccount?.displayName || '',

View File

@ -8,7 +8,6 @@ import {
View, View,
} from 'react-native' } from 'react-native'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {sha256} from 'js-sha256'
import {useAnalytics} from 'lib/analytics' import {useAnalytics} from 'lib/analytics'
import {Text} from '../../util/text/Text' import {Text} from '../../util/text/Text'
import {s, colors} from 'lib/styles' import {s, colors} from 'lib/styles'
@ -22,7 +21,7 @@ import {Step3} from './Step3'
export const CreateAccount = observer( export const CreateAccount = observer(
({onPressBack}: {onPressBack: () => void}) => { ({onPressBack}: {onPressBack: () => void}) => {
const {track, screen, identify} = useAnalytics() const {track, screen} = useAnalytics()
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const model = React.useMemo(() => new CreateAccountModel(store), [store]) const model = React.useMemo(() => new CreateAccountModel(store), [store])
@ -57,14 +56,12 @@ export const CreateAccount = observer(
} else { } else {
try { try {
await model.submit() await model.submit()
const email_hashed = sha256(model.email)
identify(email_hashed, {email_hashed})
track('Create Account') track('Create Account')
} catch { } catch {
// dont need to handle here // dont need to handle here
} }
} }
}, [model, identify, track]) }, [model, track])
return ( return (
<ScrollView testID="createAccount" style={pal.view}> <ScrollView testID="createAccount" style={pal.view}>

View File

@ -30,10 +30,10 @@
tlds "^1.234.0" tlds "^1.234.0"
typed-emitter "^2.1.0" typed-emitter "^2.1.0"
"@atproto/api@0.2.1": "@atproto/api@0.2.3":
version "0.2.1" version "0.2.3"
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.2.1.tgz#034cab5928e1a6b0059e7237f6a82c57daadb264" resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.2.3.tgz#0eb9cb542c113b2c839f2c5ca284c30b117f489a"
integrity sha512-ub92BFrHrm/r1En9IedqRc9r9BZy0i7J8mmFZ5EMxRJwdCJeMYB8CdmLfgNXQcsTPswbYF94pyZkrpeQNJWr1A== integrity sha512-i0tWdOPQyZuSlkd2MY3s7QTac2ovH104tzy5rJwTZXZyhpf2Zom1xedaHb+pQmFzug7YaD7tx7OMSPlJIV0dpg==
dependencies: dependencies:
"@atproto/common-web" "*" "@atproto/common-web" "*"
"@atproto/uri" "*" "@atproto/uri" "*"