@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.
- package/AGENTS.md +791 -0
- package/README.md +70 -622
- package/dist/vault/index.d.ts +73 -0
- package/dist/vault/index.d.ts.map +1 -0
- package/dist/vault/index.js +169 -0
- package/dist/vault/index.js.map +1 -0
- package/dist/vault/react.d.ts +27 -0
- package/dist/vault/react.d.ts.map +1 -0
- package/dist/vault/react.js +73 -0
- package/dist/vault/react.js.map +1 -0
- package/package.json +20 -5
- package/src/vault/index.ts +198 -0
- package/src/vault/react.tsx +125 -0
|
@@ -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
|
+
}
|