@vaultix.ai/react 0.2.0 → 0.2.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.js CHANGED
@@ -60,6 +60,26 @@ function resolveApiOrigin(key, apiUrlProp) {
60
60
  throw new Error("Publishable key has an unreadable API origin payload.");
61
61
  }
62
62
  }
63
+ var STORAGE_KEY = "vaultix_jwt";
64
+ function storeJwt(jwt) {
65
+ try {
66
+ sessionStorage.setItem(STORAGE_KEY, jwt);
67
+ } catch {
68
+ }
69
+ }
70
+ function loadJwt() {
71
+ try {
72
+ return sessionStorage.getItem(STORAGE_KEY);
73
+ } catch {
74
+ return null;
75
+ }
76
+ }
77
+ function clearJwt() {
78
+ try {
79
+ sessionStorage.removeItem(STORAGE_KEY);
80
+ } catch {
81
+ }
82
+ }
63
83
  var VaultixContext = (0, import_react.createContext)(null);
64
84
  function useVaultixContext() {
65
85
  const ctx = (0, import_react.useContext)(VaultixContext);
@@ -82,42 +102,89 @@ function VaultixProvider({
82
102
  const [user, setUser] = (0, import_react.useState)(null);
83
103
  const [session, setSession] = (0, import_react.useState)(null);
84
104
  const [organization, setOrganization] = (0, import_react.useState)(null);
105
+ const jwtRef = (0, import_react.useRef)(null);
85
106
  const refreshTimerRef = (0, import_react.useRef)(null);
107
+ const clearAuth = (0, import_react.useCallback)(() => {
108
+ jwtRef.current = null;
109
+ clearJwt();
110
+ setUser(null);
111
+ setSession(null);
112
+ setOrganization(null);
113
+ }, []);
86
114
  const scheduleRefresh = (0, import_react.useCallback)(
87
115
  (expiresAt) => {
88
116
  if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
89
- const msUntilRefresh = expiresAt * 1e3 - Date.now() - 1e4;
117
+ const msUntilRefresh = expiresAt * 1e3 - Date.now() - 3e4;
90
118
  if (msUntilRefresh <= 0) return;
91
119
  refreshTimerRef.current = setTimeout(async () => {
92
120
  try {
93
- const res = await fetch(`${apiOrigin}/v1/session/refresh`, {
121
+ const headers = {};
122
+ if (jwtRef.current) headers["Authorization"] = `Bearer ${jwtRef.current}`;
123
+ const res = await fetch(`${apiOrigin}/api/v1/session/refresh`, {
94
124
  method: "POST",
95
- credentials: "include"
125
+ credentials: jwtRef.current ? "omit" : "include",
126
+ headers
96
127
  });
97
128
  if (!res.ok) {
98
- setUser(null);
99
- setSession(null);
100
- setOrganization(null);
129
+ clearAuth();
101
130
  return;
102
131
  }
103
132
  const data = await res.json();
133
+ if (data.session_jwt) {
134
+ jwtRef.current = data.session_jwt;
135
+ storeJwt(data.session_jwt);
136
+ }
104
137
  setSession(data.session);
105
138
  scheduleRefresh(data.session.expiresAt);
106
139
  } catch {
107
140
  }
108
141
  }, msUntilRefresh);
109
142
  },
110
- [apiOrigin]
143
+ [apiOrigin, clearAuth]
111
144
  );
112
145
  (0, import_react.useEffect)(() => {
113
146
  let cancelled = false;
114
147
  async function hydrate() {
148
+ let jwt = null;
149
+ if (typeof window !== "undefined") {
150
+ const url = new URL(window.location.href);
151
+ const handshakeToken = url.searchParams.get("__vaultix_handshake");
152
+ if (handshakeToken) {
153
+ url.searchParams.delete("__vaultix_handshake");
154
+ window.history.replaceState({}, "", url.toString());
155
+ try {
156
+ const res = await fetch(`${apiOrigin}/api/v1/tokens/exchange`, {
157
+ method: "POST",
158
+ headers: { "Content-Type": "application/json" },
159
+ body: JSON.stringify({ handshake_token: handshakeToken })
160
+ });
161
+ if (res.ok) {
162
+ const data = await res.json();
163
+ jwt = data.session_jwt;
164
+ storeJwt(jwt);
165
+ }
166
+ } catch {
167
+ }
168
+ } else {
169
+ jwt = loadJwt();
170
+ }
171
+ jwtRef.current = jwt;
172
+ }
115
173
  try {
116
- const res = await fetch(`${apiOrigin}/v1/me`, {
117
- credentials: "include",
118
- headers: { "X-Vaultix-Publishable-Key": publishableKey }
174
+ const headers = {
175
+ "X-Vaultix-Publishable-Key": publishableKey
176
+ };
177
+ if (jwt) headers["Authorization"] = `Bearer ${jwt}`;
178
+ const res = await fetch(`${apiOrigin}/api/v1/me`, {
179
+ // Use Bearer token for cross-domain; fall back to cookie for same-domain
180
+ credentials: jwt ? "omit" : "include",
181
+ headers
119
182
  });
120
183
  if (!res.ok) {
184
+ if (jwt) {
185
+ clearJwt();
186
+ jwtRef.current = null;
187
+ }
121
188
  if (!cancelled) setIsLoaded(true);
122
189
  return;
123
190
  }
@@ -140,19 +207,21 @@ function VaultixProvider({
140
207
  };
141
208
  }, [apiOrigin, publishableKey, scheduleRefresh]);
142
209
  const signOut = (0, import_react.useCallback)(async () => {
210
+ const jwt = jwtRef.current;
211
+ clearAuth();
212
+ if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
143
213
  try {
144
- await fetch(`${apiOrigin}/v1/session`, {
214
+ const headers = {};
215
+ if (jwt) headers["Authorization"] = `Bearer ${jwt}`;
216
+ await fetch(`${apiOrigin}/api/v1/session`, {
145
217
  method: "DELETE",
146
- credentials: "include"
218
+ credentials: jwt ? "omit" : "include",
219
+ headers
147
220
  });
148
221
  } catch {
149
- } finally {
150
- setUser(null);
151
- setSession(null);
152
- setOrganization(null);
153
- if (afterSignInUrl) window.location.href = afterSignInUrl;
154
222
  }
155
- }, [apiOrigin, afterSignInUrl]);
223
+ if (afterSignInUrl) window.location.href = afterSignInUrl;
224
+ }, [apiOrigin, afterSignInUrl, clearAuth]);
156
225
  const value = {
157
226
  user,
158
227
  session,
package/dist/index.mjs CHANGED
@@ -28,6 +28,26 @@ function resolveApiOrigin(key, apiUrlProp) {
28
28
  throw new Error("Publishable key has an unreadable API origin payload.");
29
29
  }
30
30
  }
31
+ var STORAGE_KEY = "vaultix_jwt";
32
+ function storeJwt(jwt) {
33
+ try {
34
+ sessionStorage.setItem(STORAGE_KEY, jwt);
35
+ } catch {
36
+ }
37
+ }
38
+ function loadJwt() {
39
+ try {
40
+ return sessionStorage.getItem(STORAGE_KEY);
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+ function clearJwt() {
46
+ try {
47
+ sessionStorage.removeItem(STORAGE_KEY);
48
+ } catch {
49
+ }
50
+ }
31
51
  var VaultixContext = createContext(null);
32
52
  function useVaultixContext() {
33
53
  const ctx = useContext(VaultixContext);
@@ -50,42 +70,89 @@ function VaultixProvider({
50
70
  const [user, setUser] = useState(null);
51
71
  const [session, setSession] = useState(null);
52
72
  const [organization, setOrganization] = useState(null);
73
+ const jwtRef = useRef(null);
53
74
  const refreshTimerRef = useRef(null);
75
+ const clearAuth = useCallback(() => {
76
+ jwtRef.current = null;
77
+ clearJwt();
78
+ setUser(null);
79
+ setSession(null);
80
+ setOrganization(null);
81
+ }, []);
54
82
  const scheduleRefresh = useCallback(
55
83
  (expiresAt) => {
56
84
  if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
57
- const msUntilRefresh = expiresAt * 1e3 - Date.now() - 1e4;
85
+ const msUntilRefresh = expiresAt * 1e3 - Date.now() - 3e4;
58
86
  if (msUntilRefresh <= 0) return;
59
87
  refreshTimerRef.current = setTimeout(async () => {
60
88
  try {
61
- const res = await fetch(`${apiOrigin}/v1/session/refresh`, {
89
+ const headers = {};
90
+ if (jwtRef.current) headers["Authorization"] = `Bearer ${jwtRef.current}`;
91
+ const res = await fetch(`${apiOrigin}/api/v1/session/refresh`, {
62
92
  method: "POST",
63
- credentials: "include"
93
+ credentials: jwtRef.current ? "omit" : "include",
94
+ headers
64
95
  });
65
96
  if (!res.ok) {
66
- setUser(null);
67
- setSession(null);
68
- setOrganization(null);
97
+ clearAuth();
69
98
  return;
70
99
  }
71
100
  const data = await res.json();
101
+ if (data.session_jwt) {
102
+ jwtRef.current = data.session_jwt;
103
+ storeJwt(data.session_jwt);
104
+ }
72
105
  setSession(data.session);
73
106
  scheduleRefresh(data.session.expiresAt);
74
107
  } catch {
75
108
  }
76
109
  }, msUntilRefresh);
77
110
  },
78
- [apiOrigin]
111
+ [apiOrigin, clearAuth]
79
112
  );
80
113
  useEffect(() => {
81
114
  let cancelled = false;
82
115
  async function hydrate() {
116
+ let jwt = null;
117
+ if (typeof window !== "undefined") {
118
+ const url = new URL(window.location.href);
119
+ const handshakeToken = url.searchParams.get("__vaultix_handshake");
120
+ if (handshakeToken) {
121
+ url.searchParams.delete("__vaultix_handshake");
122
+ window.history.replaceState({}, "", url.toString());
123
+ try {
124
+ const res = await fetch(`${apiOrigin}/api/v1/tokens/exchange`, {
125
+ method: "POST",
126
+ headers: { "Content-Type": "application/json" },
127
+ body: JSON.stringify({ handshake_token: handshakeToken })
128
+ });
129
+ if (res.ok) {
130
+ const data = await res.json();
131
+ jwt = data.session_jwt;
132
+ storeJwt(jwt);
133
+ }
134
+ } catch {
135
+ }
136
+ } else {
137
+ jwt = loadJwt();
138
+ }
139
+ jwtRef.current = jwt;
140
+ }
83
141
  try {
84
- const res = await fetch(`${apiOrigin}/v1/me`, {
85
- credentials: "include",
86
- headers: { "X-Vaultix-Publishable-Key": publishableKey }
142
+ const headers = {
143
+ "X-Vaultix-Publishable-Key": publishableKey
144
+ };
145
+ if (jwt) headers["Authorization"] = `Bearer ${jwt}`;
146
+ const res = await fetch(`${apiOrigin}/api/v1/me`, {
147
+ // Use Bearer token for cross-domain; fall back to cookie for same-domain
148
+ credentials: jwt ? "omit" : "include",
149
+ headers
87
150
  });
88
151
  if (!res.ok) {
152
+ if (jwt) {
153
+ clearJwt();
154
+ jwtRef.current = null;
155
+ }
89
156
  if (!cancelled) setIsLoaded(true);
90
157
  return;
91
158
  }
@@ -108,19 +175,21 @@ function VaultixProvider({
108
175
  };
109
176
  }, [apiOrigin, publishableKey, scheduleRefresh]);
110
177
  const signOut = useCallback(async () => {
178
+ const jwt = jwtRef.current;
179
+ clearAuth();
180
+ if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
111
181
  try {
112
- await fetch(`${apiOrigin}/v1/session`, {
182
+ const headers = {};
183
+ if (jwt) headers["Authorization"] = `Bearer ${jwt}`;
184
+ await fetch(`${apiOrigin}/api/v1/session`, {
113
185
  method: "DELETE",
114
- credentials: "include"
186
+ credentials: jwt ? "omit" : "include",
187
+ headers
115
188
  });
116
189
  } catch {
117
- } finally {
118
- setUser(null);
119
- setSession(null);
120
- setOrganization(null);
121
- if (afterSignInUrl) window.location.href = afterSignInUrl;
122
190
  }
123
- }, [apiOrigin, afterSignInUrl]);
191
+ if (afterSignInUrl) window.location.href = afterSignInUrl;
192
+ }, [apiOrigin, afterSignInUrl, clearAuth]);
124
193
  const value = {
125
194
  user,
126
195
  session,
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "@vaultix.ai/react",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Vaultix-ID React SDK — drop-in auth components for any React app",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
8
- "files": ["dist"],
8
+ "files": [
9
+ "dist"
10
+ ],
9
11
  "exports": {
10
12
  ".": {
11
13
  "types": "./dist/index.d.ts",
@@ -41,7 +43,14 @@
41
43
  "@testing-library/jest-dom": "^6.0.0",
42
44
  "jest-environment-jsdom": "^29.0.0"
43
45
  },
44
- "keywords": ["auth", "authentication", "react", "vaultix", "oauth", "sdk"],
46
+ "keywords": [
47
+ "auth",
48
+ "authentication",
49
+ "react",
50
+ "vaultix",
51
+ "oauth",
52
+ "sdk"
53
+ ],
45
54
  "license": "MIT",
46
55
  "repository": {
47
56
  "type": "git",