diff --git a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt index 0a021535..7a7ada4b 100644 --- a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt +++ b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt @@ -293,6 +293,7 @@ class GutenbergView : WebView { val gbKitConfig = """ window.GBKit = { + "siteURL": "${configuration.siteURL}", "siteApiRoot": "${configuration.siteApiRoot}", "siteApiNamespace": ${configuration.siteApiNamespace.joinToString(",", "[", "]") { "\"$it\"" }}, "namespaceExcludedPaths": ${configuration.namespaceExcludedPaths.joinToString(",", "[", "]") { "\"$it\"" }}, diff --git a/src/utils/ajax.js b/src/utils/ajax.js new file mode 100644 index 00000000..e009c881 --- /dev/null +++ b/src/utils/ajax.js @@ -0,0 +1,80 @@ +/** + * Internal dependencies + */ +import { getGBKit } from './bridge'; +import { warn, debug } from './logger'; + +/** + * GutenbergKit lacks authentication cookies required for AJAX requests. + * This configures a root URL and authentication header for AJAX requests. + * + * @return {void} + */ +export function configureAjax() { + window.wp = window.wp || {}; + window.wp.ajax = window.wp.ajax || {}; + window.wp.ajax.settings = window.wp.ajax.settings || {}; + + const { siteURL, authHeader } = getGBKit(); + configureAjaxUrl( siteURL ); + configureAjaxAuth( authHeader ); +} + +function configureAjaxUrl( siteURL ) { + if ( ! siteURL ) { + warn( 'Unable to configure AJAX URL without siteURL' ); + return; + } + + // Global used within WordPress admin pages + window.ajaxurl = `${ siteURL }/wp-admin/admin-ajax.php`; + // Global used by WordPress' JavaScript API + window.wp.ajax.settings.url = `${ siteURL }/wp-admin/admin-ajax.php`; + + debug( 'AJAX URL configured' ); +} + +function configureAjaxAuth( authHeader ) { + if ( ! authHeader ) { + warn( 'Unable to configure AJAX auth without authHeader' ); + return; + } + + window.jQuery?.ajaxSetup( { + headers: { + Authorization: authHeader, + }, + } ); + + const originalSend = window.wp.ajax.send; + window.wp.ajax.send = function ( options ) { + const originalBeforeSend = options.beforeSend; + + options.beforeSend = function ( xhr ) { + xhr.setRequestHeader( 'Authorization', authHeader ); + + if ( typeof originalBeforeSend === 'function' ) { + originalBeforeSend( xhr ); + } + }; + + return originalSend.call( this, options ); + }; + + const originalPost = window.wp.ajax.post; + window.wp.ajax.post = function ( options ) { + const originalBeforeSend = options.beforeSend; + + options.beforeSend = function ( xhr ) { + xhr.setRequestHeader( 'Authorization', authHeader ); + + if ( typeof originalBeforeSend === 'function' ) { + originalBeforeSend( xhr ); + } + }; + + return originalPost.call( this, options ); + }; + + debug( 'AJAX auth configured' ); +} diff --git a/src/utils/ajax.test.js b/src/utils/ajax.test.js new file mode 100644 index 00000000..a9f988d5 --- /dev/null +++ b/src/utils/ajax.test.js @@ -0,0 +1,491 @@ +/** + * External dependencies + */ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; + +/** + * Internal dependencies + */ +import { configureAjax } from './ajax'; +import * as bridge from './bridge'; +import * as logger from './logger'; + +vi.mock( './bridge' ); +vi.mock( './logger' ); + +describe( 'configureAjax', () => { + let originalWindow; + let mockJQueryAjaxSetup; + let originalWpAjaxSend; + let originalWpAjaxPost; + + beforeEach( () => { + vi.clearAllMocks(); + + // Store original window state + originalWindow = { + wp: global.window.wp, + ajaxurl: global.window.ajaxurl, + jQuery: global.window.jQuery, + }; + + // Reset window.wp + global.window.wp = undefined; + global.window.ajaxurl = undefined; + + // Mock jQuery + mockJQueryAjaxSetup = vi.fn(); + global.window.jQuery = { + ajaxSetup: mockJQueryAjaxSetup, + }; + + // Create mock functions for wp.ajax methods + originalWpAjaxSend = vi.fn( ( options ) => { + // Simulate calling beforeSend if it exists + if ( options?.beforeSend ) { + const mockXhr = { setRequestHeader: vi.fn() }; + options.beforeSend( mockXhr ); + } + return Promise.resolve(); + } ); + + originalWpAjaxPost = vi.fn( ( options ) => { + // Simulate calling beforeSend if it exists + if ( options?.beforeSend ) { + const mockXhr = { setRequestHeader: vi.fn() }; + options.beforeSend( mockXhr ); + } + return Promise.resolve(); + } ); + } ); + + afterEach( () => { + // Restore original window state + global.window.wp = originalWindow.wp; + global.window.ajaxurl = originalWindow.ajaxurl; + global.window.jQuery = originalWindow.jQuery; + } ); + + describe( 'URL configuration', () => { + it( 'should configure ajax URLs when siteURL is provided', () => { + bridge.getGBKit.mockReturnValue( { + siteURL: 'https://example.com', + authHeader: null, + } ); + + configureAjax(); + + expect( global.window.ajaxurl ).toBe( + 'https://example.com/wp-admin/admin-ajax.php' + ); + expect( global.window.wp.ajax.settings.url ).toBe( + 'https://example.com/wp-admin/admin-ajax.php' + ); + expect( logger.debug ).toHaveBeenCalledWith( + 'AJAX URL configured' + ); + } ); + + it( 'should log warning when siteURL is missing', () => { + bridge.getGBKit.mockReturnValue( { + siteURL: null, + authHeader: 'Bearer token', + } ); + + configureAjax(); + + expect( logger.warn ).toHaveBeenCalledWith( + 'Unable to configure AJAX URL without siteURL' + ); + expect( global.window.ajaxurl ).toBeUndefined(); + } ); + + it( 'should handle undefined siteURL', () => { + bridge.getGBKit.mockReturnValue( { + authHeader: 'Bearer token', + } ); + + configureAjax(); + + expect( logger.warn ).toHaveBeenCalledWith( + 'Unable to configure AJAX URL without siteURL' + ); + expect( global.window.ajaxurl ).toBeUndefined(); + } ); + + it( 'should properly initialize window.wp.ajax hierarchy', () => { + bridge.getGBKit.mockReturnValue( { + siteURL: 'https://example.com', + authHeader: null, + } ); + + // Ensure window.wp doesn't exist initially + expect( global.window.wp ).toBeUndefined(); + + configureAjax(); + + expect( global.window.wp ).toBeDefined(); + expect( global.window.wp.ajax ).toBeDefined(); + expect( global.window.wp.ajax.settings ).toBeDefined(); + } ); + } ); + + describe( 'Auth configuration', () => { + beforeEach( () => { + // Setup wp.ajax with original methods + global.window.wp = { + ajax: { + send: originalWpAjaxSend, + post: originalWpAjaxPost, + settings: {}, + }, + }; + } ); + + it( 'should configure jQuery ajax with auth header', () => { + bridge.getGBKit.mockReturnValue( { + siteURL: null, + authHeader: 'Bearer test-token', + } ); + + configureAjax(); + + expect( mockJQueryAjaxSetup ).toHaveBeenCalledWith( { + headers: { + Authorization: 'Bearer test-token', + }, + } ); + expect( logger.debug ).toHaveBeenCalledWith( + 'AJAX auth configured' + ); + } ); + + it( 'should wrap wp.ajax.send with auth header', async () => { + bridge.getGBKit.mockReturnValue( { + siteURL: null, + authHeader: 'Bearer send-token', + } ); + + configureAjax(); + + // Call the wrapped send method + const options = { data: 'test' }; + await global.window.wp.ajax.send( options ); + + // Verify the original was called + expect( originalWpAjaxSend ).toHaveBeenCalled(); + + // Verify beforeSend was added + const calledOptions = originalWpAjaxSend.mock.calls[ 0 ][ 0 ]; + expect( calledOptions.beforeSend ).toBeDefined(); + + // Verify auth header is set + const mockXhr = { setRequestHeader: vi.fn() }; + calledOptions.beforeSend( mockXhr ); + expect( mockXhr.setRequestHeader ).toHaveBeenCalledWith( + 'Authorization', + 'Bearer send-token' + ); + } ); + + it( 'should wrap wp.ajax.post with auth header', async () => { + bridge.getGBKit.mockReturnValue( { + siteURL: null, + authHeader: 'Bearer post-token', + } ); + + configureAjax(); + + // Call the wrapped post method + const options = { action: 'test_action' }; + await global.window.wp.ajax.post( options ); + + // Verify the original was called + expect( originalWpAjaxPost ).toHaveBeenCalled(); + + // Verify beforeSend was added + const calledOptions = originalWpAjaxPost.mock.calls[ 0 ][ 0 ]; + expect( calledOptions.beforeSend ).toBeDefined(); + + // Verify auth header is set + const mockXhr = { setRequestHeader: vi.fn() }; + calledOptions.beforeSend( mockXhr ); + expect( mockXhr.setRequestHeader ).toHaveBeenCalledWith( + 'Authorization', + 'Bearer post-token' + ); + } ); + + it( 'should preserve original beforeSend in wp.ajax.send', async () => { + bridge.getGBKit.mockReturnValue( { + siteURL: null, + authHeader: 'Bearer preserve-token', + } ); + + configureAjax(); + + // Call with existing beforeSend + const originalBeforeSend = vi.fn(); + const options = { beforeSend: originalBeforeSend }; + await global.window.wp.ajax.send( options ); + + // Get the wrapped beforeSend + const calledOptions = originalWpAjaxSend.mock.calls[ 0 ][ 0 ]; + const mockXhr = { setRequestHeader: vi.fn() }; + calledOptions.beforeSend( mockXhr ); + + // Verify both auth header and original beforeSend were called + expect( mockXhr.setRequestHeader ).toHaveBeenCalledWith( + 'Authorization', + 'Bearer preserve-token' + ); + expect( originalBeforeSend ).toHaveBeenCalledWith( mockXhr ); + } ); + + it( 'should preserve original beforeSend in wp.ajax.post', async () => { + bridge.getGBKit.mockReturnValue( { + siteURL: null, + authHeader: 'Bearer preserve-post-token', + } ); + + configureAjax(); + + // Call with existing beforeSend + const originalBeforeSend = vi.fn(); + const options = { beforeSend: originalBeforeSend }; + await global.window.wp.ajax.post( options ); + + // Get the wrapped beforeSend + const calledOptions = originalWpAjaxPost.mock.calls[ 0 ][ 0 ]; + const mockXhr = { setRequestHeader: vi.fn() }; + calledOptions.beforeSend( mockXhr ); + + // Verify both auth header and original beforeSend were called + expect( mockXhr.setRequestHeader ).toHaveBeenCalledWith( + 'Authorization', + 'Bearer preserve-post-token' + ); + expect( originalBeforeSend ).toHaveBeenCalledWith( mockXhr ); + } ); + + it( 'should log warning when authHeader is missing', () => { + bridge.getGBKit.mockReturnValue( { + siteURL: 'https://example.com', + authHeader: null, + } ); + + configureAjax(); + + expect( logger.warn ).toHaveBeenCalledWith( + 'Unable to configure AJAX auth without authHeader' + ); + expect( mockJQueryAjaxSetup ).not.toHaveBeenCalled(); + } ); + + it( 'should handle undefined authHeader', () => { + bridge.getGBKit.mockReturnValue( { + siteURL: 'https://example.com', + } ); + + configureAjax(); + + expect( logger.warn ).toHaveBeenCalledWith( + 'Unable to configure AJAX auth without authHeader' + ); + expect( mockJQueryAjaxSetup ).not.toHaveBeenCalled(); + } ); + } ); + + describe( 'Integration tests', () => { + it( 'should configure both URL and auth when both are provided', () => { + bridge.getGBKit.mockReturnValue( { + siteURL: 'https://example.com', + authHeader: 'Bearer full-token', + } ); + + // Setup wp.ajax with methods + global.window.wp = { + ajax: { + send: originalWpAjaxSend, + post: originalWpAjaxPost, + settings: {}, + }, + }; + + configureAjax(); + + // Check URL configuration + expect( global.window.ajaxurl ).toBe( + 'https://example.com/wp-admin/admin-ajax.php' + ); + expect( global.window.wp.ajax.settings.url ).toBe( + 'https://example.com/wp-admin/admin-ajax.php' + ); + + // Check auth configuration + expect( mockJQueryAjaxSetup ).toHaveBeenCalledWith( { + headers: { + Authorization: 'Bearer full-token', + }, + } ); + + // Check debug logs + expect( logger.debug ).toHaveBeenCalledWith( + 'AJAX URL configured' + ); + expect( logger.debug ).toHaveBeenCalledWith( + 'AJAX auth configured' + ); + } ); + + it( 'should handle empty configuration object', () => { + bridge.getGBKit.mockReturnValue( {} ); + + configureAjax(); + + expect( logger.warn ).toHaveBeenCalledWith( + 'Unable to configure AJAX URL without siteURL' + ); + expect( logger.warn ).toHaveBeenCalledWith( + 'Unable to configure AJAX auth without authHeader' + ); + } ); + } ); + + describe( 'Edge cases', () => { + it( 'should handle missing jQuery gracefully', () => { + bridge.getGBKit.mockReturnValue( { + siteURL: 'https://example.com', + authHeader: 'Bearer no-jquery', + } ); + + delete global.window.jQuery; + + expect( () => configureAjax() ).not.toThrow(); + expect( logger.debug ).toHaveBeenCalledWith( + 'AJAX URL configured' + ); + expect( logger.debug ).toHaveBeenCalledWith( + 'AJAX auth configured' + ); + } ); + + it( 'should handle undefined jQuery', () => { + bridge.getGBKit.mockReturnValue( { + siteURL: 'https://example.com', + authHeader: 'Bearer undefined-jquery', + } ); + + global.window.jQuery = undefined; + + expect( () => configureAjax() ).not.toThrow(); + expect( logger.debug ).toHaveBeenCalledWith( + 'AJAX URL configured' + ); + expect( logger.debug ).toHaveBeenCalledWith( + 'AJAX auth configured' + ); + } ); + + it( 'should handle missing wp.ajax.send method', () => { + bridge.getGBKit.mockReturnValue( { + siteURL: 'https://example.com', + authHeader: 'Bearer no-send', + } ); + + global.window.wp = { + ajax: { + post: originalWpAjaxPost, + settings: {}, + }, + }; + + expect( () => configureAjax() ).not.toThrow(); + + // Should still wrap post + expect( global.window.wp.ajax.post ).not.toBe( originalWpAjaxPost ); + } ); + + it( 'should handle missing wp.ajax.post method', () => { + bridge.getGBKit.mockReturnValue( { + siteURL: 'https://example.com', + authHeader: 'Bearer no-post', + } ); + + global.window.wp = { + ajax: { + send: originalWpAjaxSend, + settings: {}, + }, + }; + + expect( () => configureAjax() ).not.toThrow(); + + // Should still wrap send + expect( global.window.wp.ajax.send ).not.toBe( originalWpAjaxSend ); + } ); + + it( 'should handle missing wp.ajax entirely', () => { + bridge.getGBKit.mockReturnValue( { + siteURL: 'https://example.com', + authHeader: 'Bearer no-ajax', + } ); + + global.window.wp = {}; + + expect( () => configureAjax() ).not.toThrow(); + + // Should create ajax object + expect( global.window.wp.ajax ).toBeDefined(); + expect( global.window.wp.ajax.settings ).toBeDefined(); + } ); + + it( 'should work with window.wp already partially initialized', () => { + bridge.getGBKit.mockReturnValue( { + siteURL: 'https://example.com', + authHeader: null, + } ); + + // Pre-existing wp object with other properties + global.window.wp = { + data: { someData: 'test' }, + }; + + configureAjax(); + + // Should preserve existing properties + expect( global.window.wp.data ).toEqual( { someData: 'test' } ); + + // Should add ajax properties + expect( global.window.wp.ajax ).toBeDefined(); + expect( global.window.wp.ajax.settings.url ).toBe( + 'https://example.com/wp-admin/admin-ajax.php' + ); + } ); + + it( 'should work when wp.ajax is partially initialized', () => { + bridge.getGBKit.mockReturnValue( { + siteURL: 'https://example.com', + authHeader: null, + } ); + + // Pre-existing wp.ajax object without settings + global.window.wp = { + ajax: { + someMethod: vi.fn(), + }, + }; + + configureAjax(); + + // Should preserve existing methods + expect( global.window.wp.ajax.someMethod ).toBeDefined(); + + // Should add settings + expect( global.window.wp.ajax.settings ).toBeDefined(); + expect( global.window.wp.ajax.settings.url ).toBe( + 'https://example.com/wp-admin/admin-ajax.php' + ); + } ); + } ); +} ); diff --git a/src/utils/api-fetch.js b/src/utils/api-fetch.js index 6c3e946a..3ce2f551 100644 --- a/src/utils/api-fetch.js +++ b/src/utils/api-fetch.js @@ -18,7 +18,7 @@ import { getGBKit } from './bridge'; * * @return {void} */ -export function initializeApiFetch() { +export function configureApiFetch() { const { siteApiRoot = '' } = getGBKit(); apiFetch.use( apiFetch.createRootURLMiddleware( siteApiRoot ) ); diff --git a/src/utils/api-fetch.test.js b/src/utils/api-fetch.test.js index 21726468..48460ea6 100644 --- a/src/utils/api-fetch.test.js +++ b/src/utils/api-fetch.test.js @@ -19,7 +19,7 @@ import apiFetch from '@wordpress/api-fetch'; /** * Internal dependencies */ -import { initializeApiFetch } from './api-fetch'; +import { configureApiFetch } from './api-fetch'; import * as bridge from './bridge'; vi.mock( './bridge' ); @@ -34,7 +34,7 @@ describe( 'api-fetch credentials handling', () => { } ); // Initialize middleware once - it will persist across all tests - initializeApiFetch(); + configureApiFetch(); } ); beforeEach( () => { diff --git a/src/utils/bundled-editor.jsx b/src/utils/bundled-editor.jsx index b32f51c1..69608322 100644 --- a/src/utils/bundled-editor.jsx +++ b/src/utils/bundled-editor.jsx @@ -6,7 +6,8 @@ import { createRoot, StrictMode } from '@wordpress/element'; /** * Internal dependencies */ -import { initializeApiFetch } from './api-fetch'; +import { configureApiFetch } from './api-fetch'; +import { configureAjax } from './ajax'; import { awaitGBKitGlobal, editorLoaded } from './bridge'; import { configureLocale } from './localization'; import EditorLoadError from '../components/editor-load-error'; @@ -37,7 +38,7 @@ export function initializeBundledEditor() { } function initializeApiAndLocale() { - initializeApiFetch(); + configureApiFetch(); return configureLocale(); } @@ -47,6 +48,7 @@ function importEditor() { function initializeEditor( editorModule ) { const { initializeEditor: _initializeEditor } = editorModule; + configureAjax(); _initializeEditor(); } diff --git a/src/utils/remote-editor.jsx b/src/utils/remote-editor.jsx index 480abe20..a3f0ccc4 100644 --- a/src/utils/remote-editor.jsx +++ b/src/utils/remote-editor.jsx @@ -3,7 +3,7 @@ */ import { awaitGBKitGlobal } from './bridge'; import { loadEditorAssets } from './editor-loader'; -import { initializeVideoPressAjaxBridge } from './videopress-bridge'; +import { configureAjax } from './ajax'; import { error, warn } from './logger'; import { isDevMode } from './dev-mode'; import './editor-styles.js'; @@ -32,7 +32,7 @@ export function initializeRemoteEditor() { .then( importL10n ) .then( configureLocale ) // Configure locale before loading modules with strings .then( loadApiFetch ) - .then( initializeApiFetch ) // Configure API fetch before loading remaining modules + .then( configureApiFetch ) // Configure api-fetch before loading remaining modules .then( loadRemainingAssets ) .then( initializeEditor ) .catch( handleError ); @@ -61,21 +61,21 @@ function loadRemainingAssets() { } ); } -function initializeApiFetch( assetsResult ) { +function configureApiFetch( assetsResult ) { return import( './api-fetch' ).then( - ( { initializeApiFetch: _initializeApiFetch } ) => { - _initializeApiFetch(); + ( { configureApiFetch: _configureApiFetch } ) => { + _configureApiFetch(); return assetsResult; } ); } function initializeEditor( assetsResult ) { - initializeVideoPressAjaxBridge(); - const { allowedBlockTypes } = assetsResult; return import( './editor' ).then( ( { initializeEditor: _initializeEditor } ) => { + configureAjax(); + _initializeEditor( { allowedBlockTypes } ); } ); diff --git a/src/utils/remote-editor.test.jsx b/src/utils/remote-editor.test.jsx index cd2b1efa..e107b4d3 100644 --- a/src/utils/remote-editor.test.jsx +++ b/src/utils/remote-editor.test.jsx @@ -10,9 +10,9 @@ import { initializeRemoteEditor } from './remote-editor.jsx'; import { awaitGBKitGlobal } from './bridge'; import { loadEditorAssets } from './editor-loader.js'; import { configureLocale } from './localization'; -import { initializeApiFetch } from './api-fetch'; +import { configureApiFetch } from './api-fetch'; import { initializeEditor } from './editor'; -import { initializeVideoPressAjaxBridge } from './videopress-bridge'; +import { configureAjax } from './ajax'; import { isDevMode } from './dev-mode'; import { error, warn } from './logger'; @@ -29,15 +29,15 @@ vi.mock( './localization', () => ( { } ) ); vi.mock( './api-fetch', () => ( { - initializeApiFetch: vi.fn(), + configureApiFetch: vi.fn(), } ) ); vi.mock( './editor', () => ( { initializeEditor: vi.fn(), } ) ); -vi.mock( './videopress-bridge', () => ( { - initializeVideoPressAjaxBridge: vi.fn(), +vi.mock( './ajax', () => ( { + configureAjax: vi.fn(), } ) ); vi.mock( './dev-mode', () => ( { @@ -92,16 +92,16 @@ describe( 'Remote Editor Loading Sequence', () => { return Promise.resolve(); } ); - initializeApiFetch.mockImplementation( () => { - loadSequence.push( { action: 'initializeApiFetch' } ); + configureApiFetch.mockImplementation( () => { + loadSequence.push( { action: 'configureApiFetch' } ); } ); initializeEditor.mockImplementation( () => { loadSequence.push( { action: 'initializeEditor' } ); } ); - initializeVideoPressAjaxBridge.mockImplementation( () => { - loadSequence.push( { action: 'initializeVideoPressAjaxBridge' } ); + configureAjax.mockImplementation( () => { + loadSequence.push( { action: 'configureAjax' } ); } ); isDevMode.mockReturnValue( false ); @@ -141,7 +141,7 @@ describe( 'Remote Editor Loading Sequence', () => { arrayEquals( item.allowedPackages, API_FETCH_PACKAGES ) ); const apiFetchInitIndex = loadSequence.findIndex( - ( item ) => item.action === 'initializeApiFetch' + ( item ) => item.action === 'configureApiFetch' ); const remainingLoadIndex = loadSequence.findIndex( ( item ) => @@ -194,7 +194,7 @@ describe( 'Remote Editor Loading Sequence', () => { await initializeRemoteEditor(); const apiFetchInitIndex = loadSequence.findIndex( - ( item ) => item.action === 'initializeApiFetch' + ( item ) => item.action === 'configureApiFetch' ); const remainingLoadIndex = loadSequence.findIndex( ( item ) => @@ -285,16 +285,16 @@ describe( 'Remote Editor Loading Sequence', () => { ); } ); - it( 'should initialize VideoPress bridge before editor', async () => { + it( 'should configure ajax before editor', async () => { await initializeRemoteEditor(); - const videoPressIndex = loadSequence.findIndex( - ( item ) => item.action === 'initializeVideoPressAjaxBridge' + const ajaxIndex = loadSequence.findIndex( + ( item ) => item.action === 'configureAjax' ); const editorIndex = loadSequence.findIndex( ( item ) => item.action === 'initializeEditor' ); - expect( videoPressIndex ).toBeLessThan( editorIndex ); + expect( ajaxIndex ).toBeLessThan( editorIndex ); } ); } ); diff --git a/src/utils/videopress-bridge.js b/src/utils/videopress-bridge.js deleted file mode 100644 index 7bed2ee2..00000000 --- a/src/utils/videopress-bridge.js +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Internal dependencies - */ -import { getGBKit } from './bridge'; -import { warn, debug, error } from './logger'; - -/** - * VideoPress AJAX to REST API bridge. - * - * GutenbergKit lacks authentication cookies required for AJAX requests. - * This module overrides wp.media.ajax to bridge specific VideoPress AJAX - * requests to their corresponding REST API endpoints. - */ - -/** - * Initializes the VideoPress AJAX bridge. - * - * This function overrides wp.media.ajax to intercept VideoPress-specific - * AJAX requests and redirect them to the appropriate REST API endpoints. - * - * @return {void} - */ -export function initializeVideoPressAjaxBridge() { - // Ensure necessary globals are available - if ( ! window.wp || ! window.wp.apiFetch ) { - warn( 'VideoPress bridge: wp.apiFetch not available' ); - return; - } - - // Initialize wp.ajax if not already present - window.wp.ajax = window.wp.ajax || {}; - window.wp.ajax.settings = window.wp.ajax.settings || {}; - - // Set up AJAX settings with site URL - const { siteURL } = getGBKit(); - if ( siteURL ) { - window.wp.ajax.settings.url = `${ siteURL }/wp-admin/admin-ajax.php`; - } - - // Store original wp.media.ajax function if it exists - const originalMediaAjax = window.wp.media?.ajax; - - // Override wp.media.ajax - window.wp.media = window.wp.media || {}; - window.wp.media.ajax = ( ...args ) => { - const [ action ] = args; - - // Handle VideoPress upload JWT request - if ( action === 'videopress-get-upload-jwt' ) { - return handleVideoPressUploadJWT(); - } - - // Fall back to original function or default behavior - if ( originalMediaAjax ) { - return originalMediaAjax( ...args ); - } - - // If no original function exists, return a rejected promise - const deferred = - window.jQuery?.Deferred?.() || createFallbackDeferred(); - deferred.reject( new Error( `Unhandled AJAX action: ${ action }` ) ); - return deferred.promise(); - }; - - debug( 'VideoPress AJAX bridge initialized' ); -} - -/** - * Handles the VideoPress upload JWT request by calling the REST API. - * - * @return {Promise} jQuery Deferred promise that resolves with the JWT response. - */ -function handleVideoPressUploadJWT() { - const deferred = window.jQuery?.Deferred?.() || createFallbackDeferred(); - - window.wp - .apiFetch( { - path: '/wpcom/v2/videopress/upload-jwt', - method: 'POST', - } ) - .then( ( response ) => { - if ( response.error ) { - deferred.reject( response.error ); - } else { - // Transform the response to match expected AJAX format - const processedResponse = { - ...response, - upload_action_url: response.upload_url, - }; - delete processedResponse.upload_url; - - debug( - 'VideoPress JWT obtained successfully', - processedResponse - ); - deferred.resolve( processedResponse ); - } - } ) - .catch( ( err ) => { - error( 'VideoPress JWT request failed', err ); - deferred.reject( err ); - } ); - - return deferred.promise(); -} - -/** - * Creates a fallback deferred object if jQuery is not available. - * - * @return {Object} Deferred-like object with resolve, reject, and promise methods. - */ -function createFallbackDeferred() { - let resolveCallback, rejectCallback; - const promise = new Promise( ( resolve, reject ) => { - resolveCallback = resolve; - rejectCallback = reject; - } ); - - return { - resolve: resolveCallback, - reject: rejectCallback, - promise: () => promise, - }; -}