diff --git a/js/package.json b/js/package.json index 95cb133..cf06440 100644 --- a/js/package.json +++ b/js/package.json @@ -7,9 +7,9 @@ "private": true, "scripts": { "start": "electron-forge start", - "package": "electron-forge package --arch=arm64 && electron-forge package --arch=x64", - "make": "electron-forge make --arch=arm64 && electron-forge make --arch=x64", - "release": "echo '\n⚠️ Have you bumped the version in package.json? (current: '$(node -p \"require('./package.json').version\")') [y/N]' && read -r answer && [ \"$answer\" = \"y\" ] && electron-forge publish --arch=arm64 && electron-forge publish --arch=x64 && gsutil setmeta -h 'Cache-Control:no-cache, no-store, must-revalidate' gs://flowy-releases/llink/darwin/arm64/RELEASES.json && gsutil setmeta -h 'Cache-Control:no-cache, no-store, must-revalidate' gs://flowy-releases/llink/darwin/x64/RELEASES.json", + "package": "APP_ENV=prod electron-forge package --arch=arm64 && APP_ENV=prod electron-forge package --arch=x64", + "make": "APP_ENV=prod electron-forge make --arch=arm64 && APP_ENV=prod electron-forge make --arch=x64", + "release": "echo '\n⚠️ Have you bumped the version in package.json? (current: '$(node -p \"require('./package.json').version\")') [y/N]' && read -r answer && [ \"$answer\" = \"y\" ] && APP_ENV=prod electron-forge publish --arch=arm64 && APP_ENV=prod electron-forge publish --arch=x64 && gsutil setmeta -h 'Cache-Control:no-cache, no-store, must-revalidate' gs://flowy-releases/llink/darwin/arm64/RELEASES.json && gsutil setmeta -h 'Cache-Control:no-cache, no-store, must-revalidate' gs://flowy-releases/llink/darwin/x64/RELEASES.json", "invalidate-gcs-cache": "gsutil -h \"Cache-Control:no-cache, no-store, max-age=0\" cp gs://flowy-releases/llink/darwin/arm64/RELEASES.json gs://flowy-releases/llink/darwin/arm64/RELEASES.json ; gsutil -h \"Cache-Control:no-cache, no-store, max-age=0\" cp gs://flowy-releases/llink/darwin/x64/RELEASES.json gs://flowy-releases/llink/darwin/x64/RELEASES.json", "lint": "eslint --ext .ts,.tsx .", "compile": "npx tsc --noEmit 2>&1 | grep '^src/'" diff --git a/js/src/api/client.ts b/js/src/api/client.ts index e1f2e98..24139ba 100644 --- a/js/src/api/client.ts +++ b/js/src/api/client.ts @@ -1,3 +1,4 @@ +import { appConfig } from "@/config/env"; import { useSessionStore } from "@/stores/session-store"; import type { z } from "zod"; import { @@ -218,7 +219,7 @@ class ApiClient { } export const apiClient = new ApiClient({ - baseUrl: "https://orion.dev.flowy.live", + baseUrl: appConfig.orionUrl, getToken: () => useSessionStore.getState().token, onUnauthorized: () => useSessionStore.getState().clearToken(), }); diff --git a/js/src/config/env.ts b/js/src/config/env.ts new file mode 100644 index 0000000..c80a415 --- /dev/null +++ b/js/src/config/env.ts @@ -0,0 +1,56 @@ +// Build-time environment selection. +// +// __APP_ENV__ is replaced by Vite via `define` in each vite.*.config.ts. Selected +// via the APP_ENV env var at build/dev time (defaults to 'dev'). `yarn package`, +// `yarn make`, and `yarn release` set APP_ENV=prod. +// +// Firebase web config is public by design (security is enforced via Firestore +// rules + App Check), so both configs live in source. To refresh, run: +// cd infra/gcp/{dev,prod} && terraform output -json firebase_config + +declare const __APP_ENV__: "dev" | "prod"; + +type FirebaseConfig = { + apiKey: string; + appId: string; + authDomain: string; + messagingSenderId: string; + projectId: string; + storageBucket: string; +}; + +type AppConfig = { + orionUrl: string; + pusherUrl: string; + firebase: FirebaseConfig; +}; + +const configs: Record<"dev" | "prod", AppConfig> = { + dev: { + orionUrl: "https://orion.dev.flowy.live", + pusherUrl: "wss://pusher.dev.flowy.live/ws", + firebase: { + apiKey: "AIzaSyDF_fbk7tDY9tNxUiOwwi--2nYZWZygMGk", + appId: "1:1006580076785:web:e2a0736d60a78e02b15950", + authDomain: "flowy-dev-440017.firebaseapp.com", + messagingSenderId: "1006580076785", + projectId: "flowy-dev-440017", + storageBucket: "flowy-dev-440017.firebasestorage.app", + }, + }, + prod: { + orionUrl: "https://orion.flowy.live", + pusherUrl: "wss://pusher.flowy.live/ws", + firebase: { + apiKey: "AIzaSyB8it3SKr9DHScHGlpu4uwWicvt8wzjdBg", + appId: "1:68063426854:web:5054f16f50898f5706e9e7", + authDomain: "flowy-prod-440017.firebaseapp.com", + messagingSenderId: "68063426854", + projectId: "flowy-prod-440017", + storageBucket: "flowy-prod-440017.firebasestorage.app", + }, + }, +}; + +export const appConfig: AppConfig = configs[__APP_ENV__]; +export const appEnv: "dev" | "prod" = __APP_ENV__; diff --git a/js/src/firebase.ts b/js/src/firebase.ts index 907ac6a..8825944 100644 --- a/js/src/firebase.ts +++ b/js/src/firebase.ts @@ -1,16 +1,8 @@ import { initializeApp } from 'firebase/app'; import { getFirestore } from "firebase/firestore"; +import { appConfig } from "@/config/env"; -const firebaseConfig = { - apiKey: "AIzaSyDF_fbk7tDY9tNxUiOwwi--2nYZWZygMGk", - appId: "1:1006580076785:web:e2a0736d60a78e02b15950", - authDomain: "flowy-dev-440017.firebaseapp.com", - messagingSenderId: "1006580076785", - projectId: "flowy-dev-440017", - storageBucket: "flowy-dev-440017.firebasestorage.app", -}; - -export const firebaseApp = initializeApp(firebaseConfig); +export const firebaseApp = initializeApp(appConfig.firebase); export const firestoreDb = getFirestore(firebaseApp); // simplifying setup to debug production issues diff --git a/js/src/lib/pusher-provider.tsx b/js/src/lib/pusher-provider.tsx index 8174d36..cf260ae 100644 --- a/js/src/lib/pusher-provider.tsx +++ b/js/src/lib/pusher-provider.tsx @@ -8,13 +8,11 @@ import { } from "react"; import { PusherClient, type ConnectionState } from "./pusher-client"; import { useSessionStore } from "@/stores/session-store"; +import { appConfig } from "@/config/env"; const PusherContext = createContext(null); const PusherStateContext = createContext("disconnected"); -// TODO: make this configurable per environment -const PUSHER_URL = "wss://pusher.dev.flowy.live/ws"; - export function PusherProvider({ children }: { children: ReactNode }) { const token = useSessionStore((s) => s.token); const clientRef = useRef(null); @@ -33,7 +31,7 @@ export function PusherProvider({ children }: { children: ReactNode }) { } const client = new PusherClient({ - url: PUSHER_URL, + url: appConfig.pusherUrl, getToken: () => useSessionStore.getState().token, }); diff --git a/js/src/main.ts b/js/src/main.ts index f1ff738..31eefdf 100644 --- a/js/src/main.ts +++ b/js/src/main.ts @@ -4,6 +4,7 @@ import started from 'electron-squirrel-startup'; import { updateElectronApp, UpdateSourceType } from 'update-electron-app'; import type { LinkMetadata } from './lib/link-metadata'; +import { appConfig } from './config/env'; if (app.isPackaged) { updateElectronApp({ @@ -484,7 +485,7 @@ app.on('ready', () => { // The server doesn't handle OPTIONS preflight, so we intercept at the // Electron network layer: inject CORS headers and return 200 for preflight. session.defaultSession.webRequest.onHeadersReceived( - { urls: ['https://orion.dev.flowy.live/*', 'https://storage.googleapis.com/*'] }, + { urls: [`${appConfig.orionUrl}/*`, 'https://storage.googleapis.com/*'] }, (details, callback) => { const headers = { ...details.responseHeaders }; headers['access-control-allow-origin'] = ['*']; diff --git a/js/vite.autoplay.config.mts b/js/vite.autoplay.config.mts index 58e0601..657c8dd 100644 --- a/js/vite.autoplay.config.mts +++ b/js/vite.autoplay.config.mts @@ -2,6 +2,7 @@ import path from "path" import tailwindcss from "@tailwindcss/vite" import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; +import { defineEnv } from './vite.env'; // https://vitejs.dev/config export default defineConfig({ @@ -20,4 +21,5 @@ export default defineConfig({ allow: [path.resolve(__dirname)], }, }, + define: defineEnv, }); diff --git a/js/vite.env.ts b/js/vite.env.ts new file mode 100644 index 0000000..69a0761 --- /dev/null +++ b/js/vite.env.ts @@ -0,0 +1,13 @@ +// Shared Vite `define` block that injects the build-time __APP_ENV__ constant +// consumed by src/config/env.ts. Sourced from process.env.APP_ENV (default 'dev'). + +const raw = process.env.APP_ENV ?? "dev"; +if (raw !== "dev" && raw !== "prod") { + throw new Error(`APP_ENV must be "dev" or "prod", got: ${raw}`); +} + +export const appEnv = raw; + +export const defineEnv = { + __APP_ENV__: JSON.stringify(appEnv), +}; diff --git a/js/vite.huddle.config.mts b/js/vite.huddle.config.mts index c0758b7..6e45ae0 100644 --- a/js/vite.huddle.config.mts +++ b/js/vite.huddle.config.mts @@ -2,6 +2,7 @@ import path from "path" import tailwindcss from "@tailwindcss/vite" import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; +import { defineEnv } from './vite.env'; // https://vitejs.dev/config export default defineConfig({ @@ -20,4 +21,5 @@ export default defineConfig({ allow: [path.resolve(__dirname)], }, }, + define: defineEnv, }); diff --git a/js/vite.main.config.ts b/js/vite.main.config.ts index 690be5b..fd869a2 100644 --- a/js/vite.main.config.ts +++ b/js/vite.main.config.ts @@ -1,4 +1,7 @@ import { defineConfig } from 'vite'; +import { defineEnv } from './vite.env'; // https://vitejs.dev/config -export default defineConfig({}); +export default defineConfig({ + define: defineEnv, +}); diff --git a/js/vite.preload.config.ts b/js/vite.preload.config.ts index 690be5b..fd869a2 100644 --- a/js/vite.preload.config.ts +++ b/js/vite.preload.config.ts @@ -1,4 +1,7 @@ import { defineConfig } from 'vite'; +import { defineEnv } from './vite.env'; // https://vitejs.dev/config -export default defineConfig({}); +export default defineConfig({ + define: defineEnv, +}); diff --git a/js/vite.renderer.config.mts b/js/vite.renderer.config.mts index 74d691e..1c6e1ef 100644 --- a/js/vite.renderer.config.mts +++ b/js/vite.renderer.config.mts @@ -2,6 +2,7 @@ import path from "path" import tailwindcss from "@tailwindcss/vite" import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; +import { defineEnv } from './vite.env'; // https://vitejs.dev/config export default defineConfig({ @@ -11,4 +12,5 @@ export default defineConfig({ "@": path.resolve(__dirname, "./src"), }, }, + define: defineEnv, }); diff --git a/js/vite.screen-record.config.mts b/js/vite.screen-record.config.mts index 34c2e62..d2edcd4 100644 --- a/js/vite.screen-record.config.mts +++ b/js/vite.screen-record.config.mts @@ -2,6 +2,7 @@ import path from "path" import tailwindcss from "@tailwindcss/vite" import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; +import { defineEnv } from './vite.env'; // https://vitejs.dev/config export default defineConfig({ @@ -20,4 +21,5 @@ export default defineConfig({ allow: [path.resolve(__dirname)], }, }, + define: defineEnv, });