@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 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 = typeof process !== "undefined" && ((_a = process.env) == null ? void 0 : _a.NEXT_PUBLIC_SUPABASE_URL) || DEFAULT_SUPABASE_URL;
25
- const anon = typeof process !== "undefined" && ((_b = process.env) == null ? void 0 : _b.NEXT_PUBLIC_SUPABASE_ANON_KEY) || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk";
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 EDGE_FN_URL = `${DEFAULT_SUPABASE_URL}/functions/v1/get-feature-flags`;
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
- process.env.DEBUG && console.log(
54
- "[use-feature-flags] initializing",
55
- `environment: ${sanitizedEnvironment}`,
56
- `apiKey provided: ${Boolean(apiKey2)}`
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
- console.log("[use-feature-flags] fetching flags for", sanitizedEnvironment);
147
+ if (isDebugEnabled()) {
148
+ console.log("[use-feature-flags] fetching flags for", sanitizedEnvironment);
149
+ }
63
150
  try {
64
- const res = await fetch(EDGE_FN_URL, {
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
- console.log("[use-feature-flags] fetched flags", flags);
81
- if (flags.length > 0 && flags[0].environment_id) {
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
- process.env.DEBUG && console.log("[use-feature-flags] isActive", key, active);
224
+ if (isDebugEnabled()) {
225
+ console.log("[use-feature-flags] isActive", key, active);
226
+ }
124
227
  return active;
125
228
  },
126
229
  flags: state.flags,
@@ -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 = typeof process !== "undefined" && ((_a = process.env) == null ? void 0 : _a.NEXT_PUBLIC_SUPABASE_URL) || DEFAULT_SUPABASE_URL;
23
- const anon = typeof process !== "undefined" && ((_b = process.env) == null ? void 0 : _b.NEXT_PUBLIC_SUPABASE_ANON_KEY) || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk";
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 EDGE_FN_URL = `${DEFAULT_SUPABASE_URL}/functions/v1/get-feature-flags`;
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
- process.env.DEBUG && console.log(
52
- "[use-feature-flags] initializing",
53
- `environment: ${sanitizedEnvironment}`,
54
- `apiKey provided: ${Boolean(apiKey2)}`
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
- console.log("[use-feature-flags] fetching flags for", sanitizedEnvironment);
145
+ if (isDebugEnabled()) {
146
+ console.log("[use-feature-flags] fetching flags for", sanitizedEnvironment);
147
+ }
61
148
  try {
62
- const res = await fetch(EDGE_FN_URL, {
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
- console.log("[use-feature-flags] fetched flags", flags);
79
- if (flags.length > 0 && flags[0].environment_id) {
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
- process.env.DEBUG && console.log("[use-feature-flags] isActive", key, active);
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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geejay/use-feature-flags",
3
- "version": "1.0.31",
3
+ "version": "1.0.34",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {