// @FILE-SUBCLASS-01-EXCEPTION /** * Client-side Ajax class for making API calls to RSX controllers * * Mirrors the PHP Ajax::call (Ajax::internal) functionality for browser-side JavaScript */ class Ajax { /** * Make an AJAX call to an RSX controller action * * All calls are automatically batched using Rsx_Ajax_Batch unless * window.rsxapp.ajax_disable_batching is true (for debugging). * * @param {string} controller - The controller class name (e.g., 'User_Controller') * @param {string} action - The action method name (e.g., 'get_profile') * @param {object} params - Parameters to send to the action * @returns {Promise} - Resolves with the return value, rejects with error */ static async call(controller, action, params = {}) { // Route through batch system return Rsx_Ajax_Batch.call(controller, action, params); } /** * DEPRECATED: Direct call implementation (preserved for reference) * This is now handled by Rsx_Ajax_Batch * @private */ static async _call_direct(controller, action, params = {}) { // Build the endpoint URL const url = `/_ajax/${controller}/${action}`; // Log the AJAX call using console_debug if (typeof Debugger !== 'undefined' && Debugger.console_debug) { Debugger.console_debug('AJAX', `Calling ${controller}.${action}`, params); } return new Promise((resolve, reject) => { $.ajax({ url: url, method: 'POST', data: params, dataType: 'json', __local_integration: true, // Bypass $.ajax override - this is the official Ajax endpoint pattern success: (response) => { // Handle console_debug messages if present if (response.console_debug && Array.isArray(response.console_debug)) { response.console_debug.forEach((msg) => { // Messages must be structured as [channel, [arguments]] if (!Array.isArray(msg) || msg.length !== 2) { throw new Error('Invalid console_debug message format - expected [channel, [arguments]]'); } const [channel, args] = msg; // Output with channel as first argument, then spread the arguments console.log(channel, ...args); }); } // Check if the response was successful if (response.success === true) { // Process the return value to instantiate any ORM models const processedValue = Rsx_Js_Model._instantiate_models_recursive(response._ajax_return_value); // Return the processed value resolve(processedValue); } else { // Handle error responses const error_type = response.error_type || 'unknown_error'; const reason = response.reason || 'Unknown error occurred'; const details = response.details || {}; // Handle specific error types switch (error_type) { case 'response_auth_required': console.error( 'The user is no longer authenticated, this is a placeholder for future code which handles this scenario.' ); // Create an error object similar to PHP exceptions const auth_error = new Error(reason); auth_error.type = 'auth_required'; auth_error.details = details; reject(auth_error); break; case 'response_unauthorized': console.error( 'The user is unauthorized to perform this action, this is a placeholder for future code which handles this scenario.' ); const unauth_error = new Error(reason); unauth_error.type = 'unauthorized'; unauth_error.details = details; reject(unauth_error); break; case 'response_form_error': // Form validation errors const form_error = new Error(reason); form_error.type = 'form_error'; form_error.details = details; reject(form_error); break; case 'response_fatal_error': // Fatal errors const fatal_error = new Error(reason); fatal_error.type = 'fatal_error'; fatal_error.details = details; // Log to server if browser error logging is enabled Debugger.log_error({ message: `Ajax Fatal Error: ${reason}`, type: 'ajax_fatal', endpoint: url, details: details, }); reject(fatal_error); break; default: // Unknown error type const generic_error = new Error(reason); generic_error.type = error_type; generic_error.details = details; reject(generic_error); break; } } }, error: (xhr, status, error) => { // Handle network or server errors let error_message = 'Network or server error'; if (xhr.responseJSON && xhr.responseJSON.message) { error_message = xhr.responseJSON.message; } else if (xhr.responseText) { try { const response = JSON.parse(xhr.responseText); if (response.message) { error_message = response.message; } } catch (e) { // If response is not JSON, use the status text error_message = `${status}: ${error}`; } } else { error_message = `${status}: ${error}`; } const network_error = new Error(error_message); network_error.type = 'network_error'; network_error.status = xhr.status; network_error.statusText = status; // Log server errors (500+) to the server if browser error logging is enabled if (xhr.status >= 500) { Debugger.log_error({ message: `Ajax Server Error ${xhr.status}: ${error_message}`, type: 'ajax_server_error', endpoint: url, status: xhr.status, statusText: status, }); } reject(network_error); }, }); }); } /** * Parses an AJAX URL into controller and action * @param {string} url - URL in format '/_ajax/Controller_Name/action_name' * @returns {Object} Object with {controller: string, action: string} * @throws {Error} If URL doesn't start with /_ajax or has invalid structure */ static ajax_url_to_controller_action(url) { if (!url.startsWith('/_ajax')) { throw new Error(`URL must start with /_ajax, got: ${url}`); } const parts = url.split('/').filter((part) => part !== ''); if (parts.length < 2) { throw new Error(`Invalid AJAX URL structure: ${url}`); } if (parts.length > 3) { throw new Error(`AJAX URL has too many segments: ${url}`); } const controller = parts[1]; const action = parts[2] || 'index'; return { controller, action }; } }