Skip to content

Instantly share code, notes, and snippets.

@harrisrobin
Last active August 9, 2023 10:00
Show Gist options
  • Save harrisrobin/c81445076242e0400a6cdf716a60e098 to your computer and use it in GitHub Desktop.
Save harrisrobin/c81445076242e0400a6cdf716a60e098 to your computer and use it in GitHub Desktop.
Typed & re-useable analytics module to make it easy to add new analytics tools to the app without having to touch multiple files
import * as Analytics from "expo-firebase-analytics"
import { recordError } from "./exceptions"
export const setCurrentScreen = (screenName: string): void => {
void Analytics.logEvent("screen_view", { screen_name: screenName })
// track screens.
__DEV__ && console.tron.log(`currentScreen: ${screenName}`)
}
/**
* Logs an event to various analytics services.
*/
export async function logEvent(
event: string,
params: Record<string, unknown> = {},
): Promise<void> {
__DEV__ && console.tron.log("logEvent", { event, ...params })
try {
await Promise.all([Analytics.logEvent(event, params)])
} catch (error) {
console.error("Something with wrong with Analytics.logEvent", error)
recordError(error)
}
}
/**
* Sets the user ID property. This feature must be used in accordance with Google's Privacy Policy
*/
export async function setCurrentUserId(userId: string): Promise<void> {
__DEV__ && console.tron.log("setCurrentUserId", userId)
try {
await Analytics.setUserId(userId)
} catch (error) {
console.error("Something with wrong with Analytics.setCurrentUserId", error)
recordError(error)
}
}
/**
* Clears all analytics data for this instance from the device and resets the app instance ID.
*/
export async function flush(): Promise<void> {
try {
await Analytics.resetAnalyticsData()
} catch (error) {
console.error("Something with wrong with Analytics.flush", error)
recordError(error)
}
}
import { Alert } from "react-native"
import { setJSExceptionHandler } from "react-native-exception-handler"
import { NAVIGATION_PERSISTENCE_KEY } from "../navigation/navigation-utilities"
import { translate } from "@bridge/i18n"
import { ROOT_STATE_STORAGE_KEY } from "../models/root-store/constants"
import * as storage from "./storage"
/**
* Error classifications used to sort errors on error reporting services.
*/
export enum ErrorType {
/**
* An API service related error.
*/
API,
/**
* An error that would normally cause a red screen in dev
* and forced the user to signout and restart.
*/
FATAL,
/**
* An error caught by try/catch where defined using Reactotron.tron.error.
*/
HANDLED,
}
/**
* Manually record a handled error.
*/
export const recordError = (
error: any,
type: ErrorType = ErrorType.FATAL,
): void => {
const message = error.message || "Unknown"
console.error(error)
console.log(message, type)
}
/**
* The exception handler
*/
const exceptionHandler = (error: Error, isFatal: boolean) => {
// why? what did I do?
const receivedError = error instanceof Error ? error : JSON.stringify(error)
if (isFatal) {
recordError(receivedError, ErrorType.FATAL)
// let the user know we dun goofed
Alert.alert(
translate("alerts.errors.generic"),
`${translate("alerts.errors.ourTeam")} ${error.message}`,
[
{
text: translate("common.ok"),
onPress: async () => {
await Promise.all([
storage.remove(ROOT_STATE_STORAGE_KEY),
storage.remove(NAVIGATION_PERSISTENCE_KEY),
])
},
},
],
)
}
}
/**
* Handle uncaught exceptions
*/
export const handleExceptions = (allowInDev = true): void => {
setJSExceptionHandler(exceptionHandler, allowInDev)
}
import { AsyncStorage } from "./async-storage"
import { load, loadString, save, saveString, clear, remove } from "./storage"
// fixtures
const VALUE_OBJECT = { x: 1 }
const VALUE_STRING = JSON.stringify(VALUE_OBJECT)
beforeEach(() =>
(AsyncStorage.getItem as jest.Mock).mockReturnValue(
Promise.resolve(VALUE_STRING),
),
)
afterEach(() => jest.clearAllMocks())
test("load", async () => {
const value = await load("something")
expect(value).toEqual(JSON.parse(VALUE_STRING))
})
test("loadString", async () => {
const value = await loadString("something")
expect(value).toEqual(VALUE_STRING)
})
test("save", async () => {
await save("something", VALUE_OBJECT)
expect(AsyncStorage.setItem).toHaveBeenCalledWith("something", VALUE_STRING)
})
test("saveString", async () => {
await saveString("something", VALUE_STRING)
expect(AsyncStorage.setItem).toHaveBeenCalledWith("something", VALUE_STRING)
})
test("remove", async () => {
await remove("something")
expect(AsyncStorage.removeItem).toHaveBeenCalledWith("something")
})
test("clear", async () => {
await clear()
expect(AsyncStorage.clear).toHaveBeenCalledWith()
})
/* eslint-disable no-empty */
import { AsyncStorage } from "./async-storage"
/**
* Loads a string from storage.
*
* @param key The key to fetch.
*/
export async function loadString(key: string): Promise<string | null> {
try {
return await AsyncStorage.getItem(key)
} catch {
// not sure why this would fail... even reading the RN docs I'm unclear
return null
}
}
/**
* Saves a string to storage.
*
* @param key The key to fetch.
* @param value The value to store.
*/
export async function saveString(key: string, value: string): Promise<boolean> {
try {
await AsyncStorage.setItem(key, value)
return true
} catch {
return false
}
}
/**
* Loads something from storage and runs it thru JSON.parse.
*
* @param key The key to fetch.
*/
export async function load(key: string): Promise<any | null> {
try {
const almostThere = await AsyncStorage.getItem(key)
//@ts-expect-error
return JSON.parse(almostThere)
} catch {
return null
}
}
/**
* Saves an object to storage.
*
* @param key The key to fetch.
* @param value The value to store.
*/
export async function save(key: string, value: any): Promise<boolean> {
try {
await AsyncStorage.setItem(key, JSON.stringify(value))
return true
} catch {
return false
}
}
/**
* Removes something from storage.
*
* @param key The key to kill.
*/
export async function remove(key: string): Promise<void> {
try {
await AsyncStorage.removeItem(key)
} catch {}
}
/**
* Burn it all to the ground.
*/
export async function clear(): Promise<void> {
try {
await AsyncStorage.clear()
} catch {}
}
/**
* Check if key exists in storage.
*/
export async function hasKey(key: string): Promise<boolean> {
try {
const item = await AsyncStorage.getItem(key)
return item != null
} catch {
return false
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment