@geejay/use-feature-flags 1.0.31 → 1.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +118 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +118 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -16,13 +16,60 @@ function setApiKey(key) {
|
|
|
16
16
|
function getApiKey() {
|
|
17
17
|
return apiKey;
|
|
18
18
|
}
|
|
19
|
+
|
|
20
|
+
// src/env.ts
|
|
21
|
+
var importMetaEnvGetter = null;
|
|
22
|
+
function getProcessEnv() {
|
|
23
|
+
if (typeof process === "undefined" || !process) return void 0;
|
|
24
|
+
const proc = process;
|
|
25
|
+
if (!proc.env || typeof proc.env !== "object") return void 0;
|
|
26
|
+
return proc.env;
|
|
27
|
+
}
|
|
28
|
+
function getImportMetaEnv() {
|
|
29
|
+
if (importMetaEnvGetter) return importMetaEnvGetter();
|
|
30
|
+
try {
|
|
31
|
+
const fn = new Function(
|
|
32
|
+
'return (typeof import.meta !== "undefined" && import.meta && import.meta.env) ? import.meta.env : undefined;'
|
|
33
|
+
);
|
|
34
|
+
importMetaEnvGetter = fn;
|
|
35
|
+
return fn();
|
|
36
|
+
} catch {
|
|
37
|
+
importMetaEnvGetter = () => void 0;
|
|
38
|
+
return void 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function toStringValue(value) {
|
|
42
|
+
if (typeof value === "string") return value;
|
|
43
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
44
|
+
return String(value);
|
|
45
|
+
}
|
|
46
|
+
return void 0;
|
|
47
|
+
}
|
|
48
|
+
function getEnvVar(name) {
|
|
49
|
+
var _a, _b;
|
|
50
|
+
const processValue = toStringValue((_a = getProcessEnv()) == null ? void 0 : _a[name]);
|
|
51
|
+
if (processValue !== void 0) return processValue;
|
|
52
|
+
const importMetaValue = toStringValue((_b = getImportMetaEnv()) == null ? void 0 : _b[name]);
|
|
53
|
+
if (importMetaValue !== void 0) return importMetaValue;
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
56
|
+
function isDebugEnabled() {
|
|
57
|
+
const debug = getEnvVar("DEBUG");
|
|
58
|
+
if (!debug) return false;
|
|
59
|
+
const normalized = debug.toLowerCase();
|
|
60
|
+
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "*";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/supabaseClient.ts
|
|
19
64
|
var GLOBAL_KEY = "__useff_supabase_singleton__";
|
|
20
65
|
var DEFAULT_SUPABASE_URL = "https://khppgsehvvlukzfdqbuo.supabase.co";
|
|
66
|
+
function getSupabaseUrl() {
|
|
67
|
+
return getEnvVar("NEXT_PUBLIC_SUPABASE_URL") || getEnvVar("VITE_SUPABASE_URL") || DEFAULT_SUPABASE_URL;
|
|
68
|
+
}
|
|
21
69
|
function getSupabase() {
|
|
22
|
-
var _a, _b;
|
|
23
70
|
if (!globalThis[GLOBAL_KEY]) {
|
|
24
|
-
const url =
|
|
25
|
-
const anon =
|
|
71
|
+
const url = getSupabaseUrl();
|
|
72
|
+
const anon = getEnvVar("NEXT_PUBLIC_SUPABASE_ANON_KEY") || getEnvVar("VITE_SUPABASE_ANON_KEY") || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk";
|
|
26
73
|
globalThis[GLOBAL_KEY] = supabaseJs.createClient(url, anon, {
|
|
27
74
|
auth: {
|
|
28
75
|
// keep this lib client stateless so it doesn't step on the host app
|
|
@@ -37,8 +84,42 @@ function getSupabase() {
|
|
|
37
84
|
}
|
|
38
85
|
|
|
39
86
|
// src/useFeatureFlags.ts
|
|
40
|
-
var
|
|
87
|
+
var CACHE_KEY_PREFIX = "use-feature-flags-cache";
|
|
41
88
|
var initialized = false;
|
|
89
|
+
function getCacheKey(environment) {
|
|
90
|
+
return `${CACHE_KEY_PREFIX}:${environment}`;
|
|
91
|
+
}
|
|
92
|
+
function getCachedFlags(environment) {
|
|
93
|
+
var _a, _b;
|
|
94
|
+
if (typeof window === "undefined" || !window.localStorage) return null;
|
|
95
|
+
try {
|
|
96
|
+
const raw = window.localStorage.getItem(getCacheKey(environment));
|
|
97
|
+
if (!raw) return null;
|
|
98
|
+
const parsed = JSON.parse(raw);
|
|
99
|
+
if (!Array.isArray(parsed.flags)) return null;
|
|
100
|
+
return {
|
|
101
|
+
flags: parsed.flags,
|
|
102
|
+
envId: (_a = parsed.envId) != null ? _a : null,
|
|
103
|
+
updatedAt: (_b = parsed.updatedAt) != null ? _b : 0
|
|
104
|
+
};
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.warn("[use-feature-flags] failed reading cache", error);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function setCachedFlags(environment, flags, envId) {
|
|
111
|
+
if (typeof window === "undefined" || !window.localStorage) return;
|
|
112
|
+
try {
|
|
113
|
+
const payload = {
|
|
114
|
+
flags,
|
|
115
|
+
envId,
|
|
116
|
+
updatedAt: Date.now()
|
|
117
|
+
};
|
|
118
|
+
window.localStorage.setItem(getCacheKey(environment), JSON.stringify(payload));
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.warn("[use-feature-flags] failed writing cache", error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
42
123
|
function useFeatureFlags(passedKey, environment = ((_a) => (_a = window == null ? void 0 : window.location) == null ? void 0 : _a.hostname)() || "localhost") {
|
|
43
124
|
const sanitizedEnvironment = react.useMemo(
|
|
44
125
|
() => environment.replace(/:\d+$/, ""),
|
|
@@ -50,18 +131,24 @@ function useFeatureFlags(passedKey, environment = ((_a) => (_a = window == null
|
|
|
50
131
|
const cleanupRef = react.useRef(null);
|
|
51
132
|
const apiKey2 = passedKey;
|
|
52
133
|
react.useEffect(() => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
134
|
+
if (isDebugEnabled()) {
|
|
135
|
+
console.log(
|
|
136
|
+
"[use-feature-flags] initializing",
|
|
137
|
+
`environment: ${sanitizedEnvironment}`,
|
|
138
|
+
`apiKey provided: ${Boolean(apiKey2)}`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
58
141
|
}, [sanitizedEnvironment, apiKey2]);
|
|
59
142
|
const supabase = getSupabase();
|
|
143
|
+
const edgeFnUrl = react.useMemo(() => `${getSupabaseUrl()}/functions/v1/get-feature-flags`, []);
|
|
60
144
|
const fetchFlags = async () => {
|
|
145
|
+
var _a2, _b;
|
|
61
146
|
setState((prev) => ({ ...prev, loading: true }));
|
|
62
|
-
|
|
147
|
+
if (isDebugEnabled()) {
|
|
148
|
+
console.log("[use-feature-flags] fetching flags for", sanitizedEnvironment);
|
|
149
|
+
}
|
|
63
150
|
try {
|
|
64
|
-
const res = await fetch(
|
|
151
|
+
const res = await fetch(edgeFnUrl, {
|
|
65
152
|
method: "POST",
|
|
66
153
|
headers: {
|
|
67
154
|
"Content-Type": "application/json",
|
|
@@ -72,17 +159,31 @@ function useFeatureFlags(passedKey, environment = ((_a) => (_a = window == null
|
|
|
72
159
|
const json = await res.json();
|
|
73
160
|
if (!res.ok) {
|
|
74
161
|
console.warn("Edge function error:", (json == null ? void 0 : json.error) || res.statusText);
|
|
162
|
+
const cached = getCachedFlags(sanitizedEnvironment);
|
|
163
|
+
if (cached) {
|
|
164
|
+
setState({ flags: cached.flags, loading: false });
|
|
165
|
+
setEnvId(cached.envId);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
75
168
|
setState({ flags: [], loading: false });
|
|
76
169
|
return;
|
|
77
170
|
}
|
|
78
171
|
const flags = json.flags || [];
|
|
79
172
|
setState({ flags, loading: false });
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
setEnvId(flags[0].environment_id);
|
|
173
|
+
if (isDebugEnabled()) {
|
|
174
|
+
console.log("[use-feature-flags] fetched flags", flags);
|
|
83
175
|
}
|
|
176
|
+
const nextEnvId = flags.length > 0 ? (_b = (_a2 = flags[0]) == null ? void 0 : _a2.environment_id) != null ? _b : null : null;
|
|
177
|
+
setEnvId(nextEnvId);
|
|
178
|
+
setCachedFlags(sanitizedEnvironment, flags, nextEnvId);
|
|
84
179
|
} catch (err) {
|
|
85
180
|
console.error("Error fetching flags:", err.message);
|
|
181
|
+
const cached = getCachedFlags(sanitizedEnvironment);
|
|
182
|
+
if (cached) {
|
|
183
|
+
setState({ flags: cached.flags, loading: false });
|
|
184
|
+
setEnvId(cached.envId);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
86
187
|
setState({ flags: [], loading: false });
|
|
87
188
|
}
|
|
88
189
|
};
|
|
@@ -120,7 +221,9 @@ function useFeatureFlags(passedKey, environment = ((_a) => (_a = window == null
|
|
|
120
221
|
return {
|
|
121
222
|
isActive: (key) => {
|
|
122
223
|
const active = state.flags.some((f) => f.key === key && f.enabled === true);
|
|
123
|
-
|
|
224
|
+
if (isDebugEnabled()) {
|
|
225
|
+
console.log("[use-feature-flags] isActive", key, active);
|
|
226
|
+
}
|
|
124
227
|
return active;
|
|
125
228
|
},
|
|
126
229
|
flags: state.flags,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/store.ts","../src/supabaseClient.ts","../src/useFeatureFlags.ts"],"names":["atom","createClient","useMemo","useAtom","useState","useRef","apiKey","useEffect"],"mappings":"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","file":"index.cjs","sourcesContent":["import { atom } from 'jotai';\n\nexport type FeatureFlag = {\n id: string;\n key: string;\n enabled: boolean;\n environment: string;\n};\n\nexport const featureFlagsAtom = atom<{ flags: FeatureFlag[]; loading: boolean }>({\n flags: [],\n loading: true,\n});\n\n// store.ts\nlet apiKey: string | null = null;\n\nexport function setApiKey(key: string) {\n apiKey = key;\n}\n\nexport function getApiKey() {\n return apiKey;\n}\n","import { createClient, type SupabaseClient } from '@supabase/supabase-js';\n\n// HMR-safe global singleton to avoid multiple GoTrueClient instances\nconst GLOBAL_KEY = '__useff_supabase_singleton__';\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __useff_supabase_singleton__: SupabaseClient | undefined;\n}\n\nexport const DEFAULT_SUPABASE_URL = 'https://khppgsehvvlukzfdqbuo.supabase.co';\n\nexport function getSupabase(): SupabaseClient {\n if (!globalThis[GLOBAL_KEY]) {\n // Prefer env vars; fallback are placeholders (replace in your app env)\n const url =\n (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_SUPABASE_URL) ||\n DEFAULT_SUPABASE_URL;\n const anon =\n (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_SUPABASE_ANON_KEY) ||\n 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk';\n\n globalThis[GLOBAL_KEY] = createClient(url, anon, {\n auth: {\n // keep this lib client stateless so it doesn't step on the host app\n persistSession: false,\n autoRefreshToken: false,\n // unique key to avoid storage collisions\n storageKey: 'use-ff-auth'\n }\n });\n }\n return globalThis[GLOBAL_KEY]!;\n}\n","\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { FeatureFlag, featureFlagsAtom } from './store';\nimport { useAtom } from 'jotai';\nimport { getSupabase, DEFAULT_SUPABASE_URL } from './supabaseClient';\n\nconst EDGE_FN_URL = `${DEFAULT_SUPABASE_URL}/functions/v1/get-feature-flags`;\n\n\n\n\ntype FlagState = {\n flags: FeatureFlag[];\n loading: boolean;\n};\n\nlet initialized = false;\n\nexport function useFeatureFlags(\n passedKey?: string,\n environment = window?.location?.hostname || 'localhost'\n) {\n const sanitizedEnvironment = useMemo(\n () => environment.replace(/:\\d+$/, ''),\n [environment]\n );\n const [state, setState] = useAtom(featureFlagsAtom);\n const [envId, setEnvId] = useState<number | null>(null);\n const debounceTimeout = useRef<NodeJS.Timeout | null>(null);\n const cleanupRef = useRef<(() => void) | null>(null);\n\n const apiKey = passedKey;\n\n useEffect(() => {\n process.env.DEBUG && console.log(\n '[use-feature-flags] initializing',\n `environment: ${sanitizedEnvironment}`,\n `apiKey provided: ${Boolean(apiKey)}`\n );\n }, [sanitizedEnvironment, apiKey]);\n const supabase = getSupabase();\n\n const fetchFlags = async () => {\n setState((prev) => ({ ...prev, loading: true }));\n\n console.log('[use-feature-flags] fetching flags for', sanitizedEnvironment);\n\n try {\n const res = await fetch(EDGE_FN_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(apiKey ? { 'api-key': apiKey } : {}),\n },\n body: JSON.stringify({ environment: sanitizedEnvironment }),\n });\n\n const json = await res.json();\n if (!res.ok) {\n console.warn('Edge function error:', json?.error || res.statusText);\n setState({ flags: [], loading: false });\n return;\n }\n\n const flags = json.flags || [];\n setState({ flags, loading: false });\n console.log('[use-feature-flags] fetched flags', flags);\n\n // Store environment_id from first flag (assumes all have same env)\n if (flags.length > 0 && flags[0].environment_id) {\n setEnvId(flags[0].environment_id);\n }\n } catch (err: any) {\n console.error('Error fetching flags:', err.message);\n setState({ flags: [], loading: false });\n }\n };\n\n useEffect(() => {\n if (!apiKey || initialized) return;\n initialized = true;\n\n fetchFlags();\n\n return () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n if (cleanupRef.current) cleanupRef.current();\n };\n }, [sanitizedEnvironment, apiKey]);\n\n // Subscribe to flag changes for the correct environment\n useEffect(() => {\n if (!envId) return;\n\n const channel = supabase\n .channel(`flags-${sanitizedEnvironment}`)\n .on(\n 'postgres_changes',\n {\n event: '*',\n schema: 'public',\n table: 'feature_flags',\n filter: `environment_id=eq.${envId}`,\n },\n () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n debounceTimeout.current = setTimeout(fetchFlags, 300);\n }\n )\n .subscribe();\n\n if (cleanupRef.current) cleanupRef.current();\n cleanupRef.current = () => supabase.removeChannel(channel);\n\n return () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n supabase.removeChannel(channel);\n };\n }, [envId]);\n\n return {\n isActive: (key: string) => {\n const active = state.flags.some((f) => f.key === key && f.enabled === true);\n process.env.DEBUG && console.log('[use-feature-flags] isActive', key, active);\n return active;\n },\n flags: state.flags,\n loading: state.loading,\n };\n}\n\n\n\n\n\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/store.ts","../src/env.ts","../src/supabaseClient.ts","../src/useFeatureFlags.ts"],"names":["atom","createClient","useMemo","useAtom","useState","useRef","apiKey","useEffect","_a"],"mappings":"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","file":"index.cjs","sourcesContent":["import { atom } from 'jotai';\n\nexport type FeatureFlag = {\n id: string;\n key: string;\n enabled: boolean;\n environment: string;\n};\n\nexport const featureFlagsAtom = atom<{ flags: FeatureFlag[]; loading: boolean }>({\n flags: [],\n loading: true,\n});\n\n// store.ts\nlet apiKey: string | null = null;\n\nexport function setApiKey(key: string) {\n apiKey = key;\n}\n\nexport function getApiKey() {\n return apiKey;\n}\n","export type EnvValue = string | undefined;\n\ndeclare global {\n // React Native / Expo dev global\n // eslint-disable-next-line no-var\n var __DEV__: boolean | undefined;\n}\n\ntype EnvRecord = Record<string, unknown>;\n\nlet importMetaEnvGetter: (() => EnvRecord | undefined) | null = null;\n\nfunction getProcessEnv(): EnvRecord | undefined {\n if (typeof process === 'undefined' || !process) return undefined;\n const proc = process as unknown as { env?: EnvRecord };\n if (!proc.env || typeof proc.env !== 'object') return undefined;\n return proc.env;\n}\n\nfunction getImportMetaEnv(): EnvRecord | undefined {\n if (importMetaEnvGetter) return importMetaEnvGetter();\n\n try {\n // Keep import.meta access runtime-safe across CJS/ESM bundlers.\n const fn = new Function(\n 'return (typeof import.meta !== \"undefined\" && import.meta && import.meta.env) ? import.meta.env : undefined;'\n ) as () => EnvRecord | undefined;\n\n importMetaEnvGetter = fn;\n return fn();\n } catch {\n importMetaEnvGetter = () => undefined;\n return undefined;\n }\n}\n\nfunction toStringValue(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n return undefined;\n}\n\nexport function getEnvVar(name: string): EnvValue {\n const processValue = toStringValue(getProcessEnv()?.[name]);\n if (processValue !== undefined) return processValue;\n\n const importMetaValue = toStringValue(getImportMetaEnv()?.[name]);\n if (importMetaValue !== undefined) return importMetaValue;\n\n return undefined;\n}\n\nfunction getNodeEnv(): string {\n const nodeEnv = getEnvVar('NODE_ENV');\n if (nodeEnv) return nodeEnv;\n\n const mode = toStringValue(getImportMetaEnv()?.MODE);\n if (mode) return mode;\n\n return 'production';\n}\n\nexport function isDevelopment(): boolean {\n if (getNodeEnv() === 'development') return true;\n\n const importMetaDev = getImportMetaEnv()?.DEV;\n if (importMetaDev === true || importMetaDev === 'true' || importMetaDev === 1 || importMetaDev === '1') {\n return true;\n }\n\n if (typeof globalThis !== 'undefined' && (globalThis as typeof globalThis & { __DEV__?: unknown }).__DEV__ === true) {\n return true;\n }\n\n return false;\n}\n\nexport function isDebugEnabled(): boolean {\n const debug = getEnvVar('DEBUG');\n if (!debug) return false;\n\n const normalized = debug.toLowerCase();\n return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === '*';\n}\n","import { createClient, type SupabaseClient } from '@supabase/supabase-js';\nimport { getEnvVar } from './env';\n\n// HMR-safe global singleton to avoid multiple GoTrueClient instances\nconst GLOBAL_KEY = '__useff_supabase_singleton__';\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __useff_supabase_singleton__: SupabaseClient | undefined;\n}\n\nexport const DEFAULT_SUPABASE_URL = 'https://khppgsehvvlukzfdqbuo.supabase.co';\n\nexport function getSupabaseUrl(): string {\n return getEnvVar('NEXT_PUBLIC_SUPABASE_URL') || getEnvVar('VITE_SUPABASE_URL') || DEFAULT_SUPABASE_URL;\n}\n\nexport function getSupabase(): SupabaseClient {\n if (!globalThis[GLOBAL_KEY]) {\n // Prefer env vars; fallback are placeholders (replace in your app env)\n const url = getSupabaseUrl();\n const anon =\n getEnvVar('NEXT_PUBLIC_SUPABASE_ANON_KEY') ||\n getEnvVar('VITE_SUPABASE_ANON_KEY') ||\n 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk';\n\n globalThis[GLOBAL_KEY] = createClient(url, anon, {\n auth: {\n // keep this lib client stateless so it doesn't step on the host app\n persistSession: false,\n autoRefreshToken: false,\n // unique key to avoid storage collisions\n storageKey: 'use-ff-auth'\n }\n });\n }\n return globalThis[GLOBAL_KEY]!;\n}\n","\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { FeatureFlag, featureFlagsAtom } from './store';\nimport { useAtom } from 'jotai';\nimport { getSupabase, getSupabaseUrl } from './supabaseClient';\nimport { isDebugEnabled } from './env';\nconst CACHE_KEY_PREFIX = 'use-feature-flags-cache';\n\n\n\n\ntype FlagState = {\n flags: FeatureFlag[];\n loading: boolean;\n};\n\ntype CachedFlags = {\n flags: FeatureFlag[];\n envId: number | null;\n updatedAt: number;\n};\n\nlet initialized = false;\n\nfunction getCacheKey(environment: string) {\n return `${CACHE_KEY_PREFIX}:${environment}`;\n}\n\nfunction getCachedFlags(environment: string): CachedFlags | null {\n if (typeof window === 'undefined' || !window.localStorage) return null;\n\n try {\n const raw = window.localStorage.getItem(getCacheKey(environment));\n if (!raw) return null;\n const parsed = JSON.parse(raw) as CachedFlags;\n if (!Array.isArray(parsed.flags)) return null;\n\n return {\n flags: parsed.flags,\n envId: parsed.envId ?? null,\n updatedAt: parsed.updatedAt ?? 0,\n };\n } catch (error) {\n console.warn('[use-feature-flags] failed reading cache', error);\n return null;\n }\n}\n\nfunction setCachedFlags(environment: string, flags: FeatureFlag[], envId: number | null) {\n if (typeof window === 'undefined' || !window.localStorage) return;\n\n try {\n const payload: CachedFlags = {\n flags,\n envId,\n updatedAt: Date.now(),\n };\n window.localStorage.setItem(getCacheKey(environment), JSON.stringify(payload));\n } catch (error) {\n console.warn('[use-feature-flags] failed writing cache', error);\n }\n}\n\nexport function useFeatureFlags(\n passedKey?: string,\n environment = window?.location?.hostname || 'localhost'\n) {\n const sanitizedEnvironment = useMemo(\n () => environment.replace(/:\\d+$/, ''),\n [environment]\n );\n const [state, setState] = useAtom(featureFlagsAtom);\n const [envId, setEnvId] = useState<number | null>(null);\n const debounceTimeout = useRef<NodeJS.Timeout | null>(null);\n const cleanupRef = useRef<(() => void) | null>(null);\n\n const apiKey = passedKey;\n\n useEffect(() => {\n if (isDebugEnabled()) {\n console.log(\n '[use-feature-flags] initializing',\n `environment: ${sanitizedEnvironment}`,\n `apiKey provided: ${Boolean(apiKey)}`\n );\n }\n }, [sanitizedEnvironment, apiKey]);\n const supabase = getSupabase();\n const edgeFnUrl = useMemo(() => `${getSupabaseUrl()}/functions/v1/get-feature-flags`, []);\n\n const fetchFlags = async () => {\n setState((prev) => ({ ...prev, loading: true }));\n\n if (isDebugEnabled()) {\n console.log('[use-feature-flags] fetching flags for', sanitizedEnvironment);\n }\n\n try {\n const res = await fetch(edgeFnUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(apiKey ? { 'api-key': apiKey } : {}),\n },\n body: JSON.stringify({ environment: sanitizedEnvironment }),\n });\n\n const json = await res.json();\n if (!res.ok) {\n console.warn('Edge function error:', json?.error || res.statusText);\n const cached = getCachedFlags(sanitizedEnvironment);\n if (cached) {\n setState({ flags: cached.flags, loading: false });\n setEnvId(cached.envId);\n return;\n }\n\n setState({ flags: [], loading: false });\n return;\n }\n\n const flags = json.flags || [];\n setState({ flags, loading: false });\n if (isDebugEnabled()) {\n console.log('[use-feature-flags] fetched flags', flags);\n }\n\n // Store environment_id from first flag (assumes all have same env)\n const nextEnvId = flags.length > 0 ? flags[0]?.environment_id ?? null : null;\n setEnvId(nextEnvId);\n setCachedFlags(sanitizedEnvironment, flags, nextEnvId);\n } catch (err: any) {\n console.error('Error fetching flags:', err.message);\n const cached = getCachedFlags(sanitizedEnvironment);\n if (cached) {\n setState({ flags: cached.flags, loading: false });\n setEnvId(cached.envId);\n return;\n }\n\n setState({ flags: [], loading: false });\n }\n };\n\n useEffect(() => {\n if (!apiKey || initialized) return;\n initialized = true;\n\n fetchFlags();\n\n return () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n if (cleanupRef.current) cleanupRef.current();\n };\n }, [sanitizedEnvironment, apiKey]);\n\n // Subscribe to flag changes for the correct environment\n useEffect(() => {\n if (!envId) return;\n\n const channel = supabase\n .channel(`flags-${sanitizedEnvironment}`)\n .on(\n 'postgres_changes',\n {\n event: '*',\n schema: 'public',\n table: 'feature_flags',\n filter: `environment_id=eq.${envId}`,\n },\n () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n debounceTimeout.current = setTimeout(fetchFlags, 300);\n }\n )\n .subscribe();\n\n if (cleanupRef.current) cleanupRef.current();\n cleanupRef.current = () => supabase.removeChannel(channel);\n\n return () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n supabase.removeChannel(channel);\n };\n }, [envId]);\n\n return {\n isActive: (key: string) => {\n const active = state.flags.some((f) => f.key === key && f.enabled === true);\n if (isDebugEnabled()) {\n console.log('[use-feature-flags] isActive', key, active);\n }\n return active;\n },\n flags: state.flags,\n loading: state.loading,\n };\n}\n\n\n\n\n"]}
|
package/dist/index.js
CHANGED
|
@@ -14,13 +14,60 @@ function setApiKey(key) {
|
|
|
14
14
|
function getApiKey() {
|
|
15
15
|
return apiKey;
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
// src/env.ts
|
|
19
|
+
var importMetaEnvGetter = null;
|
|
20
|
+
function getProcessEnv() {
|
|
21
|
+
if (typeof process === "undefined" || !process) return void 0;
|
|
22
|
+
const proc = process;
|
|
23
|
+
if (!proc.env || typeof proc.env !== "object") return void 0;
|
|
24
|
+
return proc.env;
|
|
25
|
+
}
|
|
26
|
+
function getImportMetaEnv() {
|
|
27
|
+
if (importMetaEnvGetter) return importMetaEnvGetter();
|
|
28
|
+
try {
|
|
29
|
+
const fn = new Function(
|
|
30
|
+
'return (typeof import.meta !== "undefined" && import.meta && import.meta.env) ? import.meta.env : undefined;'
|
|
31
|
+
);
|
|
32
|
+
importMetaEnvGetter = fn;
|
|
33
|
+
return fn();
|
|
34
|
+
} catch {
|
|
35
|
+
importMetaEnvGetter = () => void 0;
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function toStringValue(value) {
|
|
40
|
+
if (typeof value === "string") return value;
|
|
41
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
42
|
+
return String(value);
|
|
43
|
+
}
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
46
|
+
function getEnvVar(name) {
|
|
47
|
+
var _a, _b;
|
|
48
|
+
const processValue = toStringValue((_a = getProcessEnv()) == null ? void 0 : _a[name]);
|
|
49
|
+
if (processValue !== void 0) return processValue;
|
|
50
|
+
const importMetaValue = toStringValue((_b = getImportMetaEnv()) == null ? void 0 : _b[name]);
|
|
51
|
+
if (importMetaValue !== void 0) return importMetaValue;
|
|
52
|
+
return void 0;
|
|
53
|
+
}
|
|
54
|
+
function isDebugEnabled() {
|
|
55
|
+
const debug = getEnvVar("DEBUG");
|
|
56
|
+
if (!debug) return false;
|
|
57
|
+
const normalized = debug.toLowerCase();
|
|
58
|
+
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "*";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/supabaseClient.ts
|
|
17
62
|
var GLOBAL_KEY = "__useff_supabase_singleton__";
|
|
18
63
|
var DEFAULT_SUPABASE_URL = "https://khppgsehvvlukzfdqbuo.supabase.co";
|
|
64
|
+
function getSupabaseUrl() {
|
|
65
|
+
return getEnvVar("NEXT_PUBLIC_SUPABASE_URL") || getEnvVar("VITE_SUPABASE_URL") || DEFAULT_SUPABASE_URL;
|
|
66
|
+
}
|
|
19
67
|
function getSupabase() {
|
|
20
|
-
var _a, _b;
|
|
21
68
|
if (!globalThis[GLOBAL_KEY]) {
|
|
22
|
-
const url =
|
|
23
|
-
const anon =
|
|
69
|
+
const url = getSupabaseUrl();
|
|
70
|
+
const anon = getEnvVar("NEXT_PUBLIC_SUPABASE_ANON_KEY") || getEnvVar("VITE_SUPABASE_ANON_KEY") || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk";
|
|
24
71
|
globalThis[GLOBAL_KEY] = createClient(url, anon, {
|
|
25
72
|
auth: {
|
|
26
73
|
// keep this lib client stateless so it doesn't step on the host app
|
|
@@ -35,8 +82,42 @@ function getSupabase() {
|
|
|
35
82
|
}
|
|
36
83
|
|
|
37
84
|
// src/useFeatureFlags.ts
|
|
38
|
-
var
|
|
85
|
+
var CACHE_KEY_PREFIX = "use-feature-flags-cache";
|
|
39
86
|
var initialized = false;
|
|
87
|
+
function getCacheKey(environment) {
|
|
88
|
+
return `${CACHE_KEY_PREFIX}:${environment}`;
|
|
89
|
+
}
|
|
90
|
+
function getCachedFlags(environment) {
|
|
91
|
+
var _a, _b;
|
|
92
|
+
if (typeof window === "undefined" || !window.localStorage) return null;
|
|
93
|
+
try {
|
|
94
|
+
const raw = window.localStorage.getItem(getCacheKey(environment));
|
|
95
|
+
if (!raw) return null;
|
|
96
|
+
const parsed = JSON.parse(raw);
|
|
97
|
+
if (!Array.isArray(parsed.flags)) return null;
|
|
98
|
+
return {
|
|
99
|
+
flags: parsed.flags,
|
|
100
|
+
envId: (_a = parsed.envId) != null ? _a : null,
|
|
101
|
+
updatedAt: (_b = parsed.updatedAt) != null ? _b : 0
|
|
102
|
+
};
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.warn("[use-feature-flags] failed reading cache", error);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function setCachedFlags(environment, flags, envId) {
|
|
109
|
+
if (typeof window === "undefined" || !window.localStorage) return;
|
|
110
|
+
try {
|
|
111
|
+
const payload = {
|
|
112
|
+
flags,
|
|
113
|
+
envId,
|
|
114
|
+
updatedAt: Date.now()
|
|
115
|
+
};
|
|
116
|
+
window.localStorage.setItem(getCacheKey(environment), JSON.stringify(payload));
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.warn("[use-feature-flags] failed writing cache", error);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
40
121
|
function useFeatureFlags(passedKey, environment = ((_a) => (_a = window == null ? void 0 : window.location) == null ? void 0 : _a.hostname)() || "localhost") {
|
|
41
122
|
const sanitizedEnvironment = useMemo(
|
|
42
123
|
() => environment.replace(/:\d+$/, ""),
|
|
@@ -48,18 +129,24 @@ function useFeatureFlags(passedKey, environment = ((_a) => (_a = window == null
|
|
|
48
129
|
const cleanupRef = useRef(null);
|
|
49
130
|
const apiKey2 = passedKey;
|
|
50
131
|
useEffect(() => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
132
|
+
if (isDebugEnabled()) {
|
|
133
|
+
console.log(
|
|
134
|
+
"[use-feature-flags] initializing",
|
|
135
|
+
`environment: ${sanitizedEnvironment}`,
|
|
136
|
+
`apiKey provided: ${Boolean(apiKey2)}`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
56
139
|
}, [sanitizedEnvironment, apiKey2]);
|
|
57
140
|
const supabase = getSupabase();
|
|
141
|
+
const edgeFnUrl = useMemo(() => `${getSupabaseUrl()}/functions/v1/get-feature-flags`, []);
|
|
58
142
|
const fetchFlags = async () => {
|
|
143
|
+
var _a2, _b;
|
|
59
144
|
setState((prev) => ({ ...prev, loading: true }));
|
|
60
|
-
|
|
145
|
+
if (isDebugEnabled()) {
|
|
146
|
+
console.log("[use-feature-flags] fetching flags for", sanitizedEnvironment);
|
|
147
|
+
}
|
|
61
148
|
try {
|
|
62
|
-
const res = await fetch(
|
|
149
|
+
const res = await fetch(edgeFnUrl, {
|
|
63
150
|
method: "POST",
|
|
64
151
|
headers: {
|
|
65
152
|
"Content-Type": "application/json",
|
|
@@ -70,17 +157,31 @@ function useFeatureFlags(passedKey, environment = ((_a) => (_a = window == null
|
|
|
70
157
|
const json = await res.json();
|
|
71
158
|
if (!res.ok) {
|
|
72
159
|
console.warn("Edge function error:", (json == null ? void 0 : json.error) || res.statusText);
|
|
160
|
+
const cached = getCachedFlags(sanitizedEnvironment);
|
|
161
|
+
if (cached) {
|
|
162
|
+
setState({ flags: cached.flags, loading: false });
|
|
163
|
+
setEnvId(cached.envId);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
73
166
|
setState({ flags: [], loading: false });
|
|
74
167
|
return;
|
|
75
168
|
}
|
|
76
169
|
const flags = json.flags || [];
|
|
77
170
|
setState({ flags, loading: false });
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
setEnvId(flags[0].environment_id);
|
|
171
|
+
if (isDebugEnabled()) {
|
|
172
|
+
console.log("[use-feature-flags] fetched flags", flags);
|
|
81
173
|
}
|
|
174
|
+
const nextEnvId = flags.length > 0 ? (_b = (_a2 = flags[0]) == null ? void 0 : _a2.environment_id) != null ? _b : null : null;
|
|
175
|
+
setEnvId(nextEnvId);
|
|
176
|
+
setCachedFlags(sanitizedEnvironment, flags, nextEnvId);
|
|
82
177
|
} catch (err) {
|
|
83
178
|
console.error("Error fetching flags:", err.message);
|
|
179
|
+
const cached = getCachedFlags(sanitizedEnvironment);
|
|
180
|
+
if (cached) {
|
|
181
|
+
setState({ flags: cached.flags, loading: false });
|
|
182
|
+
setEnvId(cached.envId);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
84
185
|
setState({ flags: [], loading: false });
|
|
85
186
|
}
|
|
86
187
|
};
|
|
@@ -118,7 +219,9 @@ function useFeatureFlags(passedKey, environment = ((_a) => (_a = window == null
|
|
|
118
219
|
return {
|
|
119
220
|
isActive: (key) => {
|
|
120
221
|
const active = state.flags.some((f) => f.key === key && f.enabled === true);
|
|
121
|
-
|
|
222
|
+
if (isDebugEnabled()) {
|
|
223
|
+
console.log("[use-feature-flags] isActive", key, active);
|
|
224
|
+
}
|
|
122
225
|
return active;
|
|
123
226
|
},
|
|
124
227
|
flags: state.flags,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/store.ts","../src/supabaseClient.ts","../src/useFeatureFlags.ts"],"names":["apiKey"],"mappings":"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","file":"index.js","sourcesContent":["import { atom } from 'jotai';\n\nexport type FeatureFlag = {\n id: string;\n key: string;\n enabled: boolean;\n environment: string;\n};\n\nexport const featureFlagsAtom = atom<{ flags: FeatureFlag[]; loading: boolean }>({\n flags: [],\n loading: true,\n});\n\n// store.ts\nlet apiKey: string | null = null;\n\nexport function setApiKey(key: string) {\n apiKey = key;\n}\n\nexport function getApiKey() {\n return apiKey;\n}\n","import { createClient, type SupabaseClient } from '@supabase/supabase-js';\n\n// HMR-safe global singleton to avoid multiple GoTrueClient instances\nconst GLOBAL_KEY = '__useff_supabase_singleton__';\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __useff_supabase_singleton__: SupabaseClient | undefined;\n}\n\nexport const DEFAULT_SUPABASE_URL = 'https://khppgsehvvlukzfdqbuo.supabase.co';\n\nexport function getSupabase(): SupabaseClient {\n if (!globalThis[GLOBAL_KEY]) {\n // Prefer env vars; fallback are placeholders (replace in your app env)\n const url =\n (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_SUPABASE_URL) ||\n DEFAULT_SUPABASE_URL;\n const anon =\n (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_SUPABASE_ANON_KEY) ||\n 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk';\n\n globalThis[GLOBAL_KEY] = createClient(url, anon, {\n auth: {\n // keep this lib client stateless so it doesn't step on the host app\n persistSession: false,\n autoRefreshToken: false,\n // unique key to avoid storage collisions\n storageKey: 'use-ff-auth'\n }\n });\n }\n return globalThis[GLOBAL_KEY]!;\n}\n","\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { FeatureFlag, featureFlagsAtom } from './store';\nimport { useAtom } from 'jotai';\nimport { getSupabase, DEFAULT_SUPABASE_URL } from './supabaseClient';\n\nconst EDGE_FN_URL = `${DEFAULT_SUPABASE_URL}/functions/v1/get-feature-flags`;\n\n\n\n\ntype FlagState = {\n flags: FeatureFlag[];\n loading: boolean;\n};\n\nlet initialized = false;\n\nexport function useFeatureFlags(\n passedKey?: string,\n environment = window?.location?.hostname || 'localhost'\n) {\n const sanitizedEnvironment = useMemo(\n () => environment.replace(/:\\d+$/, ''),\n [environment]\n );\n const [state, setState] = useAtom(featureFlagsAtom);\n const [envId, setEnvId] = useState<number | null>(null);\n const debounceTimeout = useRef<NodeJS.Timeout | null>(null);\n const cleanupRef = useRef<(() => void) | null>(null);\n\n const apiKey = passedKey;\n\n useEffect(() => {\n process.env.DEBUG && console.log(\n '[use-feature-flags] initializing',\n `environment: ${sanitizedEnvironment}`,\n `apiKey provided: ${Boolean(apiKey)}`\n );\n }, [sanitizedEnvironment, apiKey]);\n const supabase = getSupabase();\n\n const fetchFlags = async () => {\n setState((prev) => ({ ...prev, loading: true }));\n\n console.log('[use-feature-flags] fetching flags for', sanitizedEnvironment);\n\n try {\n const res = await fetch(EDGE_FN_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(apiKey ? { 'api-key': apiKey } : {}),\n },\n body: JSON.stringify({ environment: sanitizedEnvironment }),\n });\n\n const json = await res.json();\n if (!res.ok) {\n console.warn('Edge function error:', json?.error || res.statusText);\n setState({ flags: [], loading: false });\n return;\n }\n\n const flags = json.flags || [];\n setState({ flags, loading: false });\n console.log('[use-feature-flags] fetched flags', flags);\n\n // Store environment_id from first flag (assumes all have same env)\n if (flags.length > 0 && flags[0].environment_id) {\n setEnvId(flags[0].environment_id);\n }\n } catch (err: any) {\n console.error('Error fetching flags:', err.message);\n setState({ flags: [], loading: false });\n }\n };\n\n useEffect(() => {\n if (!apiKey || initialized) return;\n initialized = true;\n\n fetchFlags();\n\n return () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n if (cleanupRef.current) cleanupRef.current();\n };\n }, [sanitizedEnvironment, apiKey]);\n\n // Subscribe to flag changes for the correct environment\n useEffect(() => {\n if (!envId) return;\n\n const channel = supabase\n .channel(`flags-${sanitizedEnvironment}`)\n .on(\n 'postgres_changes',\n {\n event: '*',\n schema: 'public',\n table: 'feature_flags',\n filter: `environment_id=eq.${envId}`,\n },\n () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n debounceTimeout.current = setTimeout(fetchFlags, 300);\n }\n )\n .subscribe();\n\n if (cleanupRef.current) cleanupRef.current();\n cleanupRef.current = () => supabase.removeChannel(channel);\n\n return () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n supabase.removeChannel(channel);\n };\n }, [envId]);\n\n return {\n isActive: (key: string) => {\n const active = state.flags.some((f) => f.key === key && f.enabled === true);\n process.env.DEBUG && console.log('[use-feature-flags] isActive', key, active);\n return active;\n },\n flags: state.flags,\n loading: state.loading,\n };\n}\n\n\n\n\n\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/store.ts","../src/env.ts","../src/supabaseClient.ts","../src/useFeatureFlags.ts"],"names":["apiKey","_a"],"mappings":"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","file":"index.js","sourcesContent":["import { atom } from 'jotai';\n\nexport type FeatureFlag = {\n id: string;\n key: string;\n enabled: boolean;\n environment: string;\n};\n\nexport const featureFlagsAtom = atom<{ flags: FeatureFlag[]; loading: boolean }>({\n flags: [],\n loading: true,\n});\n\n// store.ts\nlet apiKey: string | null = null;\n\nexport function setApiKey(key: string) {\n apiKey = key;\n}\n\nexport function getApiKey() {\n return apiKey;\n}\n","export type EnvValue = string | undefined;\n\ndeclare global {\n // React Native / Expo dev global\n // eslint-disable-next-line no-var\n var __DEV__: boolean | undefined;\n}\n\ntype EnvRecord = Record<string, unknown>;\n\nlet importMetaEnvGetter: (() => EnvRecord | undefined) | null = null;\n\nfunction getProcessEnv(): EnvRecord | undefined {\n if (typeof process === 'undefined' || !process) return undefined;\n const proc = process as unknown as { env?: EnvRecord };\n if (!proc.env || typeof proc.env !== 'object') return undefined;\n return proc.env;\n}\n\nfunction getImportMetaEnv(): EnvRecord | undefined {\n if (importMetaEnvGetter) return importMetaEnvGetter();\n\n try {\n // Keep import.meta access runtime-safe across CJS/ESM bundlers.\n const fn = new Function(\n 'return (typeof import.meta !== \"undefined\" && import.meta && import.meta.env) ? import.meta.env : undefined;'\n ) as () => EnvRecord | undefined;\n\n importMetaEnvGetter = fn;\n return fn();\n } catch {\n importMetaEnvGetter = () => undefined;\n return undefined;\n }\n}\n\nfunction toStringValue(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n return undefined;\n}\n\nexport function getEnvVar(name: string): EnvValue {\n const processValue = toStringValue(getProcessEnv()?.[name]);\n if (processValue !== undefined) return processValue;\n\n const importMetaValue = toStringValue(getImportMetaEnv()?.[name]);\n if (importMetaValue !== undefined) return importMetaValue;\n\n return undefined;\n}\n\nfunction getNodeEnv(): string {\n const nodeEnv = getEnvVar('NODE_ENV');\n if (nodeEnv) return nodeEnv;\n\n const mode = toStringValue(getImportMetaEnv()?.MODE);\n if (mode) return mode;\n\n return 'production';\n}\n\nexport function isDevelopment(): boolean {\n if (getNodeEnv() === 'development') return true;\n\n const importMetaDev = getImportMetaEnv()?.DEV;\n if (importMetaDev === true || importMetaDev === 'true' || importMetaDev === 1 || importMetaDev === '1') {\n return true;\n }\n\n if (typeof globalThis !== 'undefined' && (globalThis as typeof globalThis & { __DEV__?: unknown }).__DEV__ === true) {\n return true;\n }\n\n return false;\n}\n\nexport function isDebugEnabled(): boolean {\n const debug = getEnvVar('DEBUG');\n if (!debug) return false;\n\n const normalized = debug.toLowerCase();\n return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === '*';\n}\n","import { createClient, type SupabaseClient } from '@supabase/supabase-js';\nimport { getEnvVar } from './env';\n\n// HMR-safe global singleton to avoid multiple GoTrueClient instances\nconst GLOBAL_KEY = '__useff_supabase_singleton__';\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __useff_supabase_singleton__: SupabaseClient | undefined;\n}\n\nexport const DEFAULT_SUPABASE_URL = 'https://khppgsehvvlukzfdqbuo.supabase.co';\n\nexport function getSupabaseUrl(): string {\n return getEnvVar('NEXT_PUBLIC_SUPABASE_URL') || getEnvVar('VITE_SUPABASE_URL') || DEFAULT_SUPABASE_URL;\n}\n\nexport function getSupabase(): SupabaseClient {\n if (!globalThis[GLOBAL_KEY]) {\n // Prefer env vars; fallback are placeholders (replace in your app env)\n const url = getSupabaseUrl();\n const anon =\n getEnvVar('NEXT_PUBLIC_SUPABASE_ANON_KEY') ||\n getEnvVar('VITE_SUPABASE_ANON_KEY') ||\n 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk';\n\n globalThis[GLOBAL_KEY] = createClient(url, anon, {\n auth: {\n // keep this lib client stateless so it doesn't step on the host app\n persistSession: false,\n autoRefreshToken: false,\n // unique key to avoid storage collisions\n storageKey: 'use-ff-auth'\n }\n });\n }\n return globalThis[GLOBAL_KEY]!;\n}\n","\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { FeatureFlag, featureFlagsAtom } from './store';\nimport { useAtom } from 'jotai';\nimport { getSupabase, getSupabaseUrl } from './supabaseClient';\nimport { isDebugEnabled } from './env';\nconst CACHE_KEY_PREFIX = 'use-feature-flags-cache';\n\n\n\n\ntype FlagState = {\n flags: FeatureFlag[];\n loading: boolean;\n};\n\ntype CachedFlags = {\n flags: FeatureFlag[];\n envId: number | null;\n updatedAt: number;\n};\n\nlet initialized = false;\n\nfunction getCacheKey(environment: string) {\n return `${CACHE_KEY_PREFIX}:${environment}`;\n}\n\nfunction getCachedFlags(environment: string): CachedFlags | null {\n if (typeof window === 'undefined' || !window.localStorage) return null;\n\n try {\n const raw = window.localStorage.getItem(getCacheKey(environment));\n if (!raw) return null;\n const parsed = JSON.parse(raw) as CachedFlags;\n if (!Array.isArray(parsed.flags)) return null;\n\n return {\n flags: parsed.flags,\n envId: parsed.envId ?? null,\n updatedAt: parsed.updatedAt ?? 0,\n };\n } catch (error) {\n console.warn('[use-feature-flags] failed reading cache', error);\n return null;\n }\n}\n\nfunction setCachedFlags(environment: string, flags: FeatureFlag[], envId: number | null) {\n if (typeof window === 'undefined' || !window.localStorage) return;\n\n try {\n const payload: CachedFlags = {\n flags,\n envId,\n updatedAt: Date.now(),\n };\n window.localStorage.setItem(getCacheKey(environment), JSON.stringify(payload));\n } catch (error) {\n console.warn('[use-feature-flags] failed writing cache', error);\n }\n}\n\nexport function useFeatureFlags(\n passedKey?: string,\n environment = window?.location?.hostname || 'localhost'\n) {\n const sanitizedEnvironment = useMemo(\n () => environment.replace(/:\\d+$/, ''),\n [environment]\n );\n const [state, setState] = useAtom(featureFlagsAtom);\n const [envId, setEnvId] = useState<number | null>(null);\n const debounceTimeout = useRef<NodeJS.Timeout | null>(null);\n const cleanupRef = useRef<(() => void) | null>(null);\n\n const apiKey = passedKey;\n\n useEffect(() => {\n if (isDebugEnabled()) {\n console.log(\n '[use-feature-flags] initializing',\n `environment: ${sanitizedEnvironment}`,\n `apiKey provided: ${Boolean(apiKey)}`\n );\n }\n }, [sanitizedEnvironment, apiKey]);\n const supabase = getSupabase();\n const edgeFnUrl = useMemo(() => `${getSupabaseUrl()}/functions/v1/get-feature-flags`, []);\n\n const fetchFlags = async () => {\n setState((prev) => ({ ...prev, loading: true }));\n\n if (isDebugEnabled()) {\n console.log('[use-feature-flags] fetching flags for', sanitizedEnvironment);\n }\n\n try {\n const res = await fetch(edgeFnUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(apiKey ? { 'api-key': apiKey } : {}),\n },\n body: JSON.stringify({ environment: sanitizedEnvironment }),\n });\n\n const json = await res.json();\n if (!res.ok) {\n console.warn('Edge function error:', json?.error || res.statusText);\n const cached = getCachedFlags(sanitizedEnvironment);\n if (cached) {\n setState({ flags: cached.flags, loading: false });\n setEnvId(cached.envId);\n return;\n }\n\n setState({ flags: [], loading: false });\n return;\n }\n\n const flags = json.flags || [];\n setState({ flags, loading: false });\n if (isDebugEnabled()) {\n console.log('[use-feature-flags] fetched flags', flags);\n }\n\n // Store environment_id from first flag (assumes all have same env)\n const nextEnvId = flags.length > 0 ? flags[0]?.environment_id ?? null : null;\n setEnvId(nextEnvId);\n setCachedFlags(sanitizedEnvironment, flags, nextEnvId);\n } catch (err: any) {\n console.error('Error fetching flags:', err.message);\n const cached = getCachedFlags(sanitizedEnvironment);\n if (cached) {\n setState({ flags: cached.flags, loading: false });\n setEnvId(cached.envId);\n return;\n }\n\n setState({ flags: [], loading: false });\n }\n };\n\n useEffect(() => {\n if (!apiKey || initialized) return;\n initialized = true;\n\n fetchFlags();\n\n return () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n if (cleanupRef.current) cleanupRef.current();\n };\n }, [sanitizedEnvironment, apiKey]);\n\n // Subscribe to flag changes for the correct environment\n useEffect(() => {\n if (!envId) return;\n\n const channel = supabase\n .channel(`flags-${sanitizedEnvironment}`)\n .on(\n 'postgres_changes',\n {\n event: '*',\n schema: 'public',\n table: 'feature_flags',\n filter: `environment_id=eq.${envId}`,\n },\n () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n debounceTimeout.current = setTimeout(fetchFlags, 300);\n }\n )\n .subscribe();\n\n if (cleanupRef.current) cleanupRef.current();\n cleanupRef.current = () => supabase.removeChannel(channel);\n\n return () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n supabase.removeChannel(channel);\n };\n }, [envId]);\n\n return {\n isActive: (key: string) => {\n const active = state.flags.some((f) => f.key === key && f.enabled === true);\n if (isDebugEnabled()) {\n console.log('[use-feature-flags] isActive', key, active);\n }\n return active;\n },\n flags: state.flags,\n loading: state.loading,\n };\n}\n\n\n\n\n"]}
|