{"version":3,"file":"session-MPM1dzMp.cjs","names":[],"sources":["../src/session/session.ts","../src/session/storageBackend.ts","../src/session/storage.ts"],"sourcesContent":["import type { Session as AuthSession } from '../auth';\n\n/**\n * Decoded JWT token payload with processed timestamps and Hasura claims\n */\nexport interface DecodedToken {\n  /** Token expiration time as Date object */\n  exp?: number;\n  /** Token issued at time as Date object */\n  iat?: number;\n  /** Token issuer */\n  iss?: string;\n  /** Token subject (user ID) */\n  sub?: string;\n  /** Hasura JWT claims with PostgreSQL arrays converted to JavaScript arrays */\n  'https://hasura.io/jwt/claims'?: Record<string, unknown>;\n  /** Any other JWT claims */\n  [key: string]: unknown;\n}\n\n/**\n * The enriched session stored and managed by the Nhost SDK client.\n *\n * This is a superset of the raw `Session` returned by the auth API\n * (importable from `@nhost/nhost-js/auth`). In addition to the standard\n * auth fields, it includes a `decodedToken` with the parsed JWT payload,\n * making it easy to inspect Hasura claims, roles, and session variables\n * without manually decoding the access token.\n *\n * This is the type you must use when implementing a custom\n * {@link SessionStorageBackend} for `createServerClient`.\n *\n * @see {@link SessionStorageBackend}\n */\nexport interface StoredSession extends AuthSession {\n  /** Decoded JWT token payload with processed timestamps and Hasura claims */\n  decodedToken: DecodedToken;\n}\n\n/**\n * @deprecated Use {@link StoredSession} instead. Both the auth module and the\n * session module previously exported a type named `Session`, but they are\n * different: `auth.Session` is the raw API response, while `session.Session`\n * is the enriched client-side session that includes `decodedToken`.\n * `StoredSession` is the unambiguous name for the latter.\n */\nexport type Session = StoredSession;\n\n/**\n * Decodes a base64url-encoded string (RFC 4648 Section 5) to a UTF-8 string.\n *\n * JWTs use base64url encoding, which differs from standard base64 by using\n * `-` and `_` instead of `+` and `/`, and omitting padding. The browser's\n * native `atob()` does not support base64url, so we must handle the conversion.\n */\nconst decodeBase64Url = (input: string): string => {\n  // Convert base64url to standard base64\n  let base64 = input.replace(/-/g, '+').replace(/_/g, '/');\n  const pad = base64.length % 4;\n  if (pad) {\n    base64 += '='.repeat(4 - pad);\n  }\n\n  // Use TextDecoder for proper UTF-8 support (atob alone mangles multi-byte characters)\n  const binaryString = atob(base64);\n  const bytes = Uint8Array.from(binaryString, (c) => c.charCodeAt(0));\n  return new TextDecoder().decode(bytes);\n};\n\nexport const decodeUserSession = (accessToken: string): DecodedToken => {\n  const s = accessToken.split('.');\n  if (s.length !== 3 || !s[1]) {\n    throw new Error('Invalid access token format');\n  }\n\n  const decodedToken = JSON.parse(decodeBase64Url(s[1])) as Record<\n    string,\n    unknown\n  >;\n\n  // Convert iat and exp to Date objects\n  const iat =\n    typeof decodedToken['iat'] === 'number'\n      ? decodedToken['iat'] * 1000 // Convert seconds to milliseconds\n      : undefined;\n  const exp =\n    typeof decodedToken['exp'] === 'number'\n      ? decodedToken['exp'] * 1000 // Convert seconds to milliseconds\n      : undefined;\n\n  // Process Hasura claims - dynamically convert PostgreSQL array notation to arrays\n  const hasuraClaims = decodedToken['https://hasura.io/jwt/claims'] as\n    | Record<string, unknown>\n    | undefined;\n  const processedClaims = hasuraClaims\n    ? Object.entries(hasuraClaims).reduce(\n        (acc, [key, value]) => {\n          if (typeof value === 'string' && isPostgresArray(value)) {\n            acc[key] = parsePostgresArray(value);\n          } else {\n            acc[key] = value;\n          }\n          return acc;\n        },\n        {} as Record<string, unknown>,\n      )\n    : undefined;\n\n  return {\n    ...decodedToken,\n    iat,\n    exp,\n    'https://hasura.io/jwt/claims': processedClaims,\n  };\n};\n\nconst isPostgresArray = (value: string): boolean => {\n  return value.startsWith('{') && value.endsWith('}');\n};\n\nconst parsePostgresArray = (value: string): string[] => {\n  if (!value || value === '{}') return [];\n  // Remove curly braces and split by comma, handling quoted values\n  return value\n    .slice(1, -1)\n    .split(',')\n    .map((item) => item.trim().replace(/^\"(.*)\"$/, '$1'));\n};\n","/**\n * Storage implementations for session persistence in different environments.\n *\n * This module provides different storage adapters for persisting authentication sessions\n * across page reloads and browser sessions.\n */\n\nimport type { StoredSession } from './session';\n\n/**\n * Session storage interface for session persistence.\n * This interface can be implemented to provide custom storage solutions.\n *\n * **Important:** The methods here operate on {@link StoredSession}, which is\n * the enriched client-side session managed by the Nhost SDK. It extends the\n * raw `Session` type from `@nhost/nhost-js/auth` by adding a `decodedToken`\n * field with the parsed JWT payload. Do **not** use `auth.Session` here —\n * it is missing `decodedToken` and will cause a TypeScript error.\n *\n * @example\n * ```ts\n * import { type StoredSession, type SessionStorageBackend } from '@nhost/nhost-js/session';\n *\n * class MyCustomStorage implements SessionStorageBackend {\n *   get(): StoredSession | null { ... }\n *   set(value: StoredSession): void { ... }\n *   remove(): void { ... }\n * }\n * ```\n */\nexport interface SessionStorageBackend {\n  /**\n   * Get the current session from storage\n   * @returns The stored session or null if not found\n   */\n  get(): StoredSession | null;\n\n  /**\n   * Set the session in storage\n   * @param value - The session to store\n   */\n  set(value: StoredSession): void;\n\n  /**\n   * Remove the session from storage\n   */\n  remove(): void;\n}\n\n/**\n * Default storage key used for storing the Nhost session\n */\nexport const DEFAULT_SESSION_KEY = 'nhostSession';\n\n/**\n * Browser localStorage implementation of StorageInterface.\n * Persists the session across page reloads and browser restarts.\n */\nexport class LocalStorage implements SessionStorageBackend {\n  private readonly storageKey: string;\n\n  /**\n   * Creates a new LocalStorage instance\n   * @param options - Configuration options\n   * @param options.storageKey - The key to use in localStorage (defaults to \"nhostSession\")\n   */\n  constructor(options?: { storageKey?: string }) {\n    this.storageKey = options?.storageKey || DEFAULT_SESSION_KEY;\n  }\n\n  /**\n   * Gets the session from localStorage\n   * @returns The stored session or null if not found\n   */\n  get(): StoredSession | null {\n    try {\n      const value = window.localStorage.getItem(this.storageKey);\n      return value ? (JSON.parse(value) as StoredSession) : null;\n    } catch {\n      this.remove();\n      return null;\n    }\n  }\n\n  /**\n   * Sets the session in localStorage\n   * @param value - The session to store\n   */\n  set(value: StoredSession): void {\n    window.localStorage.setItem(this.storageKey, JSON.stringify(value));\n  }\n\n  /**\n   * Removes the session from localStorage\n   */\n  remove(): void {\n    window.localStorage.removeItem(this.storageKey);\n  }\n}\n\n/**\n * In-memory storage implementation for non-browser environments or when\n * persistent storage is not available or desirable.\n */\nexport class MemoryStorage implements SessionStorageBackend {\n  private session: StoredSession | null = null;\n\n  /**\n   * Gets the session from memory\n   * @returns The stored session or null if not set\n   */\n  get(): StoredSession | null {\n    return this.session;\n  }\n\n  /**\n   * Sets the session in memory\n   * @param value - The session to store\n   */\n  set(value: StoredSession): void {\n    this.session = value;\n  }\n\n  /**\n   * Clears the session from memory\n   */\n  remove(): void {\n    this.session = null;\n  }\n}\n\n/**\n * Cookie-based storage implementation.\n * This storage uses web browser cookies to store the session so it's not\n * available in server-side environments. It is useful though for synchronizing\n * sessions between client and server environments.\n */\nexport class CookieStorage implements SessionStorageBackend {\n  private readonly cookieName: string;\n  private readonly expirationDays: number;\n  private readonly secure: boolean;\n  private readonly sameSite: 'strict' | 'lax' | 'none';\n\n  /**\n   * Creates a new CookieStorage instance\n   * @param options - Configuration options\n   * @param options.cookieName - Name of the cookie to use (defaults to \"nhostSession\")\n   * @param options.expirationDays - Number of days until the cookie expires (defaults to 30)\n   * @param options.secure - Whether to set the Secure flag on the cookie (defaults to true)\n   * @param options.sameSite - SameSite policy for the cookie (defaults to \"lax\")\n   */\n  constructor(options?: {\n    cookieName?: string;\n    expirationDays?: number;\n    secure?: boolean;\n    sameSite?: 'strict' | 'lax' | 'none';\n  }) {\n    this.cookieName = options?.cookieName || DEFAULT_SESSION_KEY;\n    this.expirationDays = options?.expirationDays ?? 30;\n    this.secure = options?.secure ?? true;\n    this.sameSite = options?.sameSite || 'lax';\n  }\n\n  /**\n   * Gets the session from cookies\n   * @returns The stored session or null if not found\n   */\n  get(): StoredSession | null {\n    const cookies = document.cookie.split(';');\n    for (const cookie of cookies) {\n      const [name, value] = cookie.trim().split('=');\n      if (name === this.cookieName) {\n        try {\n          return JSON.parse(decodeURIComponent(value || '')) as StoredSession;\n        } catch {\n          this.remove();\n          return null;\n        }\n      }\n    }\n    return null;\n  }\n\n  /**\n   * Sets the session in a cookie\n   * @param value - The session to store\n   */\n  set(value: StoredSession): void {\n    const expires = new Date();\n    expires.setTime(\n      expires.getTime() + this.expirationDays * 24 * 60 * 60 * 1000,\n    );\n\n    const cookieValue = encodeURIComponent(JSON.stringify(value));\n    const cookieString = `${this.cookieName}=${cookieValue}; expires=${expires.toUTCString()}; path=/; ${this.secure ? 'secure; ' : ''}SameSite=${this.sameSite}`;\n\n    // biome-ignore lint/suspicious/noDocumentCookie: this is unnecessary\n    document.cookie = cookieString;\n  }\n\n  /**\n   * Removes the session cookie\n   */\n  remove(): void {\n    // biome-ignore lint/suspicious/noDocumentCookie: this is unnecessary\n    document.cookie = `${this.cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; ${this.secure ? 'secure; ' : ''}SameSite=${this.sameSite}`;\n  }\n}\n","/**\n * Storage implementations for session persistence in different environments.\n *\n * This module provides different storage adapters for persisting authentication sessions\n * across page reloads and browser sessions.\n */\n\nimport type { Session as AuthSession } from '../auth';\nimport { decodeUserSession, type StoredSession } from './session';\nimport {\n  LocalStorage,\n  MemoryStorage,\n  type SessionStorageBackend,\n} from './storageBackend';\n\n/**\n * Callback function type for session change subscriptions\n */\nexport type SessionChangeCallback = (session: StoredSession | null) => void;\n\n/**\n * A wrapper around any SessionStorageInterface implementation that adds\n * the ability to subscribe to session changes.\n */\nexport class SessionStorage {\n  private readonly storage: SessionStorageBackend;\n  private subscribers = new Set<SessionChangeCallback>();\n\n  /**\n   * Creates a new SessionStorage instance\n   * @param storage - The underlying storage implementation to use\n   */\n  constructor(storage: SessionStorageBackend) {\n    this.storage = storage;\n  }\n\n  /**\n   * Gets the session from the underlying storage\n   * @returns The stored session or null if not found\n   */\n  get(): StoredSession | null {\n    return this.storage.get();\n  }\n\n  /**\n   * Sets the session in the underlying storage and notifies subscribers\n   * @param value - The session to store\n   */\n  set(value: AuthSession): void {\n    const decodedToken = decodeUserSession(value.accessToken);\n    const decodedSession = {\n      ...value,\n      decodedToken: decodedToken,\n    };\n\n    this.storage.set(decodedSession);\n    this.notifySubscribers(decodedSession);\n  }\n\n  /**\n   * Removes the session from the underlying storage and notifies subscribers\n   */\n  remove(): void {\n    this.storage.remove();\n    this.notifySubscribers(null);\n  }\n\n  /**\n   * Subscribe to session changes\n   * @param callback - Function that will be called when the session changes\n   * @returns An unsubscribe function to remove this subscription\n   */\n  onChange(callback: SessionChangeCallback) {\n    this.subscribers.add(callback);\n\n    return () => {\n      this.subscribers.delete(callback);\n    };\n  }\n\n  /**\n   * Notify all subscribers of a session change\n   * @param session - The new session value or null if removed\n   */\n  private notifySubscribers(session: StoredSession | null): void {\n    for (const subscriber of this.subscribers) {\n      try {\n        subscriber(session);\n      } catch (error) {\n        console.error('Error notifying subscriber:', error);\n      }\n    }\n  }\n}\n\n/**\n * Detects the best available storage implementation for the current environment.\n *\n * The detection process follows this order:\n * 1. Try to use localStorage if we're in a browser environment\n * 2. Fall back to in-memory storage if localStorage isn't available\n *\n * @returns The best available storage implementation as a SessionStorageBackend\n */\nexport const detectStorage = (): SessionStorageBackend => {\n  if (typeof window !== 'undefined') {\n    return new LocalStorage();\n  }\n  return new MemoryStorage();\n};\n"],"mappings":"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"}