@sentroy-co/client-sdk 2.6.3 → 2.8.0

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.
@@ -0,0 +1,125 @@
1
+ "use client"
2
+
3
+ import {
4
+ createContext,
5
+ useContext,
6
+ useEffect,
7
+ useState,
8
+ type ReactNode,
9
+ } from "react"
10
+
11
+ /**
12
+ * `@sentroy-co/client-sdk/vault/react` — React provider + hook.
13
+ *
14
+ * Akış:
15
+ * 1. Server-side `getPublicEnvs()` çağrılıp result `<EnvProvider envs={...}>`
16
+ * ile root layout'a SSR sırasında inject edilir → ilk paint'te
17
+ * `useEnv()` doğru değeri döndürür (FOUC yok).
18
+ * 2. Client-side periyodik refresh (`/api/env-vault/public` endpoint'i,
19
+ * yalnızca public:true variable'lar) — admin değer değiştirince UI
20
+ * kullanıcıyı zorla refresh etmeden günceli alır. `refreshIntervalMs`
21
+ * 0 verilirse polling kapalı.
22
+ *
23
+ * **Güvenlik:** Server-side `getEnv()` private env'leri de döner; bu hook
24
+ * yalnızca PUBLIC env'leri client'a sızdırır. Provider'a server-only
25
+ * env geçmek konvansiyon ihlali — `getPublicEnvs()` filter'ını atlayıp
26
+ * `getAllEnvs()` geçerseniz secret leak'lersiniz.
27
+ */
28
+
29
+ interface EnvContextValue {
30
+ envs: Record<string, string>
31
+ loading: boolean
32
+ refresh: () => Promise<void>
33
+ }
34
+
35
+ const EnvContext = createContext<EnvContextValue>({
36
+ envs: {},
37
+ loading: false,
38
+ refresh: async () => {},
39
+ })
40
+
41
+ interface EnvProviderProps {
42
+ /** Server-side fetched public envs — SSR'da inject edilir. */
43
+ envs: Record<string, string>
44
+ /** Public refresh endpoint URL — default `/api/env-vault/public`. */
45
+ refreshUrl?: string
46
+ /** Bearer token — public endpoint için. Default `process.env.NEXT_PUBLIC_SENTROY_ENV_API_KEY`. */
47
+ apiKey?: string
48
+ /** Refresh interval ms; 0 ise polling kapalı. Default 5 dk. */
49
+ refreshIntervalMs?: number
50
+ children: ReactNode
51
+ }
52
+
53
+ const DEFAULT_REFRESH_INTERVAL_MS = 5 * 60 * 1000
54
+
55
+ export function EnvProvider({
56
+ envs: initialEnvs,
57
+ refreshUrl = "/api/env-vault/public",
58
+ apiKey,
59
+ refreshIntervalMs = DEFAULT_REFRESH_INTERVAL_MS,
60
+ children,
61
+ }: EnvProviderProps) {
62
+ const [envs, setEnvs] = useState<Record<string, string>>(initialEnvs)
63
+ const [loading, setLoading] = useState(false)
64
+
65
+ const effectiveKey =
66
+ apiKey ??
67
+ (typeof process !== "undefined"
68
+ ? process.env?.NEXT_PUBLIC_SENTROY_ENV_API_KEY
69
+ : undefined)
70
+
71
+ async function refresh() {
72
+ if (!effectiveKey) return // bootstrap yoksa polling no-op
73
+ setLoading(true)
74
+ try {
75
+ const res = await fetch(refreshUrl, {
76
+ headers: { Authorization: `Bearer ${effectiveKey}` },
77
+ cache: "no-store",
78
+ })
79
+ if (!res.ok) return
80
+ const json = (await res.json()) as {
81
+ data?: { variables: { key: string; value: string }[] }
82
+ }
83
+ const next: Record<string, string> = {}
84
+ for (const v of json.data?.variables ?? []) next[v.key] = v.value
85
+ setEnvs(next)
86
+ } catch {
87
+ // network error — keep previous envs, fail-soft
88
+ } finally {
89
+ setLoading(false)
90
+ }
91
+ }
92
+
93
+ useEffect(() => {
94
+ if (!refreshIntervalMs || refreshIntervalMs <= 0) return
95
+ const id = setInterval(refresh, refreshIntervalMs)
96
+ return () => clearInterval(id)
97
+ // eslint-disable-next-line react-hooks/exhaustive-deps
98
+ }, [refreshIntervalMs, effectiveKey])
99
+
100
+ return (
101
+ <EnvContext.Provider value={{ envs, loading, refresh }}>
102
+ {children}
103
+ </EnvContext.Provider>
104
+ )
105
+ }
106
+
107
+ /**
108
+ * `useEnv("KEY")` — provider'ın hydrate ettiği env değerini döner.
109
+ * Yoksa undefined; çağıran fallback verir (`useEnv("X") ?? "default"`).
110
+ */
111
+ export function useEnv(key: string): string | undefined {
112
+ const ctx = useContext(EnvContext)
113
+ return ctx.envs[key]
114
+ }
115
+
116
+ /** Tüm public env'leri Record olarak döner. */
117
+ export function useAllEnvs(): Record<string, string> {
118
+ return useContext(EnvContext).envs
119
+ }
120
+
121
+ /** Manuel refresh tetikleme (örn. admin "config updated" notification sonrası). */
122
+ export function useEnvRefresh(): { refresh: () => Promise<void>; loading: boolean } {
123
+ const ctx = useContext(EnvContext)
124
+ return { refresh: ctx.refresh, loading: ctx.loading }
125
+ }