@geejay/use-feature-flags 1.0.3 → 1.0.5

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.
@@ -1,6 +1,6 @@
1
1
  import { FeatureFlag } from './store';
2
2
  export declare const DEFAULT_SUPABASE_URL = "https://khppgsehvvlukzfdqbuo.supabase.co";
3
- export declare const DEFAULT_SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk";
3
+ export declare const DEFAULT_SUPABASE_KEY = "yeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk";
4
4
  export declare function useFeatureFlags(passedKey?: string, environment?: string): {
5
5
  isActive: (key: string) => boolean;
6
6
  flags: FeatureFlag[];
@@ -1,75 +1,44 @@
1
- import { useEffect, useRef, useState } from 'react';
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
2
  import { createClient } from '@supabase/supabase-js';
3
3
  export const DEFAULT_SUPABASE_URL = 'https://khppgsehvvlukzfdqbuo.supabase.co';
4
- export const DEFAULT_SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk';
5
- const supabase = createClient(DEFAULT_SUPABASE_URL, DEFAULT_SUPABASE_KEY);
4
+ export const DEFAULT_SUPABASE_KEY = 'yeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk';
5
+ const EDGE_FN_URL = `${DEFAULT_SUPABASE_URL}/functions/v1/get-feature-flags`;
6
6
  let initialized = false;
7
7
  export function useFeatureFlags(passedKey, environment = 'localhost') {
8
8
  const [state, setState] = useState({ flags: [], loading: true });
9
+ const [envId, setEnvId] = useState(null);
9
10
  const debounceTimeout = useRef(null);
10
11
  const cleanupRef = useRef();
11
12
  const apiKey = passedKey || DEFAULT_SUPABASE_KEY;
13
+ const supabase = useMemo(() => {
14
+ return createClient(DEFAULT_SUPABASE_URL, passedKey || DEFAULT_SUPABASE_KEY);
15
+ }, [passedKey]);
12
16
  const fetchFlags = async () => {
13
17
  setState((prev) => ({ ...prev, loading: true }));
14
18
  try {
15
- const headerOpts = { headers: { 'api-key': apiKey } };
16
- const { data: tenantLookup, error: keyError } = await supabase
17
- .from('api_keys')
18
- .select('tenant_id')
19
- .eq('key', apiKey)
20
- .maybeSingle();
21
- if (keyError || !(tenantLookup === null || tenantLookup === void 0 ? void 0 : tenantLookup.tenant_id)) {
22
- console.warn('Invalid or missing API key.');
19
+ const res = await fetch(EDGE_FN_URL, {
20
+ method: 'POST',
21
+ headers: {
22
+ 'Content-Type': 'application/json',
23
+ 'api-key': apiKey,
24
+ },
25
+ body: JSON.stringify({ environment }),
26
+ });
27
+ const json = await res.json();
28
+ if (!res.ok) {
29
+ console.warn('Edge function error:', (json === null || json === void 0 ? void 0 : json.error) || res.statusText);
23
30
  setState({ flags: [], loading: false });
24
31
  return;
25
32
  }
26
- const tenant = tenantLookup.tenant_id;
27
- const { data: env, error: envError } = await supabase
28
- .from('environments')
29
- .select('id')
30
- .eq('value', environment)
31
- .eq('tenant_id', tenant)
32
- .maybeSingle();
33
- if (envError || !(env === null || env === void 0 ? void 0 : env.id)) {
34
- console.warn('Environment not found:', environment);
35
- setState({ flags: [], loading: false });
36
- return;
37
- }
38
- const environmentId = env.id;
39
- const { data: flagsData, error: flagsError } = await supabase
40
- .from('feature_flags')
41
- .select('*')
42
- .eq('tenant_id', tenant)
43
- .eq('environment_id', environmentId);
44
- if (flagsError) {
45
- console.error('Flag fetch error:', flagsError.message);
46
- setState({ flags: [], loading: false });
47
- }
48
- else {
49
- setState({ flags: flagsData || [], loading: false });
50
- // Clean up existing channel if present
51
- if (cleanupRef.current)
52
- cleanupRef.current();
53
- const channel = supabase
54
- .channel(`flags-${environment}`)
55
- .on('postgres_changes', {
56
- event: '*',
57
- schema: 'public',
58
- table: 'feature_flags',
59
- filter: `environment_id=eq.${environmentId}`,
60
- }, () => {
61
- if (debounceTimeout.current)
62
- clearTimeout(debounceTimeout.current);
63
- debounceTimeout.current = setTimeout(fetchFlags, 300);
64
- })
65
- .subscribe();
66
- cleanupRef.current = () => {
67
- supabase.removeChannel(channel);
68
- };
33
+ const flags = json.flags || [];
34
+ setState({ flags, loading: false });
35
+ // Store environment_id from first flag (assumes all have same env)
36
+ if (flags.length > 0 && flags[0].environment_id) {
37
+ setEnvId(flags[0].environment_id);
69
38
  }
70
39
  }
71
40
  catch (err) {
72
- console.error('Unexpected error fetching flags:', err.message);
41
+ console.error('Error fetching flags:', err.message);
73
42
  setState({ flags: [], loading: false });
74
43
  }
75
44
  };
@@ -85,6 +54,32 @@ export function useFeatureFlags(passedKey, environment = 'localhost') {
85
54
  cleanupRef.current();
86
55
  };
87
56
  }, [environment, apiKey]);
57
+ // Subscribe to flag changes for the correct environment
58
+ useEffect(() => {
59
+ if (!envId)
60
+ return;
61
+ const channel = supabase
62
+ .channel(`flags-${environment}`)
63
+ .on('postgres_changes', {
64
+ event: '*',
65
+ schema: 'public',
66
+ table: 'feature_flags',
67
+ filter: `environment_id=eq.${envId}`,
68
+ }, () => {
69
+ if (debounceTimeout.current)
70
+ clearTimeout(debounceTimeout.current);
71
+ debounceTimeout.current = setTimeout(fetchFlags, 300);
72
+ })
73
+ .subscribe();
74
+ if (cleanupRef.current)
75
+ cleanupRef.current();
76
+ cleanupRef.current = () => supabase.removeChannel(channel);
77
+ return () => {
78
+ if (debounceTimeout.current)
79
+ clearTimeout(debounceTimeout.current);
80
+ supabase.removeChannel(channel);
81
+ };
82
+ }, [envId]);
88
83
  return {
89
84
  isActive: (key) => state.flags.some((f) => f.key === key && f.enabled),
90
85
  flags: state.flags,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geejay/use-feature-flags",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "React hook for feature flag management using Supabase",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",