UNPKG

@sanity/sdk

Version:
173 lines (152 loc) 5.71 kB
import {type ClientError} from '@sanity/client' import {EMPTY, fromEvent, Observable} from 'rxjs' import {AUTH_CODE_PARAM, DEFAULT_BASE} from './authConstants' export function getAuthCode(callbackUrl: string | undefined, locationHref: string): string | null { const loc = new URL(locationHref, DEFAULT_BASE) const callbackLocation = callbackUrl ? new URL(callbackUrl, DEFAULT_BASE) : undefined const callbackLocationMatches = callbackLocation ? loc.pathname.toLowerCase().startsWith(callbackLocation.pathname.toLowerCase()) : true // First, try getting the auth code (sid) from hash or search params directly let authCode = new URLSearchParams(loc.hash.slice(1)).get(AUTH_CODE_PARAM) || new URLSearchParams(loc.search).get(AUTH_CODE_PARAM) // If not found directly, try extracting it from the _context parameter as a fallback if (!authCode) { const contextParam = new URLSearchParams(loc.search).get('_context') if (contextParam) { try { const parsedContext = JSON.parse(contextParam) if ( parsedContext && typeof parsedContext === 'object' && typeof parsedContext.sid === 'string' && parsedContext.sid // Ensure it's not an empty string ) { authCode = parsedContext.sid } } catch { // Silently ignore _context JSON parsing errors; authCode remains null/empty } } } return authCode && callbackLocationMatches ? authCode : null } export function getTokenFromLocation(locationHref: string): string | null { const loc = new URL(locationHref) const token = new URLSearchParams(loc.hash.slice(1)).get('token') return token ? token : null } /** * Attempts to retrieve a token from the configured storage. * If invalid or not present, returns null. */ export function getTokenFromStorage( storageArea: Storage | undefined, storageKey: string, ): string | null { if (!storageArea) return null const item = storageArea.getItem(storageKey) if (item === null) return null try { const parsed: unknown = JSON.parse(item) if ( typeof parsed !== 'object' || parsed === null || !('token' in parsed) || typeof parsed.token !== 'string' ) { throw new Error('Invalid stored auth data structure') } return parsed.token } catch { storageArea.removeItem(storageKey) return null } } /** * Creates an observable stream of storage events. If not in a browser environment, * returns an EMPTY observable. */ export function getStorageEvents(): Observable<StorageEvent> { const isBrowser = typeof window !== 'undefined' && typeof window.addEventListener === 'function' if (!isBrowser) { return EMPTY } return fromEvent<StorageEvent>(window, 'storage') } /** * Returns a default storage instance (localStorage) if available. * If not available or an error occurs, returns undefined. */ export function getDefaultStorage(): Storage | undefined { try { if (typeof localStorage !== 'undefined' && typeof localStorage.getItem === 'function') { return localStorage } return undefined } catch { return undefined } } /** * Returns the default location to use. * Tries accessing `location.href`, falls back to a default base if not available or on error. */ export function getDefaultLocation(): string { try { if (typeof location === 'undefined') return DEFAULT_BASE if (typeof location.href === 'string') return location.href return DEFAULT_BASE } catch { return DEFAULT_BASE } } /** * Cleans up the URL by removing the `token` from the hash and the `sid` and `url` search params. * @internal */ export function getCleanedUrl(locationUrl: string): string { const loc = new URL(locationUrl) // Remove only the `token` param from the hash while preserving other fragments const rawHash = loc.hash.startsWith('#') ? loc.hash.slice(1) : loc.hash if (rawHash && rawHash.includes('=')) { const hashParams = new URLSearchParams(rawHash) hashParams.delete('token') hashParams.delete('withSid') const nextHash = hashParams.toString() loc.hash = nextHash ? `#${nextHash}` : '' } loc.searchParams.delete('sid') loc.searchParams.delete('url') return loc.toString() } // ----------------------------------------------------------------------------- // ClientError helpers (shared) // ----------------------------------------------------------------------------- /** @internal */ export type ApiErrorBody = { error?: {type?: string; description?: string} type?: string description?: string message?: string } /** @internal Extracts the structured API error body from a ClientError, if present. */ export function getClientErrorApiBody(error: ClientError): ApiErrorBody | undefined { const body: unknown = (error as ClientError).response?.body return body && typeof body === 'object' ? (body as ApiErrorBody) : undefined } /** @internal Returns the error type string from an API error body, if available. */ export function getClientErrorApiType(error: ClientError): string | undefined { const body = getClientErrorApiBody(error) return body?.error?.type ?? body?.type } /** @internal Returns the error description string from an API error body, if available. */ export function getClientErrorApiDescription(error: ClientError): string | undefined { const body = getClientErrorApiBody(error) return body?.error?.description ?? body?.description } /** @internal True if the error represents a projectUserNotFoundError. */ export function isProjectUserNotFoundClientError(error: ClientError): boolean { return getClientErrorApiType(error) === 'projectUserNotFoundError' }