diff --git a/src/geolocation/index.tsx b/src/geolocation/index.tsx index 6c04020ea0..c9dc0cb1ed 100644 --- a/src/geolocation/index.tsx +++ b/src/geolocation/index.tsx @@ -6,7 +6,6 @@ import { useMemo, } from 'react' -import {updateBaseMetadata} from '#/logger/metadata' import {useSyncDeviceGeolocationOnStartup} from '#/geolocation/device' import {useGeolocationServiceResponse} from '#/geolocation/service' import {type Geolocation} from '#/geolocation/types' @@ -54,9 +53,6 @@ export function Provider({children}: {children: ReactNode}) { * Needs to be available for the data prefetching we do on boot. */ device.set(['mergedGeolocation'], geolocation) - updateBaseMetadata({ - country: geolocation.countryCode || 'unknown', - }) }, [geolocation]) useSyncDeviceGeolocationOnStartup(setDeviceGeolocation) diff --git a/src/logger/growthbook/index.tsx b/src/logger/growthbook/index.tsx deleted file mode 100644 index 3dca39357a..0000000000 --- a/src/logger/growthbook/index.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import {useCallback} from 'react' -import {GrowthBook} from '@growthbook/growthbook-react' - -import {type Metadata} from '#/logger/metadata' -import {metrics} from '#/logger/metrics' -import * as env from '#/env' - -const debugEnabled = env.IS_DEV && true -const debug = (message: string, attributes?: Record) => { - if (debugEnabled) { - console.debug(`(growthbook) ${message}`, attributes || {}) - } -} - -const TIMEOUT_INIT = 500 // TODO should base on p99 or something -const TIMEOUT_PREFER_LOW_LATENCY = 250 -const TIMEOUT_PREFER_FRESH_GATES = 1500 - -/** - * We vary the amount of time we wait for GrowthBook to fetch feature - * gates based on the strategy specified. - */ -type FeatureFetchStrategy = 'prefer-low-latency' | 'prefer-fresh-gates' - -const gb = new GrowthBook({ - apiHost: env.GROWTHBOOK_API_HOST, - clientKey: env.GROWTHBOOK_CLIENT_KEY, - trackingCallback: (experiment, result) => { - metrics.track('experiment:viewed', { - experimentId: experiment.key, - variationId: result.key, - }) - }, - /** - * Initial values are set on startup in `#/logger/metdata/index.ts` - */ - attributes: {}, -}) - -/** - * Initializer promise that must be awaited before using the GrowthBook - * instance or rendering the `Provider`. Note: this may not be fully - * initialized if it takes longer than `TIMEOUT_INIT` to initialize. In that - * case, we may see a flash of uncustomized content until the initialization - * completes. - */ -export const initializer = new Promise(async y => { - await gb.init({timeout: TIMEOUT_INIT}) - y() -}) - -export function getGrowthBook() { - return gb -} - -/** - * Converts our metadata into GrowthBook attributes and sets them. - */ -export function setGrowthBookAttributes({ - deviceId: device_id, - sessionId: session_id, - ...metadata -}: Metadata) { - gb.setAttributes({ - device_id, // GrowthBook special field - session_id, // GrowthBook special field - user_id: metadata.did, // GrowthBook special field - ...metadata, - }) -} - -/** - * Refresh feature gates from GrowthBook. Updates attributes based on the - * provided account, if any. - */ -export async function refresh({strategy}: {strategy: FeatureFetchStrategy}) { - debug(`refresh`, {strategy}) - await gb.refreshFeatures({ - timeout: - strategy === 'prefer-low-latency' - ? TIMEOUT_PREFER_LOW_LATENCY - : TIMEOUT_PREFER_FRESH_GATES, - }) -} - -/** - * Hook to check if a feature gate is enabled - */ -export function useGate() { - return useCallback((gate: string): boolean => { - return gb.isOn(gate) - }, []) -} diff --git a/src/logger/index.tsx b/src/logger/index.tsx index a503d9bce0..0209c34271 100644 --- a/src/logger/index.tsx +++ b/src/logger/index.tsx @@ -1,7 +1,6 @@ import {nanoid} from 'nanoid/non-secure' import {add} from '#/logger/logDump' -import {type Metrics, metrics} from '#/logger/metrics' import {consoleTransport} from '#/logger/transports/console' import {sentryTransport} from '#/logger/transports/sentry' import { @@ -11,9 +10,9 @@ import { type Transport, } from '#/logger/types' import {enabledLogLevels} from '#/logger/util' +import {type Events as Metrics} from '#/analytics/metrics/types' import {ENV} from '#/env' - -export {type Metrics} from '#/logger/metrics' +export {type Events as Metrics} from '#/analytics/metrics/types' const TRANSPORTS: Transport[] = (function configureTransports() { switch (ENV) { @@ -104,7 +103,6 @@ export class Logger { statsig?: boolean } = {statsig: true}, ) { - metrics.track(event, metadata) for (const transport of this.transports) { transport(LogLevel.Info, LogContext.Metric, event, metadata, Date.now()) } diff --git a/src/logger/metadata/deviceId.ts b/src/logger/metadata/deviceId.ts deleted file mode 100644 index ac50143548..0000000000 --- a/src/logger/metadata/deviceId.ts +++ /dev/null @@ -1,26 +0,0 @@ -import uuid from 'react-native-uuid' -import AsyncStorage from '@react-native-async-storage/async-storage' - -import {device} from '#/storage' - -const LEGACY_STABLE_ID = 'STATSIG_LOCAL_STORAGE_STABLE_ID' - -export async function getAndMigrateDeviceId() { - const migrated = getDeviceId() - if (migrated) return migrated - const id = (await AsyncStorage.getItem(LEGACY_STABLE_ID)) || uuid.v4() - device.set(['deviceId'], id) - return id -} - -export function getDeviceId() { - return device.get(['deviceId']) -} - -export function getDeviceIdOrThrow() { - const id = device.get(['deviceId']) - if (!id) { - throw new Error(`deviceId is not set, call getAndMigrateDeviceId first`) - } - return id -} diff --git a/src/logger/metadata/index.ts b/src/logger/metadata/index.ts deleted file mode 100644 index 5081870a37..0000000000 --- a/src/logger/metadata/index.ts +++ /dev/null @@ -1,105 +0,0 @@ -import {Platform} from 'react-native' - -import {BSKY_SERVICE} from '#/lib/constants' -import {setGrowthBookAttributes} from '#/logger/growthbook' -import {getAndMigrateDeviceId, getDeviceId} from '#/logger/metadata/deviceId' -import {getSessionId} from '#/logger/metadata/sessionId' -import * as persisted from '#/state/persisted' -import * as env from '#/env' -import {device} from '#/storage' - -export type BaseMetadata = { - deviceId: string - sessionId: string - country: string -} - -export type UserMetadata = { - did: string - isBskyPds: boolean - platform: string - appVersion: string - bundleIdentifier: string - bundleDate: number - refSrc: string - refUrl: string - appLanguage: string - contentLanguages: string[] -} - -export type Metadata = BaseMetadata & Partial - -/** - * Ensures that deviceId is set and migrated from legacy storage. Handled on - * startup in `App..tsx` - */ -export const setupDeviceId = getAndMigrateDeviceId() - -let baseMetadata: BaseMetadata = { - deviceId: getDeviceId() || 'unknown', - sessionId: getSessionId(), - country: device.get(['mergedGeolocation'])?.countryCode || 'unknown', -} -export function updateBaseMetadata( - metadata: Partial>, -) { - baseMetadata = { - ...baseMetadata, - ...metadata, - sessionId: metadata.sessionId || getSessionId(), - deviceId: getDeviceId() || 'unknown', - } - __onMetadataChange() -} -export function getBaseMetadata() { - return { - ...baseMetadata, - sessionId: getSessionId(), // may have changed - } -} - -let refSrc = '' -let refUrl = '' -if (env.IS_WEB) { - const params = new URLSearchParams(window.location.search) - refSrc = params.get('ref_src') ?? '' - refUrl = decodeURIComponent(params.get('ref_url') ?? '') -} - -let userMetadata: UserMetadata | null = null -export function setUserMetadata(account: persisted.PersistedAccount | null) { - if (account === null) { - userMetadata = null - } else { - const languagePrefs = persisted.get('languagePrefs') - userMetadata = { - did: account.did, - isBskyPds: account.service.startsWith(BSKY_SERVICE), - platform: Platform.OS, - appVersion: env.RELEASE_VERSION, - bundleIdentifier: env.BUNDLE_IDENTIFIER, - bundleDate: env.BUNDLE_DATE, - appLanguage: languagePrefs.appLanguage, - contentLanguages: languagePrefs.contentLanguages, - refSrc, - refUrl, - } - } - __onMetadataChange() -} -export function getUserMetadata() { - return userMetadata -} - -export function getMetadata(): Metadata { - return { - ...getBaseMetadata(), - ...(getUserMetadata() || {}), - } -} - -function __onMetadataChange() { - const metadata = getMetadata() - setGrowthBookAttributes(metadata) -} -__onMetadataChange() diff --git a/src/logger/metadata/sessionId.ts b/src/logger/metadata/sessionId.ts deleted file mode 100644 index c4494ddcf3..0000000000 --- a/src/logger/metadata/sessionId.ts +++ /dev/null @@ -1,37 +0,0 @@ -import uuid from 'react-native-uuid' - -import {onAppStateChange} from '#/lib/appState' -import {updateBaseMetadata} from '#/logger/metadata' -import {device} from '#/storage' - -const TTL = 5 * 60 * 1e3 // 5 min on native -function expired(since: number | undefined) { - if (since === undefined) return false - return Date.now() - since >= TTL -} - -let sessionId = (() => { - const existing = device.get(['nativeSessionId']) - const lastEvent = device.get(['nativeSessionIdLastEventAt']) - const id = existing && !expired(lastEvent) ? existing : uuid.v4() - device.set(['nativeSessionId'], id) - updateBaseMetadata({sessionId: id}) - return id -})() - -onAppStateChange(state => { - if (state === 'active') { - const lastEvent = device.get(['nativeSessionIdLastEventAt']) - if (expired(lastEvent)) { - sessionId = uuid.v4() - device.set(['nativeSessionId'], sessionId) - updateBaseMetadata({sessionId}) - } - } else { - device.set(['nativeSessionIdLastEventAt'], Date.now()) - } -}) - -export function getSessionId() { - return sessionId -} diff --git a/src/logger/metadata/sessionId.web.ts b/src/logger/metadata/sessionId.web.ts deleted file mode 100644 index e86a1a8760..0000000000 --- a/src/logger/metadata/sessionId.web.ts +++ /dev/null @@ -1,40 +0,0 @@ -import uuid from 'react-native-uuid' - -import {onAppStateChange} from '#/lib/appState' - -const TTL = 30 * 60 * 1e3 // 30 min on web -const SESSION_ID_KEY = 'bsky_session_id' -const LAST_EVENT_KEY = 'bsky_session_id_last_event_at' - -function expired(since: number | undefined) { - if (since === undefined) return false - return Date.now() - since >= TTL -} - -let sessionId = (() => { - const existing = window.sessionStorage.getItem(SESSION_ID_KEY) - const lastEventStr = window.sessionStorage.getItem(LAST_EVENT_KEY) - const lastEvent = lastEventStr ? Number(lastEventStr) : undefined - const id = existing && !expired(lastEvent) ? existing : uuid.v4() - window.sessionStorage.setItem(SESSION_ID_KEY, id) - // updateBaseMetadata({sessionId: id}) - return id -})() - -onAppStateChange(state => { - if (state === 'active') { - const lastEventStr = window.sessionStorage.getItem(LAST_EVENT_KEY) - const lastEvent = lastEventStr ? Number(lastEventStr) : undefined - if (expired(lastEvent)) { - sessionId = uuid.v4() - window.sessionStorage.setItem(SESSION_ID_KEY, sessionId) - // updateBaseMetadata({sessionId}) - } - } else { - window.sessionStorage.setItem(LAST_EVENT_KEY, String(Date.now())) - } -}) - -export function getSessionId() { - return sessionId -} diff --git a/src/logger/metrics/client.ts b/src/logger/metrics/client.ts deleted file mode 100644 index 911a158e85..0000000000 --- a/src/logger/metrics/client.ts +++ /dev/null @@ -1,106 +0,0 @@ -import {onAppStateChange} from '#/lib/appState' -import {isNetworkError} from '#/lib/strings/errors' -import {getGrowthBook} from '#/logger/growthbook' -import {getMetadata, type Metadata} from '#/logger/metadata' -import {type Metrics} from '#/logger/metrics/events' -import {Sentry} from '#/logger/sentry/lib' -import * as env from '#/env' - -type Event = { - time: number - event: keyof M - payload: M[keyof M] - metadata: Metadata -} - -const TRACKING_ENDPOINT = env.METRICS_API_HOST + '/track' - -export class MetricsClient { - private started: boolean = false - private queue: Event[] = [] - private failedQueue: Event[] = [] - private flushInterval: NodeJS.Timeout | null = null - - start() { - if (this.started) return - if (!getGrowthBook().ready) return - this.started = true - this.flushInterval = setInterval(() => { - this.flush() - }, 10_000) - onAppStateChange(state => { - if (state === 'active') { - this.retryFailedLogs() - } else { - this.flush() - } - }) - } - - track(event: E, payload: Metrics[E]) { - this.start() - - this.queue.push({ - time: Date.now(), - event, - payload, - metadata: getMetadata(), - }) - - if (this.queue.length > 100) { - this.flush() - } - } - - flush() { - if (!this.queue.length) return - const events = this.queue.splice(0, this.queue.length) - this.queue = [] - this.sendBatch(events) - } - - private async sendBatch(events: Event[], isRetry: boolean = false) { - try { - const body = JSON.stringify(events) - if (env.IS_WEB && 'navigator' in globalThis && navigator.sendBeacon) { - navigator.sendBeacon( - TRACKING_ENDPOINT, - new Blob([body], {type: 'application/json'}), - ) - } else { - const res = await fetch(TRACKING_ENDPOINT, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(events), - keepalive: true, - }) - - if (!res.ok) { - const error = await res.text().catch(() => 'Unknown error') - // construct a "network error" for `isNetworkError` to work - throw new Error(`${res.status} Failed to fetch — ${error}`) - } - } - } catch (e: any) { - if (isNetworkError(e)) { - if (isRetry) return // retry once - this.failedQueue.push(...events) - return - } - Sentry.captureException(`Failed to send metrics`, { - extra: { - safeMessage: e.toString(), - }, - }) - } - } - - private retryFailedLogs() { - if (!this.failedQueue.length) return - const events = this.failedQueue.splice(0, this.failedQueue.length) - this.failedQueue = [] - this.sendBatch(events, true) - } -} diff --git a/src/logger/metrics/events.ts b/src/logger/metrics/events.ts deleted file mode 100644 index b1c5b11fcf..0000000000 --- a/src/logger/metrics/events.ts +++ /dev/null @@ -1,826 +0,0 @@ -import {type NotificationReason} from '#/lib/hooks/useNotificationHandler' -import {type FeedDescriptor} from '#/state/queries/post-feed' -import {type LiveEventFeedMetricContext} from '#/features/liveEvents/types' - -export type Metrics = { - // App events - init: { - initMs: number - } - 'experiment:viewed': { - experimentId: string - variationId: string - } - - 'account:loggedIn': { - logContext: - | 'LoginForm' - | 'SwitchAccount' - | 'ChooseAccountForm' - | 'Settings' - | 'Notification' - withPassword: boolean - } - 'account:loggedOut': { - logContext: - | 'SwitchAccount' - | 'Settings' - | 'SignupQueued' - | 'Deactivated' - | 'Takendown' - | 'AgeAssuranceNoAccessScreen' - scope: 'current' | 'every' - } - 'notifications:openApp': { - reason: NotificationReason - causedBoot: boolean - } - 'notifications:request': { - context: 'StartOnboarding' | 'AfterOnboarding' | 'Login' | 'Home' - status: 'granted' | 'denied' | 'undetermined' - } - 'state:background': { - secondsActive: number - } - 'state:foreground': {} - 'router:navigate': { - from?: string - } - 'deepLink:referrerReceived': { - to: string - referrer: string - hostname: string - } - - // Screen events - 'splash:signInPressed': {} - 'splash:createAccountPressed': {} - 'welcomeModal:signupClicked': {} - 'welcomeModal:exploreClicked': {} - 'welcomeModal:signinClicked': {} - 'welcomeModal:dismissed': {} - 'welcomeModal:presented': {} - 'signup:nextPressed': { - activeStep: number - phoneVerificationRequired?: boolean - } - 'signup:backPressed': { - activeStep: number - } - 'signup:captchaSuccess': {} - 'signup:captchaFailure': {} - 'signup:fieldError': { - field: string - errorCount: number - errorMessage: string - activeStep: number - } - 'signup:backgrounded': { - activeStep: number - backgroundCount: number - } - 'signup:handleTaken': {typeahead?: boolean} - 'signup:handleAvailable': {typeahead?: boolean} - 'signup:handleSuggestionSelected': {method: string} - 'signin:hostingProviderPressed': { - hostingProviderDidChange: boolean - } - 'signin:hostingProviderFailedResolution': {} - 'signin:success': { - failedAttemptsCount: number - isUsingCustomProvider: boolean - timeTakenSeconds: number - } - 'signin:backPressed': { - failedAttemptsCount: number - } - 'signin:forgotPasswordPressed': {} - 'signin:passwordReset': {} - 'signin:passwordResetSuccess': {} - 'signin:passwordResetFailure': {} - 'onboarding:interests:nextPressed': { - selectedInterests: string[] - selectedInterestsLength: number - } - 'onboarding:suggestedAccounts:tabPressed': { - tab: string - } - 'onboarding:suggestedAccounts:followAllPressed': { - tab: string - numAccounts: number - } - 'onboarding:suggestedAccounts:nextPressed': { - selectedAccountsLength: number - skipped: boolean - } - 'onboarding:followingFeed:nextPressed': {} - 'onboarding:algoFeeds:nextPressed': { - selectedPrimaryFeeds: string[] - selectedPrimaryFeedsLength: number - selectedSecondaryFeeds: string[] - selectedSecondaryFeedsLength: number - } - 'onboarding:topicalFeeds:nextPressed': { - selectedFeeds: string[] - selectedFeedsLength: number - } - 'onboarding:moderation:nextPressed': {} - 'onboarding:profile:nextPressed': {} - 'onboarding:finished:nextPressed': { - usedStarterPack: boolean - starterPackName?: string - starterPackCreator?: string - starterPackUri?: string - profilesFollowed: number - feedsPinned: number - } - 'onboarding:finished:avatarResult': { - avatarResult: 'default' | 'created' | 'uploaded' - } - 'onboarding:valueProp:stepOne:nextPressed': {} - 'onboarding:valueProp:stepTwo:nextPressed': {} - 'onboarding:valueProp:skipPressed': {} - 'home:feedDisplayed': { - feedUrl: string - feedType: string - index: number - } - 'feed:endReached': { - feedUrl: string - feedType: string - itemCount: number - } - 'feed:refresh': { - feedUrl: string - feedType: string - reason: 'pull-to-refresh' | 'soft-reset' | 'load-latest' - } - 'feed:save': { - feedUrl: string - } - 'feed:unsave': { - feedUrl: string - } - 'feed:pin': { - feedUrl: string - } - 'feed:unpin': { - feedUrl: string - } - 'feed:like': { - feedUrl: string - } - 'feed:unlike': { - feedUrl: string - } - 'feed:share': { - feedUrl: string - } - 'feed:suggestion:seen': { - feedUrl: string - } - 'feed:suggestion:press': { - feedUrl: string - } - 'post:showMore': { - uri: string - authorDid: string - logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - feedDescriptor?: string - position?: number - } - 'post:showLess': { - uri: string - authorDid: string - logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - feedDescriptor?: string - position?: number - } - 'feed:clickthrough': { - feed: string - count: number - } - 'feed:engaged': { - feed: string - count: number - } - 'feed:seen': { - feed: string - count: number - } - - 'feed:discover:emptyError': { - userDid: string - } - - 'composer:gif:open': {} - 'composer:gif:select': {} - 'composerPrompt:press': {} - 'composerPrompt:camera:press': {} - 'composerPrompt:gallery:press': {} - - 'composer:threadgate:open': { - nudged: boolean - } - 'composer:threadgate:save': { - replyOptions: string - quotesEnabled: boolean - persist: boolean - hasChanged: boolean - } - - // Data events - 'account:create:begin': {} - 'account:create:success': { - signupDuration: number - fieldErrorsTotal: number - backgroundCount: number - } - 'post:create': { - imageCount: number - isReply: boolean - isPartOfThread: boolean - hasLink: boolean - hasQuote: boolean - langs: string - logContext: 'Composer' - } - 'thread:create': { - postCount: number - isReply: boolean - } - 'post:like': { - uri: string - authorDid: string - doesLikerFollowPoster: boolean | undefined - doesPosterFollowLiker: boolean | undefined - likerClout: number | undefined - postClout: number | undefined - logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - feedDescriptor?: string - } - 'post:repost': { - uri: string - authorDid: string - logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - feedDescriptor?: string - } - 'post:unlike': { - uri: string - authorDid: string - logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - feedDescriptor?: string - } - 'post:unrepost': { - uri: string - authorDid: string - logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - feedDescriptor?: string - } - 'post:mute': { - uri: string - authorDid: string - logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - feedDescriptor?: string - position?: number - } - 'post:unmute': { - uri: string - authorDid: string - logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - feedDescriptor?: string - position?: number - } - 'post:pin': {} - 'post:unpin': {} - 'post:bookmark': { - uri: string - authorDid: string - logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - feedDescriptor?: string - position?: number - } - 'post:unbookmark': { - uri: string - authorDid: string - logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - feedDescriptor?: string - position?: number - } - 'post:clickReply': { - uri: string - authorDid: string - logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - feedDescriptor?: string - position?: number - } - 'post:clickQuotePost': { - uri: string - authorDid: string - logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - feedDescriptor?: string - position?: number - } - 'post:clickthroughAuthor': { - uri: string - authorDid: string - logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - feedDescriptor?: string - position?: number - } - 'post:clickthroughItem': { - uri: string - authorDid: string - logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - feedDescriptor?: string - position?: number - } - 'post:clickthroughEmbed': { - uri: string - authorDid: string - logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - feedDescriptor?: string - position?: number - } - 'post:view': { - uri: string - authorDid: string - logContext: - | 'FeedItem' - | 'PostThreadItem' - | 'Post' - | 'ImmersiveVideo' - | 'SearchResults' - | 'Bookmarks' - | 'Notifications' - | 'Hashtag' - | 'Topic' - | 'PostQuotes' - feedDescriptor?: string - position?: number - } - 'bookmarks:view': {} - 'bookmarks:post-clicked': {} - 'profile:follow': { - contextProfileDid?: string - didBecomeMutual: boolean | undefined - followeeClout: number | undefined - followeeDid: string - followerClout: number | undefined - position?: number - logContext: - | 'RecommendedFollowsItem' - | 'PostThreadItem' - | 'ProfileCard' - | 'ProfileHeader' - | 'ProfileHeaderSuggestedFollows' - | 'ProfileMenu' - | 'ProfileHoverCard' - | 'AvatarButton' - | 'StarterPackProfilesList' - | 'FeedInterstitial' - | 'PostOnboardingFindFollows' - | 'ImmersiveVideo' - | 'ExploreSuggestedAccounts' - | 'OnboardingSuggestedAccounts' - | 'FindContacts' - } - 'profile:followers:view': { - contextProfileDid: string - isOwnProfile: boolean - } - 'profile:followers:paginate': { - contextProfileDid: string - itemCount: number - page: number - } - 'profile:following:view': { - contextProfileDid: string - isOwnProfile: boolean - } - 'profile:following:paginate': { - contextProfileDid: string - itemCount: number - page: number - } - 'profileCard:seen': { - contextProfileDid?: string - profileDid: string - position?: number - } - 'suggestedUser:follow': { - logContext: - | 'Explore' - | 'InterstitialDiscover' - | 'InterstitialProfile' - | 'Profile' - | 'Onboarding' - location: 'Card' | 'Profile' - recId?: number - position: number - suggestedDid: string - category: string | null - } - 'suggestedUser:press': { - logContext: - | 'Explore' - | 'InterstitialDiscover' - | 'InterstitialProfile' - | 'Onboarding' - recId?: number - position: number - suggestedDid: string - category: string | null - } - 'suggestedUser:seen': { - logContext: - | 'Explore' - | 'InterstitialDiscover' - | 'InterstitialProfile' - | 'Profile' - | 'Onboarding' - | 'ProgressGuide' - recId?: number - position: number - suggestedDid: string - category: string | null - } - 'suggestedUser:seeMore': { - logContext: - | 'Explore' - | 'InterstitialDiscover' - | 'InterstitialProfile' - | 'Profile' - | 'Onboarding' - } - 'suggestedUser:dismiss': { - logContext: 'InterstitialDiscover' | 'InterstitialProfile' - recId?: number - position: number - suggestedDid: string - } - 'profile:unfollow': { - logContext: - | 'RecommendedFollowsItem' - | 'PostThreadItem' - | 'ProfileCard' - | 'ProfileHeader' - | 'ProfileHeaderSuggestedFollows' - | 'ProfileMenu' - | 'ProfileHoverCard' - | 'Chat' - | 'AvatarButton' - | 'StarterPackProfilesList' - | 'FeedInterstitial' - | 'PostOnboardingFindFollows' - | 'ImmersiveVideo' - | 'ExploreSuggestedAccounts' - | 'OnboardingSuggestedAccounts' - | 'FindContacts' - } - 'chat:create': { - logContext: 'ProfileHeader' | 'NewChatDialog' | 'SendViaChatDialog' - } - 'chat:open': { - logContext: - | 'ProfileHeader' - | 'NewChatDialog' - | 'ChatsList' - | 'SendViaChatDialog' - } - 'starterPack:addUser': { - starterPack?: string - } - 'starterPack:removeUser': { - starterPack?: string - } - 'starterPack:share': { - starterPack: string - shareType: 'link' | 'qrcode' - qrShareType?: 'save' | 'copy' | 'share' - } - 'starterPack:followAll': { - logContext: 'StarterPackProfilesList' | 'Onboarding' - starterPack: string - count: number - } - 'starterPack:delete': {} - 'starterPack:create': { - setName: boolean - setDescription: boolean - profilesCount: number - feedsCount: number - } - 'starterPack:ctaPress': { - starterPack: string - } - 'starterPack:opened': { - starterPack: string - } - 'link:clicked': { - url: string - domain: string - } - - 'feed:interstitial:feedCard:press': {} - 'desktopFeeds:feed:click': { - feedUri: string - feedDescriptor: string - } - - 'profile:header:suggestedFollowsCard:press': {} - 'profile:addToStarterPack': {} - - 'test:all:always': {} - 'test:all:sometimes': {} - 'test:all:boosted_by_gate1': {reason: 'base' | 'gate1'} - 'test:all:boosted_by_gate2': {reason: 'base' | 'gate2'} - 'test:all:boosted_by_both': {reason: 'base' | 'gate1' | 'gate2'} - 'test:gate1:always': {} - 'test:gate1:sometimes': {} - 'test:gate2:always': {} - 'test:gate2:sometimes': {} - - 'tmd:share': {} - 'tmd:download': {} - 'tmd:post': {} - - 'trendingTopics:show': { - context: 'settings' - } - 'trendingTopics:hide': { - context: 'settings' | 'sidebar' | 'interstitial' | 'explore:trending' - } - 'trendingTopic:click': { - context: 'sidebar' | 'interstitial' | 'explore' - } - 'recommendedTopic:click': { - context: 'explore' - } - 'trendingVideos:show': { - context: 'settings' - } - 'trendingVideos:hide': { - context: 'settings' | 'interstitial:discover' | 'interstitial:explore' - } - 'videoCard:click': { - context: 'interstitial:discover' | 'interstitial:explore' | 'feed' - } - - 'explore:module:seen': { - module: - | 'trendingTopics' - | 'trendingVideos' - | 'suggestedAccounts' - | 'suggestedFeeds' - | 'suggestedStarterPacks' - | `feed:${FeedDescriptor}` - } - 'explore:module:searchButtonPress': { - module: 'suggestedAccounts' | 'suggestedFeeds' - } - 'explore:suggestedAccounts:tabPressed': { - tab: string - } - - 'progressGuide:hide': {} - 'progressGuide:followDialog:open': {} - - 'moderation:subscribedToLabeler': {} - 'moderation:unsubscribedFromLabeler': {} - 'moderation:changeLabelPreference': { - preference: string - } - - 'moderation:subscribedToList': { - listType: 'mute' | 'block' - } - 'moderation:unsubscribedFromList': { - listType: 'mute' | 'block' - } - - 'reportDialog:open': { - subjectType: string - } - 'reportDialog:close': {} - 'reportDialog:success': { - reason: string - labeler: string - details: boolean - } - 'reportDialog:failure': {} - - translate: { - sourceLanguages: string[] - targetLanguage: string - textLength: number - } - - 'verification:create': {} - 'verification:revoke': {} - 'verification:badge:click': {} - 'verification:learn-more': { - location: - | 'initialAnnouncementeNux' - | 'verificationsDialog' - | 'verifierDialog' - | 'verificationSettings' - } - 'verification:settings:hideBadges': {} - 'verification:settings:unHideBadges': {} - - 'live:create': {duration: number} - 'live:edit': {} - 'live:remove': {} - 'live:card:open': {subject: string; from: 'post' | 'profile'} - 'live:card:watch': {subject: string} - 'live:card:openProfile': {subject: string} - 'live:view:profile': {subject: string} - 'live:view:post': {subject: string; feed?: string} - - 'post:share': { - uri: string - authorDid: string - logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - feedDescriptor?: string - postContext: 'feed' | 'thread' - position?: number - } - 'share:press:copyLink': {} - 'share:press:nativeShare': {} - 'share:press:openDmSearch': {} - 'share:press:dmSelected': {} - 'share:press:recentDm': {} - 'share:press:embed': {} - - 'thread:click:showOtherReplies': {} - 'thread:click:hideReplyForMe': {} - 'thread:click:hideReplyForEveryone': {} - 'thread:preferences:load': { - [key: string]: any - } - 'thread:preferences:update': { - [key: string]: any - } - 'thread:click:headerMenuOpen': {} - 'thread:click:editOwnThreadgate': {} - 'thread:click:viewSomeoneElsesThreadgate': {} - 'activitySubscription:enable': { - setting: 'posts' | 'posts_and_replies' - } - 'activitySubscription:disable': {} - 'activityPreference:changeChannels': { - name: string - push: boolean - list: boolean - } - 'activityPreference:changeFilter': { - name: string - value: string - } - - 'ageAssurance:navigateToSettings': {} - 'ageAssurance:dismissFeedBanner': {} - 'ageAssurance:dismissSettingsNotice': {} - 'ageAssurance:initDialogOpen': { - hasInitiatedPreviously: boolean - } - 'ageAssurance:initDialogSubmit': {} - 'ageAssurance:api:begin': { - platform: string - countryCode: string - regionCode?: string - } - 'ageAssurance:initDialogError': { - code: string - } - 'ageAssurance:redirectDialogOpen': {} - 'ageAssurance:redirectDialogSuccess': {} - 'ageAssurance:redirectDialogFail': {} - 'ageAssurance:appealDialogOpen': {} - 'ageAssurance:appealDialogSubmit': {} - 'ageAssurance:noAccessScreen:shown': { - accountCreatedAt: string - isAARegion: boolean - hasDeclaredAge: boolean - canUpdateBirthday: boolean - } - 'ageAssurance:noAccessScreen:openBirthdateDialog': {} - - /* - * Specifically for the `BlockedGeoOverlay` - */ - 'blockedGeoOverlay:shown': {} - - 'geo:debug': {} - - /* - * Find Contacts stuff - */ - - // user presses the button on the new feature NUX - 'contacts:nux:ctaPressed': {} - // user presses the banner NUX - 'contacts:nux:bannerPressed': {} - // user dismisses the banner - 'contacts:nux:bannerDismissed': {} - - // user lands on the contacts step - 'onboarding:contacts:presented': {} - // user pressed "Import Contacts" button to begin flow - 'onboarding:contacts:begin': {} - // skips the step entirely - 'onboarding:contacts:skipPressed': {} - // user shared their contacts - 'onboarding:contacts:contactsShared': {} - // user leaves the matches page - 'onboarding:contacts:nextPressed': { - matchCount: number - followCount: number - dismissedMatchCount: number - } - - // user entered a number - 'contacts:phone:phoneEntered': { - entryPoint: 'Onboarding' | 'Standalone' - } - // user entered the correct one-time-code - 'contacts:phone:phoneVerified': { - entryPoint: 'Onboarding' | 'Standalone' - } - // user responded to the contacts permission prompt - 'contacts:permission:request': { - status: 'granted' | 'denied' - accessLevelIOS?: 'all' | 'limited' | 'none' - } - // contacts were successfully imported and matched - 'contacts:import:success': { - contactCount: number - matchCount: number - entryPoint: 'Onboarding' | 'Standalone' - } - // contacts import failed - 'contacts:import:failure': { - reason: 'noValidNumbers' | 'networkError' | 'unknown' - entryPoint: 'Onboarding' | 'Standalone' - } - // user followed a single match - 'contacts:matches:follow': { - entryPoint: 'Onboarding' | 'Standalone' - } - // user pressed "Follow All" on matches - 'contacts:matches:followAll': { - followCount: number - entryPoint: 'Onboarding' | 'Standalone' - } - // user dismissed a match - 'contacts:matches:dismiss': { - entryPoint: 'Onboarding' | 'Standalone' - } - // user pressed invite to send an SMS to a non-match - 'contacts:matches:invite': { - entryPoint: 'Onboarding' | 'Standalone' - } - // user opened the Find Contacts settings screen - 'contacts:settings:presented': { - hasPreviouslySynced: boolean - matchCount?: number - } - // user followed a single match from settings - 'contacts:settings:follow': {} - // user pressed "Follow All" from settings - 'contacts:settings:followAll': { - followCount: number - } - // user dismissed a match from settings - 'contacts:settings:dismiss': {} - // user re-entered the flow via the resync button - 'contacts:settings:resync': { - daysSinceLastSync: number - } - // user pressed the remove all data button - 'contacts:settings:removeData': {} - - 'liveEvents:feedBanner:seen': { - feed: string - context: LiveEventFeedMetricContext - } - 'liveEvents:feedBanner:click': { - feed: string - context: LiveEventFeedMetricContext - } - 'liveEvents:feedBanner:hide': { - feed: string - context: LiveEventFeedMetricContext - } - 'liveEvents:feedBanner:unhide': { - feed: string - context: LiveEventFeedMetricContext - } - 'liveEvents:hideAllFeedBanners': { - context: LiveEventFeedMetricContext - } - 'liveEvents:unhideAllFeedBanners': { - context: LiveEventFeedMetricContext - } -} diff --git a/src/logger/metrics/index.ts b/src/logger/metrics/index.ts deleted file mode 100644 index 74e4774857..0000000000 --- a/src/logger/metrics/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {getCurrentState, onAppStateChange} from '#/lib/appState' -import {MetricsClient} from '#/logger/metrics/client' - -export {type Metrics} from '#/logger/metrics/events' - -/** - * Active metrics client - */ -export const metrics = new MetricsClient() - -/** - * Passive metrics go here - */ - -let lastActive = getCurrentState() === 'active' ? performance.now() : null -onAppStateChange(state => { - if (state === 'active') { - lastActive = performance.now() - metrics.track('state:foreground', {}) - } else if (lastActive !== null) { - metrics.track('state:background', { - secondsActive: Math.round((performance.now() - lastActive) / 1e3), - }) - } -}) diff --git a/src/state/session/agent.ts b/src/state/session/agent.ts index 515df62a9d..fb8a1165a4 100644 --- a/src/state/session/agent.ts +++ b/src/state/session/agent.ts @@ -24,7 +24,6 @@ import { } from '#/lib/constants' import {getAge} from '#/lib/strings/time' import {logger} from '#/logger' -import {setUserMetadata} from '#/logger/metadata' import {snoozeBirthdateUpdateAllowedForDid} from '#/state/birthdate' import {snoozeEmailConfirmationPrompt} from '#/state/shell/reminders' import { @@ -64,7 +63,6 @@ export async function createAgentAndResume( if (storedAccount.pdsUrl) { agent.sessionManager.pdsUrl = new URL(storedAccount.pdsUrl) } - setUserMetadata(storedAccount) const gates = features.refresh({ strategy: 'prefer-low-latency', }) @@ -127,7 +125,6 @@ export async function createAgentAndLogin( }) const account = agentToSessionAccountOrThrow(agent) - setUserMetadata(account) const gates = features.refresh({strategy: 'prefer-fresh-gates'}) const moderation = configureModerationForAccount(agent, account) const aa = prefetchAgeAssuranceData({agent}) @@ -176,7 +173,6 @@ export async function createAgentAndCreateAccount( verificationCode, }) const account = agentToSessionAccountOrThrow(agent) - setUserMetadata(account) const gates = features.refresh({strategy: 'prefer-fresh-gates'}) const moderation = configureModerationForAccount(agent, account)