@githat/nextjs 0.2.1 → 0.3.1

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.mjs CHANGED
@@ -18,15 +18,16 @@ function resolveConfig(config) {
18
18
  signInUrl: config.signInUrl || "/sign-in",
19
19
  signUpUrl: config.signUpUrl || "/sign-up",
20
20
  afterSignInUrl: config.afterSignInUrl || "/dashboard",
21
- afterSignOutUrl: config.afterSignOutUrl || "/"
21
+ afterSignOutUrl: config.afterSignOutUrl || "/",
22
+ tokenStorage: config.tokenStorage || "localStorage"
22
23
  };
23
24
  }
24
25
 
25
26
  // src/client.ts
26
27
  var _refreshPromise = null;
27
- async function refreshTokens(apiUrl, appKey) {
28
- const refreshToken = typeof window !== "undefined" ? localStorage.getItem(TOKEN_KEYS.refreshToken) : null;
29
- if (!refreshToken) return false;
28
+ async function refreshTokens(apiUrl, appKey, useCookies) {
29
+ const refreshToken = typeof window !== "undefined" && !useCookies ? localStorage.getItem(TOKEN_KEYS.refreshToken) : null;
30
+ if (!useCookies && !refreshToken) return false;
30
31
  let orgId = null;
31
32
  try {
32
33
  const orgStr = localStorage.getItem(TOKEN_KEYS.org);
@@ -34,18 +35,22 @@ async function refreshTokens(apiUrl, appKey) {
34
35
  } catch {
35
36
  }
36
37
  try {
37
- const res = await fetch(`${apiUrl}/auth/refresh`, {
38
+ const refreshUrl = useCookies ? `${apiUrl}/auth/refresh?setCookie=true` : `${apiUrl}/auth/refresh`;
39
+ const res = await fetch(refreshUrl, {
38
40
  method: "POST",
39
41
  headers: {
40
42
  "Content-Type": "application/json",
41
43
  "X-GitHat-App-Key": appKey
42
44
  },
43
- body: JSON.stringify({ refreshToken, orgId })
45
+ credentials: useCookies ? "include" : "same-origin",
46
+ body: JSON.stringify(useCookies ? { orgId } : { refreshToken, orgId })
44
47
  });
45
48
  if (!res.ok) return false;
46
49
  const data = await res.json();
47
- if (data.accessToken) localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
48
- if (data.refreshToken) localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
50
+ if (!useCookies) {
51
+ if (data.accessToken) localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
52
+ if (data.refreshToken) localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
53
+ }
49
54
  if (data.org) localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(data.org));
50
55
  return true;
51
56
  } catch {
@@ -55,23 +60,30 @@ async function refreshTokens(apiUrl, appKey) {
55
60
  function clearAuth() {
56
61
  if (typeof window === "undefined") return;
57
62
  Object.values(TOKEN_KEYS).forEach((key) => localStorage.removeItem(key));
58
- window.dispatchEvent(new CustomEvent("githat:auth-changed", {
59
- detail: { user: null, org: null, signedIn: false }
60
- }));
63
+ window.dispatchEvent(
64
+ new CustomEvent("githat:auth-changed", {
65
+ detail: { user: null, org: null, signedIn: false }
66
+ })
67
+ );
61
68
  }
62
- function createClient(apiUrl, appKey) {
63
- async function fetchApi(endpoint, options = {}) {
69
+ function createClient(apiUrl, appKey, options = {}) {
70
+ const { useCookies = false } = options;
71
+ async function fetchApi(endpoint, fetchOptions = {}) {
64
72
  const url = `${apiUrl}${endpoint}`;
65
- const token = typeof window !== "undefined" ? localStorage.getItem(TOKEN_KEYS.accessToken) : null;
73
+ const token = typeof window !== "undefined" && !useCookies ? localStorage.getItem(TOKEN_KEYS.accessToken) : null;
66
74
  const headers = {
67
75
  "Content-Type": "application/json",
68
76
  "X-GitHat-App-Key": appKey,
69
77
  ...token && { Authorization: `Bearer ${token}` },
70
- ...options.headers
78
+ ...fetchOptions.headers
71
79
  };
72
80
  let response;
73
81
  try {
74
- response = await fetch(url, { ...options, headers });
82
+ response = await fetch(url, {
83
+ ...fetchOptions,
84
+ headers,
85
+ credentials: useCookies ? "include" : "same-origin"
86
+ });
75
87
  } catch (networkError) {
76
88
  if (networkError instanceof TypeError) {
77
89
  const isMissingKey = !appKey || !appKey.startsWith("pk_live_");
@@ -81,27 +93,26 @@ function createClient(apiUrl, appKey) {
81
93
  "Missing GitHat API key. Add NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY to .env.local"
82
94
  );
83
95
  }
84
- throw new Error(
85
- "Unable to connect to GitHat API. Check your network connection."
86
- );
96
+ throw new Error("Unable to connect to GitHat API. Check your network connection.");
87
97
  }
88
98
  throw networkError;
89
99
  }
90
100
  if (response.status === 401) {
91
101
  if (!_refreshPromise) {
92
- _refreshPromise = refreshTokens(apiUrl, appKey).finally(() => {
102
+ _refreshPromise = refreshTokens(apiUrl, appKey, useCookies).finally(() => {
93
103
  _refreshPromise = null;
94
104
  });
95
105
  }
96
106
  const refreshed = await _refreshPromise;
97
107
  if (refreshed) {
98
- const newToken = localStorage.getItem(TOKEN_KEYS.accessToken);
108
+ const newToken = !useCookies && typeof window !== "undefined" ? localStorage.getItem(TOKEN_KEYS.accessToken) : null;
99
109
  const retryResponse = await fetch(url, {
100
- ...options,
110
+ ...fetchOptions,
101
111
  headers: {
102
112
  ...headers,
103
113
  ...newToken && { Authorization: `Bearer ${newToken}` }
104
- }
114
+ },
115
+ credentials: useCookies ? "include" : "same-origin"
105
116
  });
106
117
  const retryData = await retryResponse.json();
107
118
  if (!retryResponse.ok) throw new Error(retryData.error || "Request failed");
@@ -122,38 +133,57 @@ import { jsx } from "react/jsx-runtime";
122
133
  var GitHatContext = createContext(null);
123
134
  function GitHatProvider({ config: rawConfig, children }) {
124
135
  const config = useMemo(() => resolveConfig(rawConfig), [rawConfig]);
125
- const clientRef = useRef(createClient(config.apiUrl, config.publishableKey));
136
+ const useCookies = config.tokenStorage === "cookie";
137
+ const clientRef = useRef(createClient(config.apiUrl, config.publishableKey, { useCookies }));
126
138
  const [user, setUser] = useState(null);
127
139
  const [org, setOrg] = useState(null);
128
140
  const [isSignedIn, setIsSignedIn] = useState(false);
129
141
  const [isLoading, setIsLoading] = useState(true);
130
142
  const [authError, setAuthError] = useState(null);
131
143
  useEffect(() => {
132
- const token = localStorage.getItem(TOKEN_KEYS.accessToken);
133
- const storedUser = localStorage.getItem(TOKEN_KEYS.user);
134
- if (token && storedUser) {
135
- clientRef.current.fetchApi("/auth/me").then((data) => {
136
- const u = data.user || JSON.parse(storedUser);
137
- setUser(u);
138
- const storedOrg = localStorage.getItem(TOKEN_KEYS.org);
139
- setOrg(data.currentOrg || (storedOrg ? JSON.parse(storedOrg) : null));
140
- setIsSignedIn(true);
141
- setAuthError(null);
142
- }).catch((err) => {
143
- if (err.message === "Session expired") {
144
+ const validateSession = async () => {
145
+ try {
146
+ if (!useCookies) {
147
+ const token = localStorage.getItem(TOKEN_KEYS.accessToken);
148
+ const storedUser = localStorage.getItem(TOKEN_KEYS.user);
149
+ if (!token || !storedUser) {
150
+ setIsLoading(false);
151
+ return;
152
+ }
153
+ }
154
+ const data = await clientRef.current.fetchApi("/auth/me");
155
+ if (data.user) {
156
+ setUser(data.user);
157
+ setOrg(data.currentOrg || null);
158
+ setIsSignedIn(true);
159
+ setAuthError(null);
160
+ if (!useCookies) {
161
+ localStorage.setItem(TOKEN_KEYS.user, JSON.stringify(data.user));
162
+ if (data.currentOrg) {
163
+ localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(data.currentOrg));
164
+ }
165
+ }
166
+ }
167
+ } catch (err) {
168
+ const error = err;
169
+ if (error.message === "Session expired") {
144
170
  clientRef.current.clearAuth();
145
- } else {
146
- try {
147
- setUser(JSON.parse(storedUser));
148
- } catch {
171
+ } else if (!useCookies) {
172
+ const storedUser = localStorage.getItem(TOKEN_KEYS.user);
173
+ if (storedUser) {
174
+ try {
175
+ setUser(JSON.parse(storedUser));
176
+ setIsSignedIn(true);
177
+ } catch {
178
+ }
149
179
  }
150
- setAuthError(err.message || "Failed to verify session");
180
+ setAuthError(error.message || "Failed to verify session");
151
181
  }
152
- }).finally(() => setIsLoading(false));
153
- } else {
182
+ }
154
183
  setIsLoading(false);
155
- }
156
- }, []);
184
+ };
185
+ validateSession();
186
+ }, [useCookies]);
157
187
  useEffect(() => {
158
188
  const handleAuthChanged = (e) => {
159
189
  const detail = e.detail;
@@ -171,30 +201,36 @@ function GitHatProvider({ config: rawConfig, children }) {
171
201
  return () => window.removeEventListener("githat:auth-changed", handleAuthChanged);
172
202
  }, []);
173
203
  const signIn = useCallback(async (email, password) => {
174
- const data = await clientRef.current.fetchApi("/auth/login", {
204
+ const loginUrl = useCookies ? "/auth/login?setCookie=true" : "/auth/login";
205
+ const data = await clientRef.current.fetchApi(loginUrl, {
175
206
  method: "POST",
176
207
  body: JSON.stringify({ email, password })
177
208
  });
178
- localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
179
- localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
180
- localStorage.setItem(TOKEN_KEYS.user, JSON.stringify(data.user));
181
- if (data.org) localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(data.org));
209
+ if (!useCookies && data.accessToken && data.refreshToken) {
210
+ localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
211
+ localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
212
+ localStorage.setItem(TOKEN_KEYS.user, JSON.stringify(data.user));
213
+ if (data.org) localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(data.org));
214
+ }
182
215
  setUser(data.user);
183
216
  setOrg(data.org || null);
184
217
  setIsSignedIn(true);
185
218
  window.dispatchEvent(new CustomEvent("githat:auth-changed", {
186
219
  detail: { user: data.user, org: data.org, signedIn: true }
187
220
  }));
188
- }, []);
221
+ }, [useCookies]);
189
222
  const signUp = useCallback(async (signUpData) => {
190
- const data = await clientRef.current.fetchApi("/auth/register", {
223
+ const registerUrl = useCookies ? "/auth/register?setCookie=true" : "/auth/register";
224
+ const data = await clientRef.current.fetchApi(registerUrl, {
191
225
  method: "POST",
192
226
  body: JSON.stringify(signUpData)
193
227
  });
194
- localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
195
- localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
196
- localStorage.setItem(TOKEN_KEYS.user, JSON.stringify(data.user));
197
- if (data.org) localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(data.org));
228
+ if (!useCookies && data.accessToken && data.refreshToken) {
229
+ localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
230
+ localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
231
+ localStorage.setItem(TOKEN_KEYS.user, JSON.stringify(data.user));
232
+ if (data.org) localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(data.org));
233
+ }
198
234
  setUser(data.user);
199
235
  setOrg(data.org || null);
200
236
  setIsSignedIn(true);
@@ -202,10 +238,11 @@ function GitHatProvider({ config: rawConfig, children }) {
202
238
  detail: { user: data.user, org: data.org, signedIn: true }
203
239
  }));
204
240
  return { requiresVerification: !data.user.emailVerified, email: signUpData.email };
205
- }, []);
241
+ }, [useCookies]);
206
242
  const signOut = useCallback(async () => {
207
243
  try {
208
- await clientRef.current.fetchApi("/auth/logout", { method: "POST" });
244
+ const logoutUrl = useCookies ? "/auth/logout?setCookie=true" : "/auth/logout";
245
+ await clientRef.current.fetchApi(logoutUrl, { method: "POST" });
209
246
  } catch {
210
247
  }
211
248
  clientRef.current.clearAuth();
@@ -215,22 +252,24 @@ function GitHatProvider({ config: rawConfig, children }) {
215
252
  if (typeof window !== "undefined" && config.afterSignOutUrl) {
216
253
  window.location.href = config.afterSignOutUrl;
217
254
  }
218
- }, [config.afterSignOutUrl]);
255
+ }, [config.afterSignOutUrl, useCookies]);
219
256
  const switchOrg = useCallback(async (orgId) => {
220
257
  try {
221
- const data = await clientRef.current.fetchApi(`/user/orgs/${orgId}/switch`, { method: "POST" });
222
- if (data.accessToken) localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
223
- if (data.refreshToken) localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
224
- const orgData = data.org;
225
- localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(orgData));
226
- setOrg(orgData);
258
+ const switchUrl = useCookies ? `/user/orgs/${orgId}/switch?setCookie=true` : `/user/orgs/${orgId}/switch`;
259
+ const data = await clientRef.current.fetchApi(switchUrl, { method: "POST" });
260
+ if (!useCookies) {
261
+ if (data.accessToken) localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
262
+ if (data.refreshToken) localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
263
+ localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(data.org));
264
+ }
265
+ setOrg(data.org);
227
266
  window.dispatchEvent(new CustomEvent("githat:auth-changed", {
228
- detail: { user, org: orgData, signedIn: true }
267
+ detail: { user, org: data.org, signedIn: true }
229
268
  }));
230
269
  } catch (e) {
231
270
  console.error("Org switch failed:", e);
232
271
  }
233
- }, [user]);
272
+ }, [user, useCookies]);
234
273
  const value = useMemo(() => ({
235
274
  user,
236
275
  org,
@@ -247,7 +286,7 @@ function GitHatProvider({ config: rawConfig, children }) {
247
286
  }
248
287
 
249
288
  // src/hooks.ts
250
- import { useContext, useMemo as useMemo2 } from "react";
289
+ import { useContext, useMemo as useMemo2, useCallback as useCallback2 } from "react";
251
290
  function useAuth() {
252
291
  const ctx = useContext(GitHatContext);
253
292
  if (!ctx) throw new Error("useAuth must be used within a <GitHatProvider>");
@@ -259,14 +298,119 @@ function useGitHat() {
259
298
  () => createClient(ctx.config.apiUrl, ctx.config.publishableKey),
260
299
  [ctx.config.apiUrl, ctx.config.publishableKey]
261
300
  );
301
+ const getOrgMetadata = useCallback2(async () => {
302
+ if (!ctx.org?.id) {
303
+ throw new Error("No active organization");
304
+ }
305
+ const response = await client.fetchApi(
306
+ `/orgs/${ctx.org.id}/metadata`
307
+ );
308
+ return response.metadata || {};
309
+ }, [client, ctx.org?.id]);
310
+ const updateOrgMetadata = useCallback2(
311
+ async (updates) => {
312
+ if (!ctx.org?.id) {
313
+ throw new Error("No active organization");
314
+ }
315
+ const response = await client.fetchApi(
316
+ `/orgs/${ctx.org.id}/metadata`,
317
+ {
318
+ method: "PATCH",
319
+ body: JSON.stringify(updates)
320
+ }
321
+ );
322
+ return response.metadata || {};
323
+ },
324
+ [client, ctx.org?.id]
325
+ );
262
326
  return {
263
327
  fetch: client.fetchApi,
264
328
  getUserOrgs: () => client.fetchApi("/user/orgs"),
265
329
  verifyMCP: (domain) => client.fetchApi(`/verify/mcp/${domain}`),
266
- verifyAgent: (wallet) => client.fetchApi(`/verify/agent/${wallet}`)
330
+ verifyAgent: (wallet) => client.fetchApi(`/verify/agent/${wallet}`),
331
+ getOrgMetadata,
332
+ updateOrgMetadata
267
333
  };
268
334
  }
269
335
 
336
+ // src/data.ts
337
+ import { useMemo as useMemo3 } from "react";
338
+ function useData() {
339
+ const ctx = useAuth();
340
+ const client = useMemo3(
341
+ () => createClient(ctx.config.apiUrl, ctx.config.publishableKey),
342
+ [ctx.config.apiUrl, ctx.config.publishableKey]
343
+ );
344
+ return useMemo3(() => ({
345
+ /**
346
+ * Store an item in a collection. If the item exists, it will be updated.
347
+ * @param collection - Collection name (e.g., 'orders', 'users')
348
+ * @param data - Data object with required `id` field
349
+ */
350
+ put: async (collection, data) => {
351
+ if (!data.id) {
352
+ throw new Error('Data must include an "id" field');
353
+ }
354
+ return client.fetchApi(`/data/${collection}/${data.id}`, {
355
+ method: "PUT",
356
+ body: JSON.stringify(data)
357
+ });
358
+ },
359
+ /**
360
+ * Get a single item from a collection.
361
+ * @param collection - Collection name
362
+ * @param id - Item ID
363
+ */
364
+ get: async (collection, id) => {
365
+ try {
366
+ const result = await client.fetchApi(`/data/${collection}/${id}`);
367
+ return result.item;
368
+ } catch (err) {
369
+ if (err instanceof Error && err.message === "Item not found") {
370
+ return null;
371
+ }
372
+ throw err;
373
+ }
374
+ },
375
+ /**
376
+ * Query items from a collection with optional filters and pagination.
377
+ * @param collection - Collection name
378
+ * @param options - Query options (limit, cursor, filter)
379
+ */
380
+ query: async (collection, options = {}) => {
381
+ const params = new URLSearchParams();
382
+ if (options.limit) params.set("limit", options.limit.toString());
383
+ if (options.cursor) params.set("cursor", options.cursor);
384
+ if (options.filter) params.set("filter", JSON.stringify(options.filter));
385
+ const queryString = params.toString();
386
+ const url = `/data/${collection}${queryString ? `?${queryString}` : ""}`;
387
+ return client.fetchApi(url);
388
+ },
389
+ /**
390
+ * Delete an item from a collection.
391
+ * @param collection - Collection name
392
+ * @param id - Item ID
393
+ */
394
+ remove: async (collection, id) => {
395
+ return client.fetchApi(`/data/${collection}/${id}`, {
396
+ method: "DELETE"
397
+ });
398
+ },
399
+ /**
400
+ * Batch operations (put/delete) on a collection.
401
+ * Maximum 100 operations per request.
402
+ * @param collection - Collection name
403
+ * @param operations - Array of operations
404
+ */
405
+ batch: async (collection, operations) => {
406
+ return client.fetchApi(`/data/${collection}/batch`, {
407
+ method: "POST",
408
+ body: JSON.stringify({ operations })
409
+ });
410
+ }
411
+ }), [client]);
412
+ }
413
+
270
414
  // src/components/SignInForm.tsx
271
415
  import { useState as useState2 } from "react";
272
416
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
@@ -639,6 +783,7 @@ export {
639
783
  UserButton,
640
784
  VerifiedBadge,
641
785
  useAuth,
786
+ useData,
642
787
  useGitHat
643
788
  };
644
789
  //# sourceMappingURL=index.mjs.map