diff --git a/mirror-2/.env.example b/mirror-2/.env.example index df846c90..9dc553d7 100644 --- a/mirror-2/.env.example +++ b/mirror-2/.env.example @@ -4,3 +4,5 @@ NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321 NEXT_PUBLIC_SUPABASE_ANON_KEY=SUPABASE_CLIENT_API_KEY NEXT_PUBLIC_APP_NAME="The Mirror" # only use The Mirror or Reflekt NEXT_PUBLIC_DISCORD_INVITE_URL=https://themirror.space/discord +NEXT_PUBLIC_GA_KEY_GAME_PUBLIC= +NEXT_PUBLIC_GA_KEY_SECRET= # I asked about this to GA support; why secret public?? Using throwaway keys for now diff --git a/mirror-2/app/layout.tsx b/mirror-2/app/layout.tsx index 60ad36f6..2dbb172e 100644 --- a/mirror-2/app/layout.tsx +++ b/mirror-2/app/layout.tsx @@ -3,10 +3,12 @@ import { Montserrat } from 'next/font/google' import "./globals.css"; import { Metadata } from "next"; import { appName, appDescription, faviconPath } from "@/lib/theme-service"; +import Analytics from "@/utils/analytics/analytics"; export const metadata: Metadata = { title: appName(), description: appDescription(), } + const montserrat = Montserrat({ subsets: ['latin'], display: 'swap', @@ -20,6 +22,7 @@ export default function RootLayout({ + {children} diff --git a/mirror-2/package.json b/mirror-2/package.json index eafd203f..320ba1b2 100644 --- a/mirror-2/package.json +++ b/mirror-2/package.json @@ -1,4 +1,5 @@ { + "version": "0.0.1", "private": true, "scripts": { "dev": "next dev & supabase start", @@ -32,6 +33,7 @@ "autoprefixer": "10.4.17", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "gameanalytics": "^4.4.7", "geist": "^1.2.1", "lucide-react": "^0.446.0", "next": "latest", diff --git a/mirror-2/state/local.tsx b/mirror-2/state/local.tsx index 8044ca89..6f73d8ff 100644 --- a/mirror-2/state/local.tsx +++ b/mirror-2/state/local.tsx @@ -1,7 +1,8 @@ import { RootState } from '@/state/store'; +import { setAnalyticsUserId } from '@/utils/analytics/analytics'; import { Database } from '@/utils/database.types'; import type { PayloadAction } from '@reduxjs/toolkit'; -import { createSlice } from '@reduxjs/toolkit'; +import { createListenerMiddleware, createSlice } from '@reduxjs/toolkit'; export type ControlBarView = "assets" | "hierarchy" | "scenes" | "code" | "database" | "versions" | "settings"; @@ -106,6 +107,16 @@ export const { insertAutomaticallyExpandedSceneIds } = localSlice.actions; +// Middleware +export const listenerMiddlewareLocal = createListenerMiddleware() +listenerMiddlewareLocal.startListening({ + actionCreator: updateLocalUserState, + effect: async (action, listenerApi) => { + setAnalyticsUserId(action.payload.id) + } +}) + + // Selectors export const selectUiSoundsCanPlay = (state: RootState) => state.local.uiSoundsCanPlay; export const selectControlBarCurrentView = (state: RootState) => state.local.controlBarCurrentView; diff --git a/mirror-2/state/store.tsx b/mirror-2/state/store.tsx index 3b6b1111..d005774e 100644 --- a/mirror-2/state/store.tsx +++ b/mirror-2/state/store.tsx @@ -1,7 +1,7 @@ "use client" import { configureStore } from '@reduxjs/toolkit' import { setupListeners } from '@reduxjs/toolkit/query/react' -import { localSlice } from '@/state/local' +import { listenerMiddlewareLocal, localSlice } from '@/state/local' import { spacesApi } from '@/state/spaces' import { scenesApi } from '@/state/scenes' import { entitiesApi } from '@/state/entities' @@ -27,6 +27,7 @@ export const store = configureStore({ .concat(scenesApi.middleware) .concat(entitiesApi.middleware) .concat(componentsApi.middleware) + .concat(listenerMiddlewareLocal.middleware) }) // optional, but required for refetchOnFocus/refetchOnReconnect behaviors diff --git a/mirror-2/utils/analytics/analytics.tsx b/mirror-2/utils/analytics/analytics.tsx new file mode 100644 index 00000000..8e6fed20 --- /dev/null +++ b/mirror-2/utils/analytics/analytics.tsx @@ -0,0 +1,21 @@ +"use client" +import { useEffect } from 'react'; +import packageJson from '../../package.json'; +import { GameAnalytics } from 'gameanalytics' + +export function setAnalyticsUserId(userId: string) { + GameAnalytics("configureUserId", userId); +} + +const Analytics = function () { + useEffect(() => { + if (typeof window !== 'undefined') { + GameAnalytics("configureBuild", `web ${packageJson.version}`); + GameAnalytics("initialize", process.env.NEXT_PUBLIC_GA_KEY_GAME_PUBLIC, process.env.NEXT_PUBLIC_GA_KEY_SECRET); + } + }, []) + + return <> +} + +export default Analytics diff --git a/mirror-2/yarn.lock b/mirror-2/yarn.lock index 8b20e695..39c6d8d2 100644 --- a/mirror-2/yarn.lock +++ b/mirror-2/yarn.lock @@ -1368,6 +1368,11 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +gameanalytics@^4.4.7: + version "4.4.7" + resolved "https://registry.yarnpkg.com/gameanalytics/-/gameanalytics-4.4.7.tgz#f619861acb499a61beea4c65bc650db1246ebe5d" + integrity sha512-yXC6+j/OtkjRtrA1J2E3kVUDR8bdUm25QUzee1JPSTMOajbFV5zMg4+3kFJxhTvDVpj6qkO9QvHBrQCMNWLPVQ== + geist@^1.2.1: version "1.3.1" resolved "https://registry.yarnpkg.com/geist/-/geist-1.3.1.tgz#bbd95db23b2a00baf6020e3b1b63a5752f4787d2"