UNPKG

@wristband/react-client-auth

Version:

A lightweight React SDK that pairs with your backend server auth to initialize and sync frontend sessions via secure session cookies.

177 lines (173 loc) 6.87 kB
'use strict'; var index = require('../error/index.js'); var authProviderTypes = require('../types/auth-provider-types.js'); /* This module has browser conditionals that must be preserved */ /** * Resolves and properly formats a login URL for the Wristband Auth Provider. * * This function ensures that the login URL includes a "return_url" parameter, allowing users to be redirected back to * their original location after authentication. If the login URL already contains a return_url parameter, it will be * preserved. * * @param {string} loginUrl - The base login URL to process * @returns {string} The fully resolved login URL with appropriate query parameters * @throws {WristbandError} If loginUrl is undefined, null, empty, or not a valid URL. * * @example * // Basic usage with a relative URL * const url = resolveAuthProviderLoginUrl('/api/auth/login'); * // Returns "/api/auth/login?return_url=https%3A%2F%2Fcurrent-page.com%2Fpath" * * @example * // With a login URL that already has a return_url parameter * const url = resolveAuthProviderLoginUrl('/api/auth/login?return_url=https%3A%2F%2Fspecific-page.com'); * // Preserves existing return_url parameter */ function resolveAuthProviderLoginUrl(loginUrl) { if (!loginUrl || !loginUrl.trim()) { throw new index.WristbandError(authProviderTypes.WristbandErrorCode.INVALID_LOGIN_URL, 'WristbandAuthProvider: [loginUrl] is required'); } // For frameworks like NextJS, need to ensure this doesn't break in server-side environments. if (typeof window === 'undefined') { return loginUrl; } let resolvedUrl; try { resolvedUrl = new URL(loginUrl, window.location.origin); } catch { throw new index.WristbandError(authProviderTypes.WristbandErrorCode.INVALID_LOGIN_URL, `WristbandAuthProvider: [${loginUrl}] is not a valid loginUrl`); } // If return_url is not present, add it. if (!resolvedUrl.searchParams.has('return_url')) { resolvedUrl.searchParams.append('return_url', encodeURI(window.location.href)); } return resolvedUrl.toString(); } /** * Validates a session URL for the Wristband Auth Provider. * * This function checks that the provided session URL is properly formatted and can be resolved to a valid URL. It * does not modify the URL in any way but simply validates it. * * @param {string} sessionUrl - The session URL to validate * @throws {WristbandError} If sessionUrl is undefined, null, empty, or not a valid URL. * * @example * // Basic validation * validateAuthProviderSessionUrl('/api/auth/session'); * // No error thrown, URL is valid * * @example * // With an absolute URL * validateAuthProviderSessionUrl('https://auth.example.com/session'); * // No error thrown, URL is valid */ function validateAuthProviderSessionUrl(sessionUrl) { if (!sessionUrl || !sessionUrl.trim()) { throw new index.WristbandError(authProviderTypes.WristbandErrorCode.INVALID_SESSION_URL, 'WristbandAuthProvider: [sessionUrl] is required'); } // For frameworks like NextJS, need to ensure this doesn't break in server-side environments. if (typeof window !== 'undefined') { try { new URL(sessionUrl, window.location.origin); } catch { throw new index.WristbandError(authProviderTypes.WristbandErrorCode.INVALID_SESSION_URL, `WristbandAuthProvider: [${sessionUrl}] is not a valid sessionUrl`); } } } /** * Validates a token URL for the Wristband Auth Provider. * * This function checks that the provided token URL is properly formatted and can be resolved to a valid URL. It * does not modify the URL in any way but simply validates it. * * @param {string} tokenUrl - The token URL to validate * @throws {WristbandError} If tokenUrl is undefined, null, empty, or not a valid URL. * * @example * // Basic validation * validateAuthProviderTokenUrl('/api/auth/token'); * // No error thrown, URL is valid * * @example * // With an absolute URL * validateAuthProviderTokenUrl('https://auth.example.com/token'); * // No error thrown, URL is valid */ function validateAuthProviderTokenUrl(tokenUrl) { // For frameworks like NextJS, need to ensure this doesn't break in server-side environments. if (typeof window !== 'undefined' && tokenUrl && tokenUrl.trim()) { try { new URL(tokenUrl, window.location.origin); } catch { throw new index.WristbandError(authProviderTypes.WristbandErrorCode.INVALID_TOKEN_URL, `WristbandAuthProvider: [${tokenUrl}] is not a valid tokenUrl`); } } } /** * Checks if an error represents an HTTP error with a specific error status code. * * @param {unknown} error - The error to check. * @param {number} statusCode - The HTTP status code to check for. * @returns {boolean} True if the error is an ApiError with the specified status code; false otherwise. * @throws {TypeError} If the error is null or undefined. * * @example * try { * const response = await fetch('/api/resource'); * } catch (error) { * if (isHttpStatusError(error, 401)) { * console.log('Unauthorized'); * } * } */ function isHttpStatusError(error, statusCode) { if (error === null || error === undefined) { throw new TypeError('Argument [error] cannot be null or undefined'); } if (!(error instanceof index.ApiError)) { return false; } return error.status === statusCode; } /** * Checks if an error represents an HTTP 401 Unauthorized error. * * @param {unknown} error - The error to check. * @returns {boolean} True if the error is an ApiError with a 401 status code; false otherwise. * @throws {TypeError} If the error is null or undefined. * * @example * try { * const response = await fetch('/api/resource'); * } catch (error) { * if (isUnauthorizedError(error)) { * console.log('Unauthorized'); * } * } */ const isUnauthorizedError = (error) => isHttpStatusError(error, 401); // Helper function to check if error is 4xx const is4xxError = (error) => { if (error === null || error === undefined) { throw new TypeError('Argument [error] cannot be null or undefined'); } if (!(error instanceof index.ApiError) || !error.status) { return false; } return error?.status >= 400 && error?.status < 500; }; // Helper function to delay execution const delay = (ms) => { return new Promise((resolve) => setTimeout(resolve, ms)); }; exports.delay = delay; exports.is4xxError = is4xxError; exports.isHttpStatusError = isHttpStatusError; exports.isUnauthorizedError = isUnauthorizedError; exports.resolveAuthProviderLoginUrl = resolveAuthProviderLoginUrl; exports.validateAuthProviderSessionUrl = validateAuthProviderSessionUrl; exports.validateAuthProviderTokenUrl = validateAuthProviderTokenUrl;