@bounc.ing/next 0.1.0 → 0.2.2

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.
@@ -3,6 +3,7 @@ import type { User } from '../types.js';
3
3
  interface BouncingContextValue {
4
4
  user: User | null;
5
5
  isLoading: boolean;
6
+ error: Error | null;
6
7
  domain: string;
7
8
  }
8
9
  export declare function useBouncingContext(): BouncingContextValue;
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { createContext, useContext, useState, useEffect } from 'react';
4
4
  const BouncingContext = createContext({
5
- user: null, isLoading: true, domain: '',
5
+ user: null, isLoading: true, error: null, domain: '',
6
6
  });
7
7
  export function useBouncingContext() {
8
8
  return useContext(BouncingContext);
@@ -10,6 +10,7 @@ export function useBouncingContext() {
10
10
  export function BouncingProvider({ domain, children }) {
11
11
  const [user, setUser] = useState(null);
12
12
  const [isLoading, setIsLoading] = useState(true);
13
+ const [error, setError] = useState(null);
13
14
  useEffect(() => {
14
15
  // Try hydration from server-rendered script tag
15
16
  const script = document.getElementById('__bouncing');
@@ -26,10 +27,23 @@ export function BouncingProvider({ domain, children }) {
26
27
  }
27
28
  // Fetch from /auth/me
28
29
  fetch(`https://${domain}/auth/me`, { credentials: 'include' })
29
- .then((res) => res.ok ? res.json() : null)
30
- .then((data) => setUser(data))
31
- .catch(() => setUser(null))
30
+ .then(async (res) => {
31
+ if (res.status === 401) {
32
+ // Not authenticated — not an error, just no user
33
+ setUser(null);
34
+ return;
35
+ }
36
+ if (!res.ok) {
37
+ throw new Error(`/auth/me returned ${res.status}`);
38
+ }
39
+ const data = await res.json();
40
+ setUser(data);
41
+ })
42
+ .catch((e) => {
43
+ setError(e instanceof Error ? e : new Error(String(e)));
44
+ setUser(null);
45
+ })
32
46
  .finally(() => setIsLoading(false));
33
47
  }, [domain]);
34
- return (_jsx(BouncingContext, { value: { user, isLoading, domain }, children: children }));
48
+ return (_jsx(BouncingContext, { value: { user, isLoading, error, domain }, children: children }));
35
49
  }
@@ -2,4 +2,5 @@ import type { User } from '../types.js';
2
2
  export declare function useUser(): {
3
3
  user: User | null;
4
4
  isLoading: boolean;
5
+ error: Error | null;
5
6
  };
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { useBouncingContext } from './provider.js';
3
3
  export function useUser() {
4
- const { user, isLoading } = useBouncingContext();
5
- return { user, isLoading };
4
+ const { user, isLoading, error } = useBouncingContext();
5
+ return { user, isLoading, error };
6
6
  }
package/dist/index.d.ts CHANGED
@@ -2,3 +2,4 @@ export { createBouncing } from './server/config.js';
2
2
  export type { BouncingConfig, BouncingInstance, Session, User } from './server/config.js';
3
3
  export { createAdminClient } from './server/admin.js';
4
4
  export type { BouncingAdminClient } from './server/admin.js';
5
+ export { BouncingError, BouncingUnauthenticatedError, BouncingNetworkError } from './types.js';
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { createBouncing } from './server/config.js';
2
2
  export { createAdminClient } from './server/admin.js';
3
+ export { BouncingError, BouncingUnauthenticatedError, BouncingNetworkError } from './types.js';
@@ -1,4 +1,6 @@
1
1
  import type { Session } from '../types.js';
2
+ /** Clear the JWKS cache for a baseURL — call after verification failure to force re-fetch */
3
+ export declare function invalidateJWKSCache(baseURL: string): void;
2
4
  /** Verify a JWT access token against the JWKS endpoint. Returns Session or null. */
3
5
  export declare function verifyAccessToken(token: string, baseURL: string): Promise<Session | null>;
4
6
  /** Exchange a refresh token for new tokens. Returns null on failure. */
@@ -1,13 +1,22 @@
1
1
  import { createRemoteJWKSet, jwtVerify } from 'jose';
2
+ const JWKS_TTL_MS = 60 * 60 * 1000; // 1 hour
2
3
  const jwksCache = new Map();
3
4
  function getJWKS(baseURL) {
4
- let jwks = jwksCache.get(baseURL);
5
- if (!jwks) {
6
- jwks = createRemoteJWKSet(new URL('/.well-known/jwks.json', baseURL));
7
- jwksCache.set(baseURL, jwks);
5
+ const cached = jwksCache.get(baseURL);
6
+ if (cached && Date.now() - cached.fetchedAt < JWKS_TTL_MS) {
7
+ return cached.jwks;
8
8
  }
9
+ const jwks = createRemoteJWKSet(new URL('/.well-known/jwks.json', baseURL), {
10
+ cacheMaxAge: JWKS_TTL_MS,
11
+ cooldownDuration: 5000,
12
+ });
13
+ jwksCache.set(baseURL, { jwks, fetchedAt: Date.now() });
9
14
  return jwks;
10
15
  }
16
+ /** Clear the JWKS cache for a baseURL — call after verification failure to force re-fetch */
17
+ export function invalidateJWKSCache(baseURL) {
18
+ jwksCache.delete(baseURL);
19
+ }
11
20
  /** Verify a JWT access token against the JWKS endpoint. Returns Session or null. */
12
21
  export async function verifyAccessToken(token, baseURL) {
13
22
  try {
@@ -28,6 +37,8 @@ export async function verifyAccessToken(token, baseURL) {
28
37
  };
29
38
  }
30
39
  catch {
40
+ // Invalidate cache on verification failure — signing keys may have rotated
41
+ invalidateJWKSCache(baseURL);
31
42
  return null;
32
43
  }
33
44
  }
package/dist/types.d.ts CHANGED
@@ -21,3 +21,14 @@ export interface TokenResponse {
21
21
  refresh_token: string;
22
22
  expires_in: number;
23
23
  }
24
+ /** Base class for SDK errors */
25
+ export declare class BouncingError extends Error {
26
+ code: string;
27
+ constructor(message: string, code: string);
28
+ }
29
+ export declare class BouncingUnauthenticatedError extends BouncingError {
30
+ constructor(message?: string);
31
+ }
32
+ export declare class BouncingNetworkError extends BouncingError {
33
+ constructor(message: string);
34
+ }
package/dist/types.js CHANGED
@@ -1 +1,21 @@
1
- export {};
1
+ /** Base class for SDK errors */
2
+ export class BouncingError extends Error {
3
+ code;
4
+ constructor(message, code) {
5
+ super(message);
6
+ this.code = code;
7
+ this.name = 'BouncingError';
8
+ }
9
+ }
10
+ export class BouncingUnauthenticatedError extends BouncingError {
11
+ constructor(message = 'Not authenticated') {
12
+ super(message, 'unauthenticated');
13
+ this.name = 'BouncingUnauthenticatedError';
14
+ }
15
+ }
16
+ export class BouncingNetworkError extends BouncingError {
17
+ constructor(message) {
18
+ super(message, 'network_error');
19
+ this.name = 'BouncingNetworkError';
20
+ }
21
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bounc.ing/next",
3
- "version": "0.1.0",
3
+ "version": "0.2.2",
4
4
  "description": "Bouncing auth SDK for Next.js — auth for your app in 3 minutes",
5
5
  "type": "module",
6
6
  "exports": {
@@ -38,7 +38,7 @@
38
38
  "vitest": "^4.1.0"
39
39
  },
40
40
  "files": ["dist"],
41
- "license": "MIT",
41
+ "license": "Apache-2.0",
42
42
  "repository": {
43
43
  "type": "git",
44
44
  "url": "https://github.com/scttfrdmn/bouncing-managed",