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"