UNPKG

react-discord-login

Version:

A React component for easy integration of 'Sign in with Discord' functionality into web applications

296 lines 10.9 kB
import { __awaiter, __generator } from "tslib"; /** * Normalizes Discord OAuth2 configuration parameters with sensible defaults. * * @param params - Raw Discord login parameters * @param params.clientId - Discord application client ID * @param params.redirectUri - OAuth2 redirect URI (optional) * @param params.responseType - OAuth2 response type (optional) * @param params.scopes - Discord OAuth2 scopes array (optional) * * @returns Normalized configuration object * * @example * ```ts * const config = normalizeDiscordConfig({ * clientId: '123456789012345678', * // Other params optional with defaults * }); * // Returns: { clientId, redirectUri: window.location.origin, responseType: 'code', scopes: ['identify'] } * ``` */ export var normalizeDiscordConfig = function (_a) { var clientId = _a.clientId, uri = _a.redirectUri, type = _a.responseType, scopesArray = _a.scopes; var hasWindow = typeof window !== 'undefined' && typeof window.location !== 'undefined'; var redirectUri; if (uri) { redirectUri = uri; } else if (hasWindow) { redirectUri = window.location.origin; } else { throw new Error('redirectUri must be provided when window is not available (SSR environment)'); } var responseType = type || 'code'; var scopes = scopesArray || ['identify']; return { clientId: clientId, redirectUri: redirectUri, responseType: responseType, scopes: scopes, }; }; /** * Generates Discord OAuth2 authorization URL. * * @param config - Normalized Discord OAuth2 configuration * @param config.clientId - Discord application client ID * @param config.redirectUri - OAuth2 redirect URI * @param config.responseType - OAuth2 response type ('code' or 'token') * @param config.scopes - Discord OAuth2 scopes array * * @returns Complete Discord authorization URL * * @example * ```ts * const url = generateUrl({ * clientId: '123456789012345678', * redirectUri: 'https://myapp.com/callback', * responseType: 'code', * scopes: ['identify', 'email'] * }); * // Returns: 'https://discord.com/api/oauth2/authorize?client_id=123...&scope=identify%20email' * ``` */ export var generateUrl = function (_a) { var clientId = _a.clientId, redirectUri = _a.redirectUri, responseType = _a.responseType, scopes = _a.scopes; var searchParams = new URLSearchParams(); searchParams.append('client_id', clientId); searchParams.append('response_type', responseType); searchParams.append('redirect_uri', redirectUri); searchParams.append('scope', scopes.join(' ')); return "https://discord.com/api/oauth2/authorize?".concat(searchParams.toString()); }; /** * Extracts and combines URL parameters from both query string and hash fragment. * * Discord OAuth2 can return parameters in either location depending on the response type: * - 'code' flow: parameters in query string (?code=...) * - 'token' flow: parameters in hash fragment (#access_token=...) * * @returns URLSearchParams containing combined parameters from both sources * * @internal This function is not exported and used internally by getCallbackResponse */ var getQueryAndHash = function () { var params = new URLSearchParams(); // Return empty URLSearchParams during SSR if (typeof window === 'undefined') { return params; } // Parse query parameters first var query = new URLSearchParams(window.location.search); query.forEach(function (value, key) { params.set(key, value); }); // Parse hash fragment parameters (these override query values if duplicate keys) var fragment = new URLSearchParams(window.location.hash.slice(1)); fragment.forEach(function (value, key) { params.set(key, value); }); return params; }; /** * Parses Discord OAuth2 callback response from current URL. * * Analyzes the current page URL (both query parameters and hash fragments) to detect * and parse Discord OAuth2 callback responses. Handles all three possible response types: * error, code, and token. * * @returns Parsed callback response object with discriminated union type * * @example * ```ts * // URL: https://app.com/callback?error=access_denied&error_description=User%20denied * const response = getCallbackResponse(); * if (response.type === 'error') { * console.error(response.error); // { error: 'access_denied', description: 'User denied' } * } * * // URL: https://app.com/callback?code=NhhvTDYsFcdgNLvQ * const response = getCallbackResponse(); * if (response.type === 'code') { * console.log(response.code); // { code: 'NhhvTDYsFcdgNLvQ' } * } * * // URL: https://app.com/callback#access_token=abc123&token_type=Bearer * const response = getCallbackResponse(); * if (response.type === 'token') { * console.log(response.token); // { access_token: 'abc123', token_type: 'Bearer', ... } * } * ``` * * @remarks * - Returns `{ type: null }` if no OAuth2 callback parameters are detected * - Supports both query string (?code=...) and hash fragment (#access_token=...) parsing * - Automatically handles parameter type conversion (strings to numbers for expires_in) * - Splits space-separated scope strings into arrays */ export var getCallbackResponse = function () { var params = getQueryAndHash(); var error = params.get('error'); var error_description = params.get('error_description'); var token_type = params.get('token_type'); var code = params.get('code'); if (error || error_description) { return { type: 'error', error: { error: error !== null && error !== void 0 ? error : 'unknown_error', description: error_description !== null && error_description !== void 0 ? error_description : '', }, }; } if (token_type) { var access_token = params.get('access_token'); var expires_in = params.get('expires_in'); var scope = params.get('scope'); // Validate that access_token exists for token responses if (!access_token) { return { type: 'error', error: { error: 'invalid_token_response', description: 'Token response is missing required access_token parameter', }, }; } return { type: 'token', token: { token_type: token_type, access_token: access_token, expires_in: expires_in ? Number(expires_in) : 0, scope: scope ? scope.split(' ') : [], }, }; } if (code) { return { type: 'code', code: { code: code, }, }; } return { type: null, }; }; /** * Fetches Discord user data using an OAuth2 access token. * * Makes an authenticated request to Discord's `/users/@me` endpoint to retrieve * the current user's profile information. This function is automatically called * by the useDiscordLogin hook when using the 'token' response type. * * @param token - OAuth2 token response containing access token and metadata * @param token.access_token - The Discord access token * @param token.token_type - Token type (typically 'Bearer') * * @returns Promise resolving to Discord user object * * @throws {Error} When Discord API request fails * @throws {Error} When network request fails * @throws {Error} When response parsing fails * * @example * ```ts * const tokenResponse = { * access_token: 'abc123...', * token_type: 'Bearer', * expires_in: 3600, * scope: ['identify'] * }; * * try { * const user = await fetchUser(tokenResponse); * console.log(user.username); // Discord username * console.log(user.id); // Discord user ID * } catch (error) { * console.error('Failed to fetch user:', error.message); * } * ``` * * @remarks * All errors are wrapped with descriptive messages: * - HTTP errors: "Failed to fetch user data: Discord API responded with status: 401 Unauthorized" * - Network errors: "Failed to fetch user data: Network connection failed" * - Unknown errors: "Failed to fetch user data: Unknown error occurred" */ export var fetchUser = function (token) { return __awaiter(void 0, void 0, void 0, function () { var result, userData, error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 3, , 4]); return [4 /*yield*/, fetch('https://discord.com/api/users/@me', { headers: { authorization: "".concat(token.token_type, " ").concat(token.access_token), }, })]; case 1: result = _a.sent(); if (!result.ok) { throw new Error("Discord API responded with status: ".concat(result.status, " ").concat(result.statusText)); } return [4 /*yield*/, result.json()]; case 2: userData = _a.sent(); return [2 /*return*/, userData]; case 3: error_1 = _a.sent(); if (error_1 instanceof Error) { throw new Error("Failed to fetch user data: ".concat(error_1.message)); } throw new Error('Failed to fetch user data: Unknown error occurred'); case 4: return [2 /*return*/]; } }); }); }; /** * Determines if the current URL contains Discord OAuth2 callback parameters. * * Checks both query parameters and hash fragments for the presence of Discord OAuth2 * callback indicators: 'code', 'error', or 'token_type'. Used by the useDiscordLogin * hook to decide whether to process the current URL as an OAuth2 callback. * * @returns True if OAuth2 callback parameters are detected, false otherwise * * @example * ```ts * // URL: https://app.com/callback?code=NhhvTDYsFcdgNLvQ * shouldHandleCallback(); // returns true * * // URL: https://app.com/callback#access_token=abc123&token_type=Bearer * shouldHandleCallback(); // returns true * * // URL: https://app.com/callback?error=access_denied * shouldHandleCallback(); // returns true * * // URL: https://app.com/normal-page * shouldHandleCallback(); // returns false * ``` * * @remarks * This function enables the hook to automatically detect and process OAuth2 callbacks * without requiring explicit callback registration or route matching. */ export var shouldHandleCallback = function () { var params = getQueryAndHash(); var keys = Array.from(params.keys()); var targets = ['code', 'error', 'token_type']; return targets.some(function (target) { return keys.includes(target); }); }; //# sourceMappingURL=utils.js.map