···18 return sessionId
19}
2021+/**
22+ * Gets the current session ID. Freshness depends on `useSessionId` being
23+ * mounted, which handles refreshing this value between foreground/background
24+ * transitions. Since that's mounted in `analytics/index.tsx`, this value can
25+ * generally be trusted to be up to date.
26+ */
27+export function getSessionId() {
28+ return device.get(['nativeSessionId'])
29+}
30+31export function useSessionId() {
32 const [id, setId] = useState(() => sessionId)
33
···21 return sessionId
22}
2324+/**
25+ * Gets the current session ID. Freshness depends on `useSessionId` being
26+ * mounted, which handles refreshing this value between foreground/background
27+ * transitions. Since that's mounted in `analytics/index.tsx`, this value can
28+ * generally be trusted to be up to date.
29+ */
30+export function getSessionId() {
31+ return window.sessionStorage.getItem(SESSION_ID_KEY)
32+}
33+34export function useSessionId() {
35 const [id, setId] = useState(() => sessionId)
36
+20-15
src/analytics/index.tsx
···1-import {createContext, useContext, useEffect, useMemo} from 'react'
2import {Platform} from 'react-native'
34import {Logger} from '#/logger'
···24import {type Metrics, metrics} from '#/analytics/metrics'
25import * as refParams from '#/analytics/misc/refParams'
26import * as env from '#/env'
27-import {useGeolocation} from '#/geolocation'
28import {device} from '#/storage'
2930export * as utils from '#/analytics/utils'
···104 referrerSrc: refParams.src,
105 referrerUrl: refParams.url,
106 },
107- geolocation: device.get(['mergedGeolocation']) || {
108 countryCode: '',
109 regionCode: '',
110 },
···137 }
138 }
139 const sessionId = useSessionId()
140- const geolocation = useGeolocation()
141 const parentContext = useContext(Context)
142 const childContext = useMemo(() => {
143 const combinedMetadata = {
···181 const parentContext = useContext(Context)
182183 /**
184- * Side-effect: we need to synchronously set this during the
185- * same render cycle. It does not trigger a re-render, it just
186- * sets properties on the singleton GrowthBook instance.
187 */
188 setAttributes(parentContext.metadata)
189-190- useEffect(() => {
191- feats.setTrackingCallback((experiment, result) => {
192- parentContext.metric('experiment:viewed', {
193- experimentId: experiment.key,
194- variationId: result.key,
195- })
00000196 })
197- }, [parentContext.metric])
198199 const childContext = useMemo<AnalyticsContextType>(() => {
200 return {
···1+import {createContext, useContext, useMemo} from 'react'
2import {Platform} from 'react-native'
34import {Logger} from '#/logger'
···24import {type Metrics, metrics} from '#/analytics/metrics'
25import * as refParams from '#/analytics/misc/refParams'
26import * as env from '#/env'
27+import {useGeolocationServiceResponse} from '#/geolocation/service'
28import {device} from '#/storage'
2930export * as utils from '#/analytics/utils'
···104 referrerSrc: refParams.src,
105 referrerUrl: refParams.url,
106 },
107+ geolocation: device.get(['geolocationServiceResponse']) || {
108 countryCode: '',
109 regionCode: '',
110 },
···137 }
138 }
139 const sessionId = useSessionId()
140+ const geolocation = useGeolocationServiceResponse()
141 const parentContext = useContext(Context)
142 const childContext = useMemo(() => {
143 const combinedMetadata = {
···181 const parentContext = useContext(Context)
182183 /**
184+ * Side-effects: we need to synchronously set these during the same render
185+ * cycle. These calls do not trigger re-renders, they just set properties on
186+ * the singleton GrowthBook instance.
187 */
188 setAttributes(parentContext.metadata)
189+ feats.setTrackingCallback((experiment, result) => {
190+ parentContext.metric('experiment:viewed', {
191+ experimentId: experiment.key,
192+ variationId: result.key,
193+ })
194+ })
195+ feats.setFeatureUsageCallback((feature, result) => {
196+ parentContext.metric('feature:viewed', {
197+ featureId: feature,
198+ featureResultValue: result.value,
199+ experimentId: result.experiment?.key,
200+ variationId: result.experimentResult?.key,
201 })
202+ })
203204 const childContext = useMemo<AnalyticsContextType>(() => {
205 return {
+3-1
src/analytics/metrics/client.ts
···4import * as env from '#/env'
56type Event<M extends Record<string, any>> = {
07 time: number
8 event: keyof M
9 payload: M[keyof M]
···43 ) {
44 this.start()
4546- const e = {
047 time: Date.now(),
48 event,
49 payload,
···15 experimentId: string
16 variationId: string
17 }
18+ 'feature:viewed': {
19+ featureId: string
20+ featureResultValue: unknown
21+ /** Only available if feature has experiment rules applied */
22+ experimentId?: string
23+ /** Only available if feature has experiment rules applied */
24+ variationId?: string
25+ }
2627 'account:loggedIn': {
28 logContext:
+24
src/geolocation/util.ts
···3import {IS_ANDROID} from '#/env'
4import {logger} from '#/geolocation/logger'
5import {type Geolocation} from '#/geolocation/types'
067/**
8 * Maps full US region names to their short codes.
···125 })
126 return geolocation
127}
00000000000000000000000
···3import {IS_ANDROID} from '#/env'
4import {logger} from '#/geolocation/logger'
5import {type Geolocation} from '#/geolocation/types'
6+import {device} from '#/storage'
78/**
9 * Maps full US region names to their short codes.
···126 })
127 return geolocation
128}
129+130+/**
131+ * Gets the IP-based geolocation as a string in the format of
132+ * "countryCode-regionCode", or just "countryCode" if regionCode is not
133+ * available.
134+ *
135+ * IMPORTANT: this method should only return IP-based data, not the user's GPS
136+ * based data. IP-based data we can already infer from requests, but for
137+ * consistency between frontend and backend, we sometimes want to share the
138+ * value we have on the frontend with the backend.
139+ */
140+export function getIPGeolocationString() {
141+ const geo = device.get(['geolocationServiceResponse'])
142+ if (!geo) return
143+ const {countryCode, regionCode} = geo
144+ if (countryCode) {
145+ if (regionCode) {
146+ return `${countryCode}-${regionCode}`
147+ } else {
148+ return countryCode
149+ }
150+ }
151+}