Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

authored by

Paul Frazee and committed by
GitHub
8e28d3c6 92b80ff0

+108 -30
+1 -1
package.json
··· 21 21 "e2e:run": "detox test --configuration ios.sim.debug --take-screenshots all" 22 22 }, 23 23 "dependencies": { 24 - "@atproto/api": "0.2.1", 24 + "@atproto/api": "0.2.3", 25 25 "@bam.tech/react-native-image-resizer": "^3.0.4", 26 26 "@expo/webpack-config": "^18.0.1", 27 27 "@fortawesome/fontawesome-svg-core": "^6.1.1",
+59 -16
src/lib/analytics.tsx
··· 1 1 import React from 'react' 2 2 import {AppState, AppStateStatus} from 'react-native' 3 - import {createClient, AnalyticsProvider} from '@segment/analytics-react-native' 3 + import { 4 + createClient, 5 + AnalyticsProvider, 6 + useAnalytics as useAnalyticsOrig, 7 + } from '@segment/analytics-react-native' 4 8 import {RootStoreModel, AppInfo} from 'state/models/root-store' 9 + import {useStores} from 'state/models/root-store' 10 + import {sha256} from 'js-sha256' 5 11 6 12 const segmentClient = createClient({ 7 13 writeKey: '8I6DsgfiSLuoONyaunGoiQM7A6y2ybdI', 8 14 trackAppLifecycleEvents: false, 9 15 }) 10 - export const track = segmentClient?.track?.bind?.(segmentClient) 11 16 12 - export {useAnalytics} from '@segment/analytics-react-native' 17 + export function useAnalytics() { 18 + const store = useStores() 19 + const methods = useAnalyticsOrig() 20 + return React.useMemo(() => { 21 + if (store.session.hasSession) { 22 + return methods 23 + } 24 + // dont send analytics pings for anonymous users 25 + return { 26 + screen: () => {}, 27 + track: () => {}, 28 + identify: () => {}, 29 + flush: () => {}, 30 + group: () => {}, 31 + alias: () => {}, 32 + reset: () => {}, 33 + } 34 + }, [store, methods]) 35 + } 13 36 14 37 export function init(store: RootStoreModel) { 38 + store.onSessionLoaded(() => { 39 + const sess = store.session.currentSession 40 + if (sess) { 41 + if (sess.email) { 42 + store.log.debug('Ping w/hash') 43 + const email_hashed = sha256(sess.email) 44 + segmentClient.identify(email_hashed, {email_hashed}) 45 + } else { 46 + store.log.debug('Ping w/o hash') 47 + segmentClient.identify() 48 + } 49 + } 50 + }) 51 + 15 52 // NOTE 16 - // this method is a copy of segment's own lifecycle event tracking 53 + // this is a copy of segment's own lifecycle event tracking 17 54 // we handle it manually to ensure that it never fires while the app is backgrounded 18 55 // -prf 19 56 segmentClient.isReady.onChange(() => { ··· 33 70 store.log.debug('Recording app info', {new: newAppInfo, old: oldAppInfo}) 34 71 35 72 if (typeof oldAppInfo === 'undefined') { 36 - segmentClient.track('Application Installed', { 37 - version: newAppInfo.version, 38 - build: newAppInfo.build, 39 - }) 73 + if (store.session.hasSession) { 74 + segmentClient.track('Application Installed', { 75 + version: newAppInfo.version, 76 + build: newAppInfo.build, 77 + }) 78 + } 40 79 } else if (newAppInfo.version !== oldAppInfo.version) { 41 - segmentClient.track('Application Updated', { 80 + if (store.session.hasSession) { 81 + segmentClient.track('Application Updated', { 82 + version: newAppInfo.version, 83 + build: newAppInfo.build, 84 + previous_version: oldAppInfo.version, 85 + previous_build: oldAppInfo.build, 86 + }) 87 + } 88 + } 89 + if (store.session.hasSession) { 90 + segmentClient.track('Application Opened', { 91 + from_background: false, 42 92 version: newAppInfo.version, 43 93 build: newAppInfo.build, 44 - previous_version: oldAppInfo.version, 45 - previous_build: oldAppInfo.build, 46 94 }) 47 95 } 48 - segmentClient.track('Application Opened', { 49 - from_background: false, 50 - version: newAppInfo.version, 51 - build: newAppInfo.build, 52 - }) 53 96 }) 54 97 55 98 let lastState: AppStateStatus = AppState.currentState
+40 -4
src/lib/analytics.web.tsx
··· 1 1 import React from 'react' 2 - import {createClient, AnalyticsProvider} from '@segment/analytics-react' 2 + import { 3 + createClient, 4 + AnalyticsProvider, 5 + useAnalytics as useAnalyticsOrig, 6 + } from '@segment/analytics-react' 3 7 import {RootStoreModel} from 'state/models/root-store' 8 + import {useStores} from 'state/models/root-store' 9 + import {sha256} from 'js-sha256' 4 10 5 11 const segmentClient = createClient( 6 12 { ··· 16 22 ) 17 23 export const track = segmentClient?.track?.bind?.(segmentClient) 18 24 19 - export {useAnalytics} from '@segment/analytics-react' 25 + export function useAnalytics() { 26 + const store = useStores() 27 + const methods = useAnalyticsOrig() 28 + return React.useMemo(() => { 29 + if (store.session.hasSession) { 30 + return methods 31 + } 32 + // dont send analytics pings for anonymous users 33 + return { 34 + screen: () => {}, 35 + track: () => {}, 36 + identify: () => {}, 37 + flush: () => {}, 38 + group: () => {}, 39 + alias: () => {}, 40 + reset: () => {}, 41 + } 42 + }, [store, methods]) 43 + } 20 44 21 - export function init(_store: RootStoreModel) { 22 - // no init needed on web 45 + export function init(store: RootStoreModel) { 46 + store.onSessionLoaded(() => { 47 + const sess = store.session.currentSession 48 + if (sess) { 49 + if (sess.email) { 50 + store.log.debug('Ping w/hash') 51 + const email_hashed = sha256(sess.email) 52 + segmentClient.identify(email_hashed, {email_hashed}) 53 + } else { 54 + store.log.debug('Ping w/o hash') 55 + segmentClient.identify() 56 + } 57 + } 58 + }) 23 59 } 24 60 25 61 export function Provider({children}: React.PropsWithChildren<{}>) {
+2
src/state/models/session.ts
··· 25 25 accessJwt: z.string().optional(), 26 26 handle: z.string(), 27 27 did: z.string(), 28 + email: z.string().optional(), 28 29 displayName: z.string().optional(), 29 30 aviUrl: z.string().optional(), 30 31 }) ··· 201 202 accessJwt, 202 203 203 204 handle: session?.handle || existingAccount?.handle || '', 205 + email: session?.email || existingAccount?.email || '', 204 206 displayName: addedInfo 205 207 ? addedInfo.displayName 206 208 : existingAccount?.displayName || '',
+2 -5
src/view/com/auth/create/CreateAccount.tsx
··· 8 8 View, 9 9 } from 'react-native' 10 10 import {observer} from 'mobx-react-lite' 11 - import {sha256} from 'js-sha256' 12 11 import {useAnalytics} from 'lib/analytics' 13 12 import {Text} from '../../util/text/Text' 14 13 import {s, colors} from 'lib/styles' ··· 22 21 23 22 export const CreateAccount = observer( 24 23 ({onPressBack}: {onPressBack: () => void}) => { 25 - const {track, screen, identify} = useAnalytics() 24 + const {track, screen} = useAnalytics() 26 25 const pal = usePalette('default') 27 26 const store = useStores() 28 27 const model = React.useMemo(() => new CreateAccountModel(store), [store]) ··· 57 56 } else { 58 57 try { 59 58 await model.submit() 60 - const email_hashed = sha256(model.email) 61 - identify(email_hashed, {email_hashed}) 62 59 track('Create Account') 63 60 } catch { 64 61 // dont need to handle here 65 62 } 66 63 } 67 - }, [model, identify, track]) 64 + }, [model, track]) 68 65 69 66 return ( 70 67 <ScrollView testID="createAccount" style={pal.view}>
+4 -4
yarn.lock
··· 30 30 tlds "^1.234.0" 31 31 typed-emitter "^2.1.0" 32 32 33 - "@atproto/api@0.2.1": 34 - version "0.2.1" 35 - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.2.1.tgz#034cab5928e1a6b0059e7237f6a82c57daadb264" 36 - integrity sha512-ub92BFrHrm/r1En9IedqRc9r9BZy0i7J8mmFZ5EMxRJwdCJeMYB8CdmLfgNXQcsTPswbYF94pyZkrpeQNJWr1A== 33 + "@atproto/api@0.2.3": 34 + version "0.2.3" 35 + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.2.3.tgz#0eb9cb542c113b2c839f2c5ca284c30b117f489a" 36 + integrity sha512-i0tWdOPQyZuSlkd2MY3s7QTac2ovH104tzy5rJwTZXZyhpf2Zom1xedaHb+pQmFzug7YaD7tx7OMSPlJIV0dpg== 37 37 dependencies: 38 38 "@atproto/common-web" "*" 39 39 "@atproto/uri" "*"