@hono/auth-js 1.0.11 → 1.0.13

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/react.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  // src/react.tsx
2
- import * as React2 from "react";
2
+ import * as React from "react";
3
3
 
4
4
  // src/client.ts
5
5
  import { AuthError } from "@auth/core/errors";
6
- import * as React from "react";
6
+ import { useEffect, useState } from "react";
7
7
  var ClientFetchError = class extends AuthError {
8
8
  };
9
9
  var ClientSessionError = class extends AuthError {
@@ -34,17 +34,18 @@ async function fetchData(path, config, logger2, req = {}) {
34
34
  }
35
35
  }
36
36
  function useOnline() {
37
- const [isOnline, setIsOnline] = React.useState(
37
+ const [isOnline, setIsOnline] = useState(
38
38
  typeof navigator !== "undefined" ? navigator.onLine : false
39
39
  );
40
- React.useEffect(() => {
40
+ useEffect(() => {
41
+ const abortController = new AbortController();
42
+ const { signal } = abortController;
41
43
  const setOnline = () => setIsOnline(true);
42
44
  const setOffline = () => setIsOnline(false);
43
- window.addEventListener("online", setOnline);
44
- window.addEventListener("offline", setOffline);
45
+ window.addEventListener("online", setOnline, { signal });
46
+ window.addEventListener("offline", setOffline, { signal });
45
47
  return () => {
46
- window.removeEventListener("online", setOnline);
47
- window.removeEventListener("offline", setOffline);
48
+ abortController.abort();
48
49
  };
49
50
  }, []);
50
51
  return isOnline;
@@ -67,17 +68,28 @@ function parseUrl(url) {
67
68
  }
68
69
 
69
70
  // src/react.tsx
71
+ import { useCallback, useContext, useEffect as useEffect2, useMemo, useState as useState3 } from "react";
72
+ var logger = {
73
+ debug: console.debug,
74
+ error: console.error,
75
+ warn: console.warn
76
+ };
70
77
  var AuthConfigManager = class _AuthConfigManager {
71
78
  static instance = null;
72
- _config = {
73
- baseUrl: typeof window !== "undefined" ? parseUrl(window.location.origin).origin : "",
74
- basePath: typeof window !== "undefined" ? parseUrl(window.location.origin).path : "/api/auth",
75
- credentials: "same-origin",
76
- _lastSync: 0,
77
- _session: void 0,
78
- _getSession: () => {
79
- }
80
- };
79
+ config;
80
+ constructor() {
81
+ this.config = this.createDefaultConfig();
82
+ }
83
+ createDefaultConfig() {
84
+ return {
85
+ baseUrl: typeof window !== "undefined" ? parseUrl(window.location.origin).origin : "",
86
+ basePath: typeof window !== "undefined" ? parseUrl(window.location.origin).path : "/api/auth",
87
+ credentials: "same-origin",
88
+ lastSync: 0,
89
+ session: null,
90
+ fetchSession: async () => void 0
91
+ };
92
+ }
81
93
  static getInstance() {
82
94
  if (!_AuthConfigManager.instance) {
83
95
  _AuthConfigManager.instance = new _AuthConfigManager();
@@ -85,43 +97,158 @@ var AuthConfigManager = class _AuthConfigManager {
85
97
  return _AuthConfigManager.instance;
86
98
  }
87
99
  setConfig(userConfig) {
88
- this._config = { ...this._config, ...userConfig };
100
+ this.config = { ...this.config, ...userConfig };
89
101
  }
90
102
  getConfig() {
91
- return this._config;
103
+ return this.config;
104
+ }
105
+ initializeConfig(hasInitialSession) {
106
+ this.config.lastSync = hasInitialSession ? now() : 0;
92
107
  }
93
108
  };
94
109
  var authConfigManager = AuthConfigManager.getInstance();
95
- function broadcast() {
96
- if (typeof BroadcastChannel !== "undefined") {
97
- return new BroadcastChannel("auth-js");
98
- }
99
- return {
100
- postMessage: () => {
110
+ var SessionContext = React.createContext(void 0);
111
+ function useInitializeSession(hasInitialSession, initialSession) {
112
+ const authConfig = authConfigManager.getConfig();
113
+ const [session, setSession] = React.useState(initialSession);
114
+ const [loading, setLoading] = React.useState(!hasInitialSession);
115
+ useEffect2(() => {
116
+ authConfig.fetchSession = async ({ event } = {}) => {
117
+ try {
118
+ const isStorageEvent = event === "storage";
119
+ if (isStorageEvent || !authConfig.session) {
120
+ authConfig.lastSync = now();
121
+ authConfig.session = await getSession();
122
+ setSession(authConfig.session);
123
+ return;
124
+ }
125
+ if (!event || !authConfig.session || now() < authConfig.lastSync) {
126
+ return;
127
+ }
128
+ authConfig.lastSync = now();
129
+ authConfig.session = await getSession();
130
+ setSession(authConfig.session);
131
+ } catch (error) {
132
+ logger.error(new ClientSessionError(error.message, error));
133
+ } finally {
134
+ setLoading(false);
135
+ }
136
+ };
137
+ authConfig.fetchSession();
138
+ return () => {
139
+ authConfig.lastSync = 0;
140
+ authConfig.session = null;
141
+ authConfig.fetchSession = async () => void 0;
142
+ };
143
+ }, []);
144
+ return { session, setSession, loading, setLoading };
145
+ }
146
+ function useVisibilityChangeEventListener(authConfig, refetchOnWindowFocus) {
147
+ useEffect2(() => {
148
+ const abortController = new AbortController();
149
+ const handleVisibilityChange = () => {
150
+ if (refetchOnWindowFocus && document.visibilityState === "visible") {
151
+ authConfig.fetchSession({ event: "visibilitychange" });
152
+ }
153
+ };
154
+ document.addEventListener("visibilitychange", handleVisibilityChange, {
155
+ signal: abortController.signal
156
+ });
157
+ return () => abortController.abort();
158
+ }, [refetchOnWindowFocus]);
159
+ }
160
+ function useRefetchInterval(authConfig, refetchInterval, shouldRefetch) {
161
+ useEffect2(() => {
162
+ if (refetchInterval && shouldRefetch) {
163
+ const intervalId = setInterval(() => {
164
+ if (authConfig.session) {
165
+ authConfig.fetchSession({ event: "poll" });
166
+ }
167
+ }, refetchInterval * 1e3);
168
+ return () => clearInterval(intervalId);
169
+ }
170
+ }, [refetchInterval, shouldRefetch]);
171
+ }
172
+ async function getSession(params) {
173
+ const { baseUrl, basePath, credentials } = authConfigManager.getConfig();
174
+ const session = await fetchData(
175
+ "session",
176
+ {
177
+ baseUrl,
178
+ basePath,
179
+ credentials
101
180
  },
102
- addEventListener: () => {
181
+ logger,
182
+ params
183
+ );
184
+ return session;
185
+ }
186
+ async function getCsrfToken() {
187
+ const { baseUrl, basePath, credentials } = authConfigManager.getConfig();
188
+ const response = await fetchData(
189
+ "csrf",
190
+ {
191
+ baseUrl,
192
+ basePath,
193
+ credentials
103
194
  },
104
- removeEventListener: () => {
105
- }
106
- };
195
+ logger
196
+ );
197
+ return response?.csrfToken ?? "";
198
+ }
199
+ function SessionProvider(props) {
200
+ if (!SessionContext) {
201
+ throw new Error("React Context is unavailable in Server Components");
202
+ }
203
+ const { children, refetchInterval, refetchWhenOffline = true } = props;
204
+ const authConfig = authConfigManager.getConfig();
205
+ const hasInitialSession = !!props.session;
206
+ authConfigManager.initializeConfig(hasInitialSession);
207
+ const { session, setSession, loading, setLoading } = useInitializeSession(
208
+ hasInitialSession,
209
+ props.session ?? null
210
+ );
211
+ useVisibilityChangeEventListener(authConfig, props.refetchOnWindowFocus ?? true);
212
+ const isOnline = useOnline();
213
+ const shouldRefetch = refetchWhenOffline || isOnline;
214
+ useRefetchInterval(authConfig, refetchInterval, shouldRefetch);
215
+ const contextValue = useMemo(
216
+ () => ({
217
+ data: session,
218
+ status: loading ? "loading" : session ? "authenticated" : "unauthenticated",
219
+ update: async (data) => {
220
+ if (loading || !session) {
221
+ return;
222
+ }
223
+ setLoading(true);
224
+ const updatedSession = await fetchData(
225
+ "session",
226
+ authConfig,
227
+ logger,
228
+ data ? { body: { csrfToken: await getCsrfToken(), data } } : void 0
229
+ );
230
+ setLoading(false);
231
+ if (updatedSession) {
232
+ setSession(updatedSession);
233
+ }
234
+ return updatedSession;
235
+ }
236
+ }),
237
+ [session, loading, setSession]
238
+ );
239
+ return /* @__PURE__ */ React.createElement(SessionContext.Provider, { value: contextValue }, children);
107
240
  }
108
- var logger = {
109
- debug: console.debug,
110
- error: console.error,
111
- warn: console.warn
112
- };
113
- var SessionContext = React2.createContext?.(void 0);
114
241
  function useSession(options) {
115
242
  if (!SessionContext) {
116
243
  throw new Error("React Context is unavailable in Server Components");
117
244
  }
118
- const __AUTHJS = authConfigManager.getConfig();
119
- const value = React2.useContext(SessionContext);
245
+ const config = authConfigManager.getConfig();
246
+ const session = useContext(SessionContext);
120
247
  const { required, onUnauthenticated } = options ?? {};
121
- const requiredAndNotLoading = required && value.status === "unauthenticated";
122
- React2.useEffect(() => {
248
+ const requiredAndNotLoading = required && session?.status === "unauthenticated";
249
+ useEffect2(() => {
123
250
  if (requiredAndNotLoading) {
124
- const url = `${__AUTHJS.baseUrl}${__AUTHJS.basePath}/signin?${new URLSearchParams({
251
+ const url = `${config.baseUrl}${config.basePath}/signin?${new URLSearchParams({
125
252
  error: "SessionRequired",
126
253
  callbackUrl: window.location.href
127
254
  })}`;
@@ -134,66 +261,44 @@ function useSession(options) {
134
261
  }, [requiredAndNotLoading, onUnauthenticated]);
135
262
  if (requiredAndNotLoading) {
136
263
  return {
137
- data: value.data,
138
- update: value.update,
264
+ data: session?.data,
265
+ update: session?.update,
139
266
  status: "loading"
140
267
  };
141
268
  }
142
- return value;
143
- }
144
- async function getSession(params) {
145
- const session = await fetchData("session", authConfigManager.getConfig(), logger, params);
146
- if (params?.broadcast ?? true) {
147
- broadcast().postMessage({
148
- event: "session",
149
- data: { trigger: "getSession" }
150
- });
151
- }
152
269
  return session;
153
270
  }
154
- async function getCsrfToken() {
155
- const response = await fetchData(
156
- "csrf",
157
- authConfigManager.getConfig(),
158
- logger
159
- );
160
- return response?.csrfToken ?? "";
161
- }
162
271
  async function getProviders() {
163
272
  return fetchData("providers", authConfigManager.getConfig(), logger);
164
273
  }
165
- async function signIn(provider, options, authorizationParams) {
166
- const { callbackUrl = window.location.href, redirect = true } = options ?? {};
167
- const __AUTHJS = authConfigManager.getConfig();
168
- const href = `${__AUTHJS.baseUrl}${__AUTHJS.basePath}`;
274
+ async function signIn(provider, options = {}, authorizationParams = {}) {
275
+ const { callbackUrl = window.location.href, redirect = true, ...opts } = options;
276
+ const config = authConfigManager.getConfig();
277
+ const href = `${config.baseUrl}${config.basePath}`;
169
278
  const providers = await getProviders();
170
279
  if (!providers) {
171
280
  window.location.href = `${href}/error`;
172
281
  return;
173
282
  }
174
283
  if (!provider || !(provider in providers)) {
175
- window.location.href = `${href}/signin?${new URLSearchParams({
176
- callbackUrl
177
- })}`;
284
+ window.location.href = `${href}/signin?${new URLSearchParams({ callbackUrl })}`;
178
285
  return;
179
286
  }
180
287
  const isCredentials = providers[provider].type === "credentials";
181
288
  const isEmail = providers[provider].type === "email";
182
- const isSupportingReturn = isCredentials || isEmail;
183
289
  const signInUrl = `${href}/${isCredentials ? "callback" : "signin"}/${provider}`;
184
290
  const csrfToken = await getCsrfToken();
185
291
  const res = await fetch(`${signInUrl}?${new URLSearchParams(authorizationParams)}`, {
186
- method: "post",
292
+ method: "POST",
187
293
  headers: {
188
294
  "Content-Type": "application/x-www-form-urlencoded",
189
295
  "X-Auth-Return-Redirect": "1"
190
296
  },
191
- // @ts-expect-error TODO: Fix this
192
- body: new URLSearchParams({ ...options, csrfToken, callbackUrl }),
193
- credentials: __AUTHJS.credentials
297
+ body: new URLSearchParams({ ...opts, csrfToken, callbackUrl }),
298
+ credentials: config.credentials
194
299
  });
195
300
  const data = await res.json();
196
- if (redirect || !isSupportingReturn) {
301
+ if (redirect) {
197
302
  const url = data.url ?? callbackUrl;
198
303
  window.location.href = url;
199
304
  if (url.includes("#")) {
@@ -203,7 +308,7 @@ async function signIn(provider, options, authorizationParams) {
203
308
  }
204
309
  const error = new URL(data.url).searchParams.get("error");
205
310
  if (res.ok) {
206
- await __AUTHJS._getSession({ event: "storage" });
311
+ await config.fetchSession?.({ event: "storage" });
207
312
  }
208
313
  return {
209
314
  error,
@@ -213,146 +318,82 @@ async function signIn(provider, options, authorizationParams) {
213
318
  };
214
319
  }
215
320
  async function signOut(options) {
216
- const { callbackUrl = window.location.href } = options ?? {};
217
- const __AUTHJS = authConfigManager.getConfig();
218
- const href = `${__AUTHJS.baseUrl}${__AUTHJS.basePath}`;
321
+ const { callbackUrl = window.location.href, redirect = true } = options ?? {};
322
+ const config = authConfigManager.getConfig();
219
323
  const csrfToken = await getCsrfToken();
220
- const res = await fetch(`${href}/signout`, {
221
- method: "post",
324
+ const res = await fetch(`${config.baseUrl}${config.basePath}/signout`, {
325
+ method: "POST",
222
326
  headers: {
223
327
  "Content-Type": "application/x-www-form-urlencoded",
224
328
  "X-Auth-Return-Redirect": "1"
225
329
  },
226
330
  body: new URLSearchParams({ csrfToken, callbackUrl }),
227
- credentials: __AUTHJS.credentials
331
+ credentials: config.credentials
228
332
  });
229
333
  const data = await res.json();
230
- broadcast().postMessage({ event: "session", data: { trigger: "signout" } });
231
- if (options?.redirect ?? true) {
334
+ if (redirect) {
232
335
  const url = data.url ?? callbackUrl;
233
336
  window.location.href = url;
234
337
  if (url.includes("#")) {
235
338
  window.location.reload();
236
339
  }
237
- return;
340
+ return void 0;
238
341
  }
239
- await __AUTHJS._getSession({ event: "storage" });
342
+ await config.fetchSession?.({ event: "storage" });
240
343
  return data;
241
344
  }
242
- function SessionProvider(props) {
243
- if (!SessionContext) {
244
- throw new Error("React Context is unavailable in Server Components");
245
- }
246
- const { children, refetchInterval, refetchWhenOffline } = props;
247
- const __AUTHJS = authConfigManager.getConfig();
248
- const hasInitialSession = props.session !== void 0;
249
- __AUTHJS._lastSync = hasInitialSession ? now() : 0;
250
- const [session, setSession] = React2.useState(() => {
251
- if (hasInitialSession) {
252
- __AUTHJS._session = props.session;
345
+ var createPopup = ({ url, title, height, width }) => {
346
+ const left = window.screenX + (window.outerWidth - width) / 2;
347
+ const top = window.screenY + (window.outerHeight - height) / 2.5;
348
+ const externalPopup = window.open(
349
+ url,
350
+ title,
351
+ `width=${width},height=${height},left=${left},top=${top}`
352
+ );
353
+ return externalPopup;
354
+ };
355
+ var useOauthPopupLogin = (provider, options = {}) => {
356
+ const { width = 500, height = 500, title = "Signin", onSuccess, callbackUrl = "/" } = options;
357
+ const [externalWindow, setExternalWindow] = useState3();
358
+ const [state, setState] = useState3({ status: "loading" });
359
+ const popUpSignin = useCallback(async () => {
360
+ const res = await signIn(provider, {
361
+ redirect: false,
362
+ callbackUrl
363
+ });
364
+ if (res?.error) {
365
+ setState({ status: "errored", error: res.error });
366
+ return;
253
367
  }
254
- return props.session;
255
- });
256
- const [loading, setLoading] = React2.useState(!hasInitialSession);
257
- React2.useEffect(() => {
258
- __AUTHJS._getSession = async ({ event } = {}) => {
259
- try {
260
- const storageEvent = event === "storage";
261
- if (storageEvent || __AUTHJS._session === void 0) {
262
- __AUTHJS._lastSync = now();
263
- __AUTHJS._session = await getSession({
264
- broadcast: !storageEvent
265
- });
266
- setSession(__AUTHJS._session);
267
- return;
268
- }
269
- if (
270
- // If there is no time defined for when a session should be considered
271
- // stale, then it's okay to use the value we have until an event is
272
- // triggered which updates it
273
- !event || // If the client doesn't have a session then we don't need to call
274
- // the server to check if it does (if they have signed in via another
275
- // tab or window that will come through as a "stroage" event
276
- // event anyway)
277
- __AUTHJS._session === null || // Bail out early if the client session is not stale yet
278
- now() < __AUTHJS._lastSync
279
- ) {
280
- return;
368
+ setExternalWindow(
369
+ createPopup({
370
+ url: res?.url,
371
+ title,
372
+ width,
373
+ height
374
+ })
375
+ );
376
+ }, []);
377
+ useEffect2(() => {
378
+ const handleMessage = (event) => {
379
+ if (event.origin !== window.location.origin)
380
+ return;
381
+ if (event.data.status) {
382
+ setState(event.data);
383
+ if (event.data.status === "success") {
384
+ onSuccess?.();
281
385
  }
282
- __AUTHJS._lastSync = now();
283
- __AUTHJS._session = await getSession();
284
- setSession(__AUTHJS._session);
285
- } catch (error) {
286
- logger.error(new ClientSessionError(error.message, error));
287
- } finally {
288
- setLoading(false);
386
+ externalWindow?.close();
289
387
  }
290
388
  };
291
- __AUTHJS._getSession();
389
+ window.addEventListener("message", handleMessage);
292
390
  return () => {
293
- __AUTHJS._lastSync = 0;
294
- __AUTHJS._session = void 0;
295
- __AUTHJS._getSession = () => {
296
- };
297
- };
298
- }, []);
299
- React2.useEffect(() => {
300
- const handle = () => __AUTHJS._getSession({ event: "storage" });
301
- broadcast().addEventListener("message", handle);
302
- return () => broadcast().removeEventListener("message", handle);
303
- }, []);
304
- React2.useEffect(() => {
305
- const { refetchOnWindowFocus = true } = props;
306
- const visibilityHandler = () => {
307
- if (refetchOnWindowFocus && document.visibilityState === "visible") {
308
- __AUTHJS._getSession({ event: "visibilitychange" });
309
- }
391
+ window.removeEventListener("message", handleMessage);
392
+ externalWindow?.close();
310
393
  };
311
- document.addEventListener("visibilitychange", visibilityHandler, false);
312
- return () => document.removeEventListener("visibilitychange", visibilityHandler, false);
313
- }, [props.refetchOnWindowFocus]);
314
- const isOnline = useOnline();
315
- const shouldRefetch = refetchWhenOffline !== false || isOnline;
316
- React2.useEffect(() => {
317
- if (refetchInterval && shouldRefetch) {
318
- const refetchIntervalTimer = setInterval(() => {
319
- if (__AUTHJS._session) {
320
- __AUTHJS._getSession({ event: "poll" });
321
- }
322
- }, refetchInterval * 1e3);
323
- return () => clearInterval(refetchIntervalTimer);
324
- }
325
- }, [refetchInterval, shouldRefetch]);
326
- const value = React2.useMemo(
327
- () => ({
328
- data: session,
329
- status: loading ? "loading" : session ? "authenticated" : "unauthenticated",
330
- async update(data) {
331
- if (loading || !session) {
332
- return;
333
- }
334
- setLoading(true);
335
- const newSession = await fetchData(
336
- "session",
337
- __AUTHJS,
338
- logger,
339
- typeof data === "undefined" ? void 0 : { body: { csrfToken: await getCsrfToken(), data } }
340
- );
341
- setLoading(false);
342
- if (newSession) {
343
- setSession(newSession);
344
- broadcast().postMessage({
345
- event: "session",
346
- data: { trigger: "getSession" }
347
- });
348
- }
349
- return newSession;
350
- }
351
- }),
352
- [session, loading]
353
- );
354
- return /* @__PURE__ */ React2.createElement(SessionContext.Provider, { value }, children);
355
- }
394
+ }, [externalWindow]);
395
+ return { popUpSignin, ...state };
396
+ };
356
397
  export {
357
398
  SessionContext,
358
399
  SessionProvider,
@@ -362,5 +403,6 @@ export {
362
403
  getSession,
363
404
  signIn,
364
405
  signOut,
406
+ useOauthPopupLogin,
365
407
  useSession
366
408
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hono/auth-js",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "A third-party Auth js middleware for Hono",
5
5
  "main": "dist/index.js",
6
6
  "exports": {