diff --git a/src/ipc-utils.ts b/src/ipc-utils.ts index 80b9cb99a..37b1183d0 100644 --- a/src/ipc-utils.ts +++ b/src/ipc-utils.ts @@ -19,6 +19,7 @@ type SnapshotKeyValueEventData = { export interface IpcEvents { 'add-site': [ void ]; + 'add-site-with-blueprint': [ { blueprintJson: string } ]; 'auth-updated': [ { token: StoredToken } | { error: unknown } ]; 'on-export': [ ImportExportEventData, string ]; 'on-import': [ ImportExportEventData, string ]; diff --git a/src/lib/oauth.ts b/src/lib/oauth.ts index 1d83b3c2f..80e8514d8 100644 --- a/src/lib/oauth.ts +++ b/src/lib/oauth.ts @@ -103,5 +103,19 @@ export async function onOpenUrlCallback( url: string ) { if ( remoteSiteId && studioSiteId ) { void sendIpcEventToRenderer( 'sync-connect-site', { remoteSiteId, studioSiteId } ); } + } else if ( host === 'add-site' ) { + const blueprintBase64 = searchParams.get( 'blueprint' ); + if ( blueprintBase64 ) { + try { + // Decode the base64-encoded blueprint JSON + const blueprintJson = Buffer.from( blueprintBase64, 'base64' ).toString( 'utf-8' ); + // Validate it's valid JSON + JSON.parse( blueprintJson ); + void sendIpcEventToRenderer( 'add-site-with-blueprint', { blueprintJson } ); + } catch ( error ) { + Sentry.captureException( error ); + console.error( 'Failed to parse blueprint from deeplink:', error ); + } + } } } diff --git a/src/lib/tests/oauth.test.ts b/src/lib/tests/oauth.test.ts index e4d744f71..8cff3fa97 100644 --- a/src/lib/tests/oauth.test.ts +++ b/src/lib/tests/oauth.test.ts @@ -213,4 +213,48 @@ describe( 'onOpenUrlCallback', () => { expect( sendIpcEventToRenderer ).not.toHaveBeenCalled(); } ); } ); + + describe( 'add-site callback', () => { + it( 'should handle add-site with valid base64-encoded blueprint', async () => { + const blueprintData = { + steps: [ { step: 'login', username: 'admin' } ], + meta: { title: 'Test Blueprint', description: 'A test blueprint' }, + }; + const blueprintJson = JSON.stringify( blueprintData ); + const blueprintBase64 = Buffer.from( blueprintJson ).toString( 'base64' ); + const url = `studio://add-site?blueprint=${ blueprintBase64 }`; + + await onOpenUrlCallback( url ); + + expect( sendIpcEventToRenderer ).toHaveBeenCalledWith( 'add-site-with-blueprint', { + blueprintJson, + } ); + } ); + + it( 'should not send event if blueprint parameter is missing', async () => { + const url = 'studio://add-site'; + await onOpenUrlCallback( url ); + + expect( sendIpcEventToRenderer ).not.toHaveBeenCalled(); + } ); + + it( 'should handle invalid base64-encoded blueprint gracefully', async () => { + const url = 'studio://add-site?blueprint=invalid-base64!!!'; + await onOpenUrlCallback( url ); + + // Should not throw and should not send event + expect( sendIpcEventToRenderer ).not.toHaveBeenCalled(); + } ); + + it( 'should handle invalid JSON in blueprint gracefully', async () => { + const invalidJson = 'not valid json'; + const blueprintBase64 = Buffer.from( invalidJson ).toString( 'base64' ); + const url = `studio://add-site?blueprint=${ blueprintBase64 }`; + + await onOpenUrlCallback( url ); + + // Should not throw and should not send event + expect( sendIpcEventToRenderer ).not.toHaveBeenCalled(); + } ); + } ); } ); diff --git a/src/modules/add-site/index.tsx b/src/modules/add-site/index.tsx index 0586d976e..2ac554761 100644 --- a/src/modules/add-site/index.tsx +++ b/src/modules/add-site/index.tsx @@ -239,6 +239,7 @@ export default function AddSite( { className, variant = 'outlined' }: AddSitePro const { __ } = useI18n(); const [ showModal, setShowModal ] = useState( false ); const [ nameSuggested, setNameSuggested ] = useState( false ); + const [ initialPath, setInitialPath ] = useState< string >( '/' ); const defaultPhpVersion = useRootSelector( selectDefaultPhpVersion ); const defaultWordPressVersion = useRootSelector( selectDefaultWordPressVersion ); const [ blueprintPreferredVersions, setBlueprintPreferredVersions ] = useState< @@ -314,15 +315,46 @@ export default function AddSite( { className, variant = 'outlined' }: AddSitePro defaultPhpVersion, ] ); - const openModal = useCallback( () => { - if ( ! isUninitialized ) { - void refetch(); - } - setShowModal( true ); - }, [ refetch, isUninitialized ] ); + const openModal = useCallback( + ( options?: { initialPath?: string; blueprint?: Blueprint } ) => { + if ( ! isUninitialized ) { + void refetch(); + } + if ( options?.initialPath ) { + setInitialPath( options.initialPath ); + } + if ( options?.blueprint ) { + setSelectedBlueprint( options.blueprint ); + // Apply blueprint's preferred versions if available + if ( options.blueprint.blueprint?.preferredVersions ) { + const preferredVersions = options.blueprint.blueprint.preferredVersions as { + php?: string; + wp?: string; + }; + setBlueprintPreferredVersions( preferredVersions ); + if ( preferredVersions.php && preferredVersions.php !== 'latest' ) { + setPhpVersion( preferredVersions.php ); + } + if ( preferredVersions.wp && preferredVersions.wp !== 'latest' ) { + setWpVersion( preferredVersions.wp ); + } + } + } + setShowModal( true ); + }, + [ + refetch, + isUninitialized, + setSelectedBlueprint, + setBlueprintPreferredVersions, + setPhpVersion, + setWpVersion, + ] + ); const closeModal = useCallback( () => { setShowModal( false ); + setInitialPath( '/' ); resetForm(); }, [ resetForm ] ); @@ -382,10 +414,30 @@ export default function AddSite( { className, variant = 'outlined' }: AddSitePro openModal(); } ); + useIpcListener( 'add-site-with-blueprint', ( _event, { blueprintJson } ) => { + if ( isAnySiteProcessing ) { + return; + } + try { + const blueprintData = JSON.parse( blueprintJson ); + const blueprint: Blueprint = { + slug: `deeplink-${ Date.now() }`, + title: blueprintData.meta?.title || __( 'Custom Blueprint' ), + excerpt: blueprintData.meta?.description || __( 'Blueprint from deeplink' ), + image: '', + playground_url: '', + blueprint: blueprintData, + }; + openModal( { initialPath: '/blueprint/create', blueprint } ); + } catch ( error ) { + console.error( 'Failed to parse blueprint from IPC event:', error ); + } + } ); + return ( <> - + openModal() } disabled={ isAnySiteProcessing } > { __( 'Add site' ) }