@githat/nextjs 0.3.0 → 0.4.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/dist/index.d.mts CHANGED
@@ -22,6 +22,17 @@ interface GitHatConfig {
22
22
  signUpUrl?: string;
23
23
  afterSignInUrl?: string;
24
24
  afterSignOutUrl?: string;
25
+ /**
26
+ * Token storage mode:
27
+ * - 'localStorage' (default): Tokens stored in browser localStorage
28
+ * - 'cookie': Tokens stored in httpOnly cookies (more secure, XSS-resistant)
29
+ *
30
+ * When using 'cookie' mode:
31
+ * - Login/refresh automatically set httpOnly cookies
32
+ * - SDK reads auth state from cookies (server-side)
33
+ * - Better for apps with server-side rendering
34
+ */
35
+ tokenStorage?: 'localStorage' | 'cookie';
25
36
  }
26
37
  interface AuthState {
27
38
  user: GitHatUser | null;
@@ -56,9 +67,12 @@ interface GitHatProviderProps {
56
67
  }
57
68
  declare function GitHatProvider({ config: rawConfig, children }: GitHatProviderProps): react_jsx_runtime.JSX.Element;
58
69
 
70
+ interface OrgMetadata$1 {
71
+ [key: string]: unknown;
72
+ }
59
73
  declare function useAuth(): GitHatContextValue;
60
74
  declare function useGitHat(): {
61
- fetch: <T = any>(endpoint: string, options?: RequestInit) => Promise<T>;
75
+ fetch: <T = unknown>(endpoint: string, fetchOptions?: RequestInit) => Promise<T>;
62
76
  getUserOrgs: () => Promise<{
63
77
  orgs: GitHatOrg[];
64
78
  }>;
@@ -68,6 +82,8 @@ declare function useGitHat(): {
68
82
  verifyAgent: (wallet: string) => Promise<{
69
83
  verified: boolean;
70
84
  }>;
85
+ getOrgMetadata: () => Promise<OrgMetadata$1>;
86
+ updateOrgMetadata: (updates: OrgMetadata$1) => Promise<OrgMetadata$1>;
71
87
  };
72
88
 
73
89
  interface DataItem {
@@ -210,4 +226,34 @@ interface ProtectedRouteProps {
210
226
  }
211
227
  declare function ProtectedRoute({ children, fallback }: ProtectedRouteProps): react_jsx_runtime.JSX.Element | null;
212
228
 
213
- export { type AuthActions, type AuthState, type BatchOperation, type BatchResult, type DataItem, type DeleteResult, type GitHatConfig, type GitHatContextValue, type GitHatOrg, GitHatProvider, type GitHatUser, OrgSwitcher, ProtectedRoute, type PutResult, type QueryOptions, type QueryResult, SignInButton, SignInForm, SignUpButton, type SignUpData, SignUpForm, type SignUpResult, UserButton, VerifiedBadge, useAuth, useData, useGitHat };
229
+ /**
230
+ * @githat/nextjs/server
231
+ *
232
+ * Server-side utilities for token verification in Next.js API routes and middleware.
233
+ * This module runs on the server only — do not import in client components.
234
+ */
235
+ interface AuthPayload {
236
+ userId: string;
237
+ email: string;
238
+ orgId: string | null;
239
+ orgSlug: string | null;
240
+ role: 'owner' | 'admin' | 'member' | null;
241
+ tier: 'free' | 'basic' | 'pro' | 'enterprise' | null;
242
+ }
243
+ interface VerifyOptions {
244
+ /**
245
+ * Secret key for local JWT verification. If provided, tokens are verified
246
+ * locally without making an API call (~1ms vs ~50-100ms).
247
+ * Must match the JWT_SECRET used by the GitHat backend.
248
+ */
249
+ secretKey?: string;
250
+ /**
251
+ * API URL for remote token verification. Defaults to https://api.githat.io
252
+ */
253
+ apiUrl?: string;
254
+ }
255
+ interface OrgMetadata {
256
+ [key: string]: unknown;
257
+ }
258
+
259
+ export { type AuthActions, type AuthPayload, type AuthState, type BatchOperation, type BatchResult, type DataItem, type DeleteResult, type GitHatConfig, type GitHatContextValue, type GitHatOrg, GitHatProvider, type GitHatUser, type OrgMetadata, OrgSwitcher, ProtectedRoute, type PutResult, type QueryOptions, type QueryResult, SignInButton, SignInForm, SignUpButton, type SignUpData, SignUpForm, type SignUpResult, UserButton, VerifiedBadge, type VerifyOptions, useAuth, useData, useGitHat };
package/dist/index.d.ts CHANGED
@@ -22,6 +22,17 @@ interface GitHatConfig {
22
22
  signUpUrl?: string;
23
23
  afterSignInUrl?: string;
24
24
  afterSignOutUrl?: string;
25
+ /**
26
+ * Token storage mode:
27
+ * - 'localStorage' (default): Tokens stored in browser localStorage
28
+ * - 'cookie': Tokens stored in httpOnly cookies (more secure, XSS-resistant)
29
+ *
30
+ * When using 'cookie' mode:
31
+ * - Login/refresh automatically set httpOnly cookies
32
+ * - SDK reads auth state from cookies (server-side)
33
+ * - Better for apps with server-side rendering
34
+ */
35
+ tokenStorage?: 'localStorage' | 'cookie';
25
36
  }
26
37
  interface AuthState {
27
38
  user: GitHatUser | null;
@@ -56,9 +67,12 @@ interface GitHatProviderProps {
56
67
  }
57
68
  declare function GitHatProvider({ config: rawConfig, children }: GitHatProviderProps): react_jsx_runtime.JSX.Element;
58
69
 
70
+ interface OrgMetadata$1 {
71
+ [key: string]: unknown;
72
+ }
59
73
  declare function useAuth(): GitHatContextValue;
60
74
  declare function useGitHat(): {
61
- fetch: <T = any>(endpoint: string, options?: RequestInit) => Promise<T>;
75
+ fetch: <T = unknown>(endpoint: string, fetchOptions?: RequestInit) => Promise<T>;
62
76
  getUserOrgs: () => Promise<{
63
77
  orgs: GitHatOrg[];
64
78
  }>;
@@ -68,6 +82,8 @@ declare function useGitHat(): {
68
82
  verifyAgent: (wallet: string) => Promise<{
69
83
  verified: boolean;
70
84
  }>;
85
+ getOrgMetadata: () => Promise<OrgMetadata$1>;
86
+ updateOrgMetadata: (updates: OrgMetadata$1) => Promise<OrgMetadata$1>;
71
87
  };
72
88
 
73
89
  interface DataItem {
@@ -210,4 +226,34 @@ interface ProtectedRouteProps {
210
226
  }
211
227
  declare function ProtectedRoute({ children, fallback }: ProtectedRouteProps): react_jsx_runtime.JSX.Element | null;
212
228
 
213
- export { type AuthActions, type AuthState, type BatchOperation, type BatchResult, type DataItem, type DeleteResult, type GitHatConfig, type GitHatContextValue, type GitHatOrg, GitHatProvider, type GitHatUser, OrgSwitcher, ProtectedRoute, type PutResult, type QueryOptions, type QueryResult, SignInButton, SignInForm, SignUpButton, type SignUpData, SignUpForm, type SignUpResult, UserButton, VerifiedBadge, useAuth, useData, useGitHat };
229
+ /**
230
+ * @githat/nextjs/server
231
+ *
232
+ * Server-side utilities for token verification in Next.js API routes and middleware.
233
+ * This module runs on the server only — do not import in client components.
234
+ */
235
+ interface AuthPayload {
236
+ userId: string;
237
+ email: string;
238
+ orgId: string | null;
239
+ orgSlug: string | null;
240
+ role: 'owner' | 'admin' | 'member' | null;
241
+ tier: 'free' | 'basic' | 'pro' | 'enterprise' | null;
242
+ }
243
+ interface VerifyOptions {
244
+ /**
245
+ * Secret key for local JWT verification. If provided, tokens are verified
246
+ * locally without making an API call (~1ms vs ~50-100ms).
247
+ * Must match the JWT_SECRET used by the GitHat backend.
248
+ */
249
+ secretKey?: string;
250
+ /**
251
+ * API URL for remote token verification. Defaults to https://api.githat.io
252
+ */
253
+ apiUrl?: string;
254
+ }
255
+ interface OrgMetadata {
256
+ [key: string]: unknown;
257
+ }
258
+
259
+ export { type AuthActions, type AuthPayload, type AuthState, type BatchOperation, type BatchResult, type DataItem, type DeleteResult, type GitHatConfig, type GitHatContextValue, type GitHatOrg, GitHatProvider, type GitHatUser, type OrgMetadata, OrgSwitcher, ProtectedRoute, type PutResult, type QueryOptions, type QueryResult, SignInButton, SignInForm, SignUpButton, type SignUpData, SignUpForm, type SignUpResult, UserButton, VerifiedBadge, type VerifyOptions, useAuth, useData, useGitHat };
package/dist/index.js CHANGED
@@ -54,15 +54,16 @@ function resolveConfig(config) {
54
54
  signInUrl: config.signInUrl || "/sign-in",
55
55
  signUpUrl: config.signUpUrl || "/sign-up",
56
56
  afterSignInUrl: config.afterSignInUrl || "/dashboard",
57
- afterSignOutUrl: config.afterSignOutUrl || "/"
57
+ afterSignOutUrl: config.afterSignOutUrl || "/",
58
+ tokenStorage: config.tokenStorage || "localStorage"
58
59
  };
59
60
  }
60
61
 
61
62
  // src/client.ts
62
63
  var _refreshPromise = null;
63
- async function refreshTokens(apiUrl, appKey) {
64
- const refreshToken = typeof window !== "undefined" ? localStorage.getItem(TOKEN_KEYS.refreshToken) : null;
65
- if (!refreshToken) return false;
64
+ async function refreshTokens(apiUrl, appKey, useCookies) {
65
+ const refreshToken = typeof window !== "undefined" && !useCookies ? localStorage.getItem(TOKEN_KEYS.refreshToken) : null;
66
+ if (!useCookies && !refreshToken) return false;
66
67
  let orgId = null;
67
68
  try {
68
69
  const orgStr = localStorage.getItem(TOKEN_KEYS.org);
@@ -70,18 +71,22 @@ async function refreshTokens(apiUrl, appKey) {
70
71
  } catch {
71
72
  }
72
73
  try {
73
- const res = await fetch(`${apiUrl}/auth/refresh`, {
74
+ const refreshUrl = useCookies ? `${apiUrl}/auth/refresh?setCookie=true` : `${apiUrl}/auth/refresh`;
75
+ const res = await fetch(refreshUrl, {
74
76
  method: "POST",
75
77
  headers: {
76
78
  "Content-Type": "application/json",
77
79
  "X-GitHat-App-Key": appKey
78
80
  },
79
- body: JSON.stringify({ refreshToken, orgId })
81
+ credentials: useCookies ? "include" : "same-origin",
82
+ body: JSON.stringify(useCookies ? { orgId } : { refreshToken, orgId })
80
83
  });
81
84
  if (!res.ok) return false;
82
85
  const data = await res.json();
83
- if (data.accessToken) localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
84
- if (data.refreshToken) localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
86
+ if (!useCookies) {
87
+ if (data.accessToken) localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
88
+ if (data.refreshToken) localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
89
+ }
85
90
  if (data.org) localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(data.org));
86
91
  return true;
87
92
  } catch {
@@ -91,23 +96,30 @@ async function refreshTokens(apiUrl, appKey) {
91
96
  function clearAuth() {
92
97
  if (typeof window === "undefined") return;
93
98
  Object.values(TOKEN_KEYS).forEach((key) => localStorage.removeItem(key));
94
- window.dispatchEvent(new CustomEvent("githat:auth-changed", {
95
- detail: { user: null, org: null, signedIn: false }
96
- }));
99
+ window.dispatchEvent(
100
+ new CustomEvent("githat:auth-changed", {
101
+ detail: { user: null, org: null, signedIn: false }
102
+ })
103
+ );
97
104
  }
98
- function createClient(apiUrl, appKey) {
99
- async function fetchApi(endpoint, options = {}) {
105
+ function createClient(apiUrl, appKey, options = {}) {
106
+ const { useCookies = false } = options;
107
+ async function fetchApi(endpoint, fetchOptions = {}) {
100
108
  const url = `${apiUrl}${endpoint}`;
101
- const token = typeof window !== "undefined" ? localStorage.getItem(TOKEN_KEYS.accessToken) : null;
109
+ const token = typeof window !== "undefined" && !useCookies ? localStorage.getItem(TOKEN_KEYS.accessToken) : null;
102
110
  const headers = {
103
111
  "Content-Type": "application/json",
104
112
  "X-GitHat-App-Key": appKey,
105
113
  ...token && { Authorization: `Bearer ${token}` },
106
- ...options.headers
114
+ ...fetchOptions.headers
107
115
  };
108
116
  let response;
109
117
  try {
110
- response = await fetch(url, { ...options, headers });
118
+ response = await fetch(url, {
119
+ ...fetchOptions,
120
+ headers,
121
+ credentials: useCookies ? "include" : "same-origin"
122
+ });
111
123
  } catch (networkError) {
112
124
  if (networkError instanceof TypeError) {
113
125
  const isMissingKey = !appKey || !appKey.startsWith("pk_live_");
@@ -117,27 +129,26 @@ function createClient(apiUrl, appKey) {
117
129
  "Missing GitHat API key. Add NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY to .env.local"
118
130
  );
119
131
  }
120
- throw new Error(
121
- "Unable to connect to GitHat API. Check your network connection."
122
- );
132
+ throw new Error("Unable to connect to GitHat API. Check your network connection.");
123
133
  }
124
134
  throw networkError;
125
135
  }
126
136
  if (response.status === 401) {
127
137
  if (!_refreshPromise) {
128
- _refreshPromise = refreshTokens(apiUrl, appKey).finally(() => {
138
+ _refreshPromise = refreshTokens(apiUrl, appKey, useCookies).finally(() => {
129
139
  _refreshPromise = null;
130
140
  });
131
141
  }
132
142
  const refreshed = await _refreshPromise;
133
143
  if (refreshed) {
134
- const newToken = localStorage.getItem(TOKEN_KEYS.accessToken);
144
+ const newToken = !useCookies && typeof window !== "undefined" ? localStorage.getItem(TOKEN_KEYS.accessToken) : null;
135
145
  const retryResponse = await fetch(url, {
136
- ...options,
146
+ ...fetchOptions,
137
147
  headers: {
138
148
  ...headers,
139
149
  ...newToken && { Authorization: `Bearer ${newToken}` }
140
- }
150
+ },
151
+ credentials: useCookies ? "include" : "same-origin"
141
152
  });
142
153
  const retryData = await retryResponse.json();
143
154
  if (!retryResponse.ok) throw new Error(retryData.error || "Request failed");
@@ -158,38 +169,57 @@ var import_jsx_runtime = require("react/jsx-runtime");
158
169
  var GitHatContext = (0, import_react.createContext)(null);
159
170
  function GitHatProvider({ config: rawConfig, children }) {
160
171
  const config = (0, import_react.useMemo)(() => resolveConfig(rawConfig), [rawConfig]);
161
- const clientRef = (0, import_react.useRef)(createClient(config.apiUrl, config.publishableKey));
172
+ const useCookies = config.tokenStorage === "cookie";
173
+ const clientRef = (0, import_react.useRef)(createClient(config.apiUrl, config.publishableKey, { useCookies }));
162
174
  const [user, setUser] = (0, import_react.useState)(null);
163
175
  const [org, setOrg] = (0, import_react.useState)(null);
164
176
  const [isSignedIn, setIsSignedIn] = (0, import_react.useState)(false);
165
177
  const [isLoading, setIsLoading] = (0, import_react.useState)(true);
166
178
  const [authError, setAuthError] = (0, import_react.useState)(null);
167
179
  (0, import_react.useEffect)(() => {
168
- const token = localStorage.getItem(TOKEN_KEYS.accessToken);
169
- const storedUser = localStorage.getItem(TOKEN_KEYS.user);
170
- if (token && storedUser) {
171
- clientRef.current.fetchApi("/auth/me").then((data) => {
172
- const u = data.user || JSON.parse(storedUser);
173
- setUser(u);
174
- const storedOrg = localStorage.getItem(TOKEN_KEYS.org);
175
- setOrg(data.currentOrg || (storedOrg ? JSON.parse(storedOrg) : null));
176
- setIsSignedIn(true);
177
- setAuthError(null);
178
- }).catch((err) => {
179
- if (err.message === "Session expired") {
180
+ const validateSession = async () => {
181
+ try {
182
+ if (!useCookies) {
183
+ const token = localStorage.getItem(TOKEN_KEYS.accessToken);
184
+ const storedUser = localStorage.getItem(TOKEN_KEYS.user);
185
+ if (!token || !storedUser) {
186
+ setIsLoading(false);
187
+ return;
188
+ }
189
+ }
190
+ const data = await clientRef.current.fetchApi("/auth/me");
191
+ if (data.user) {
192
+ setUser(data.user);
193
+ setOrg(data.currentOrg || null);
194
+ setIsSignedIn(true);
195
+ setAuthError(null);
196
+ if (!useCookies) {
197
+ localStorage.setItem(TOKEN_KEYS.user, JSON.stringify(data.user));
198
+ if (data.currentOrg) {
199
+ localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(data.currentOrg));
200
+ }
201
+ }
202
+ }
203
+ } catch (err) {
204
+ const error = err;
205
+ if (error.message === "Session expired") {
180
206
  clientRef.current.clearAuth();
181
- } else {
182
- try {
183
- setUser(JSON.parse(storedUser));
184
- } catch {
207
+ } else if (!useCookies) {
208
+ const storedUser = localStorage.getItem(TOKEN_KEYS.user);
209
+ if (storedUser) {
210
+ try {
211
+ setUser(JSON.parse(storedUser));
212
+ setIsSignedIn(true);
213
+ } catch {
214
+ }
185
215
  }
186
- setAuthError(err.message || "Failed to verify session");
216
+ setAuthError(error.message || "Failed to verify session");
187
217
  }
188
- }).finally(() => setIsLoading(false));
189
- } else {
218
+ }
190
219
  setIsLoading(false);
191
- }
192
- }, []);
220
+ };
221
+ validateSession();
222
+ }, [useCookies]);
193
223
  (0, import_react.useEffect)(() => {
194
224
  const handleAuthChanged = (e) => {
195
225
  const detail = e.detail;
@@ -207,30 +237,36 @@ function GitHatProvider({ config: rawConfig, children }) {
207
237
  return () => window.removeEventListener("githat:auth-changed", handleAuthChanged);
208
238
  }, []);
209
239
  const signIn = (0, import_react.useCallback)(async (email, password) => {
210
- const data = await clientRef.current.fetchApi("/auth/login", {
240
+ const loginUrl = useCookies ? "/auth/login?setCookie=true" : "/auth/login";
241
+ const data = await clientRef.current.fetchApi(loginUrl, {
211
242
  method: "POST",
212
243
  body: JSON.stringify({ email, password })
213
244
  });
214
- localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
215
- localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
216
- localStorage.setItem(TOKEN_KEYS.user, JSON.stringify(data.user));
217
- if (data.org) localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(data.org));
245
+ if (!useCookies && data.accessToken && data.refreshToken) {
246
+ localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
247
+ localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
248
+ localStorage.setItem(TOKEN_KEYS.user, JSON.stringify(data.user));
249
+ if (data.org) localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(data.org));
250
+ }
218
251
  setUser(data.user);
219
252
  setOrg(data.org || null);
220
253
  setIsSignedIn(true);
221
254
  window.dispatchEvent(new CustomEvent("githat:auth-changed", {
222
255
  detail: { user: data.user, org: data.org, signedIn: true }
223
256
  }));
224
- }, []);
257
+ }, [useCookies]);
225
258
  const signUp = (0, import_react.useCallback)(async (signUpData) => {
226
- const data = await clientRef.current.fetchApi("/auth/register", {
259
+ const registerUrl = useCookies ? "/auth/register?setCookie=true" : "/auth/register";
260
+ const data = await clientRef.current.fetchApi(registerUrl, {
227
261
  method: "POST",
228
262
  body: JSON.stringify(signUpData)
229
263
  });
230
- localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
231
- localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
232
- localStorage.setItem(TOKEN_KEYS.user, JSON.stringify(data.user));
233
- if (data.org) localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(data.org));
264
+ if (!useCookies && data.accessToken && data.refreshToken) {
265
+ localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
266
+ localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
267
+ localStorage.setItem(TOKEN_KEYS.user, JSON.stringify(data.user));
268
+ if (data.org) localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(data.org));
269
+ }
234
270
  setUser(data.user);
235
271
  setOrg(data.org || null);
236
272
  setIsSignedIn(true);
@@ -238,10 +274,11 @@ function GitHatProvider({ config: rawConfig, children }) {
238
274
  detail: { user: data.user, org: data.org, signedIn: true }
239
275
  }));
240
276
  return { requiresVerification: !data.user.emailVerified, email: signUpData.email };
241
- }, []);
277
+ }, [useCookies]);
242
278
  const signOut = (0, import_react.useCallback)(async () => {
243
279
  try {
244
- await clientRef.current.fetchApi("/auth/logout", { method: "POST" });
280
+ const logoutUrl = useCookies ? "/auth/logout?setCookie=true" : "/auth/logout";
281
+ await clientRef.current.fetchApi(logoutUrl, { method: "POST" });
245
282
  } catch {
246
283
  }
247
284
  clientRef.current.clearAuth();
@@ -251,22 +288,24 @@ function GitHatProvider({ config: rawConfig, children }) {
251
288
  if (typeof window !== "undefined" && config.afterSignOutUrl) {
252
289
  window.location.href = config.afterSignOutUrl;
253
290
  }
254
- }, [config.afterSignOutUrl]);
291
+ }, [config.afterSignOutUrl, useCookies]);
255
292
  const switchOrg = (0, import_react.useCallback)(async (orgId) => {
256
293
  try {
257
- const data = await clientRef.current.fetchApi(`/user/orgs/${orgId}/switch`, { method: "POST" });
258
- if (data.accessToken) localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
259
- if (data.refreshToken) localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
260
- const orgData = data.org;
261
- localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(orgData));
262
- setOrg(orgData);
294
+ const switchUrl = useCookies ? `/user/orgs/${orgId}/switch?setCookie=true` : `/user/orgs/${orgId}/switch`;
295
+ const data = await clientRef.current.fetchApi(switchUrl, { method: "POST" });
296
+ if (!useCookies) {
297
+ if (data.accessToken) localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
298
+ if (data.refreshToken) localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
299
+ localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(data.org));
300
+ }
301
+ setOrg(data.org);
263
302
  window.dispatchEvent(new CustomEvent("githat:auth-changed", {
264
- detail: { user, org: orgData, signedIn: true }
303
+ detail: { user, org: data.org, signedIn: true }
265
304
  }));
266
305
  } catch (e) {
267
306
  console.error("Org switch failed:", e);
268
307
  }
269
- }, [user]);
308
+ }, [user, useCookies]);
270
309
  const value = (0, import_react.useMemo)(() => ({
271
310
  user,
272
311
  org,
@@ -295,11 +334,38 @@ function useGitHat() {
295
334
  () => createClient(ctx.config.apiUrl, ctx.config.publishableKey),
296
335
  [ctx.config.apiUrl, ctx.config.publishableKey]
297
336
  );
337
+ const getOrgMetadata = (0, import_react2.useCallback)(async () => {
338
+ if (!ctx.org?.id) {
339
+ throw new Error("No active organization");
340
+ }
341
+ const response = await client.fetchApi(
342
+ `/orgs/${ctx.org.id}/metadata`
343
+ );
344
+ return response.metadata || {};
345
+ }, [client, ctx.org?.id]);
346
+ const updateOrgMetadata = (0, import_react2.useCallback)(
347
+ async (updates) => {
348
+ if (!ctx.org?.id) {
349
+ throw new Error("No active organization");
350
+ }
351
+ const response = await client.fetchApi(
352
+ `/orgs/${ctx.org.id}/metadata`,
353
+ {
354
+ method: "PATCH",
355
+ body: JSON.stringify(updates)
356
+ }
357
+ );
358
+ return response.metadata || {};
359
+ },
360
+ [client, ctx.org?.id]
361
+ );
298
362
  return {
299
363
  fetch: client.fetchApi,
300
364
  getUserOrgs: () => client.fetchApi("/user/orgs"),
301
365
  verifyMCP: (domain) => client.fetchApi(`/verify/mcp/${domain}`),
302
- verifyAgent: (wallet) => client.fetchApi(`/verify/agent/${wallet}`)
366
+ verifyAgent: (wallet) => client.fetchApi(`/verify/agent/${wallet}`),
367
+ getOrgMetadata,
368
+ updateOrgMetadata
303
369
  };
304
370
  }
305
371