Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace using BroadcastChannel by injecting error message into error templates #104

Merged
merged 7 commits into from
Nov 6, 2018
17 changes: 1 addition & 16 deletions wp-admin/error.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,7 @@
esc_html__( 'Something went wrong which prevented WordPress from serving a response. Please check your error logs.', 'pwa' )
);
ob_start();
?>
<details id="error-details" hidden>
<summary><?php esc_html_e( 'More details', 'pwa' ); ?></summary>
<iframe id="error-details__iframe" style="width:100%;" srcdoc=""></iframe>
<script>
function renderErrorDetails( data ) {
if ( data.bodyText.trim().length ) {
const details = document.getElementById( 'error-details' );
details.querySelector( 'iframe' ).srcdoc = data.bodyText;
details.hidden = false;
}
}
</script>
<?php wp_print_service_worker_error_details_script( 'renderErrorDetails' ); ?>
</details>
<?php
wp_service_worker_error_details_template();
$content .= ob_get_clean();
break;
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,13 +348,19 @@ public function serve( WP_Service_Worker_Scripts $scripts ) {
$server_error_precache_entry = apply_filters( 'wp_server_error_precache_entry', $server_error_precache_entry );

} else {
$revision = PWA_VERSION;
if ( WP_DEBUG ) {
$revision .= filemtime( PWA_PLUGIN_DIR . '/wp-admin/error.php' );
$revision .= filemtime( PWA_PLUGIN_DIR . '/wp-includes/service-workers.php' );
}

$offline_error_precache_entry = array(
'url' => add_query_arg( 'code', 'offline', admin_url( 'admin-ajax.php?action=wp_error_template' ) ), // Upon core merge, this would use admin_url( 'error.php' ).
'revision' => PWA_VERSION, // Upon core merge, this should be the core version.
'revision' => $revision, // Upon core merge, this should be the core version.
);
$server_error_precache_entry = array(
'url' => add_query_arg( 'code', '500', admin_url( 'admin-ajax.php?action=wp_error_template' ) ), // Upon core merge, this would use admin_url( 'error.php' ).
'revision' => PWA_VERSION, // Upon core merge, this should be the core version.
'revision' => $revision, // Upon core merge, this should be the core version.
);
}

Expand Down
85 changes: 60 additions & 25 deletions wp-includes/js/service-worker-navigation-routing.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

wp.serviceWorker.routing.registerRoute( new wp.serviceWorker.routing.NavigationRoute(
async function ( { event } ) {
const { url } = event.request;

let responsePreloaded = false;

const canStreamResponse = () => {
Expand All @@ -31,29 +29,63 @@
return response;
}
}
const channel = new BroadcastChannel( 'wordpress-server-errors' );

// Wait for client to request the error message.
channel.onmessage = ( event ) => {
if ( event.data && event.data.clientUrl && url === event.data.clientUrl ) {
response.text().then( ( text ) => {
channel.postMessage({
requestUrl: url,
bodyText: text,
status: response.status,
statusText: response.statusText
});
channel.close();
} );
}
};

// Close the channel if client did not request the message within 30 seconds.
setTimeout( () => {
channel.close();
}, 30 * 1000 );
if ( canStreamResponse() ) {
return caches.match( ERROR_500_BODY_FRAGMENT_URL );
}

const originalResponse = response.clone();
return response.text().then( function( responseBody ) {

// Prevent serving custom error template if WordPress is already responding with a valid error page (e.g. via wp_die()).
if ( -1 !== responseBody.indexOf( '</html>' ) ) {
return originalResponse;
}

return caches.match( canStreamResponse() ? ERROR_500_BODY_FRAGMENT_URL : ERROR_500_URL );
return caches.match( ERROR_500_URL ).then( function( errorResponse ) {

if ( ! errorResponse ) {
return response;
}

return errorResponse.text().then( function( text ) {
let init = {
status: errorResponse.status,
statusText: errorResponse.statusText,
headers: errorResponse.headers
};

let body = text.replace( /<!--WP_SERVICE_WORKER_ERROR_MESSAGE-->/, errorMessages.error );
body = body.replace(
/(<!--WP_SERVICE_WORKER_ERROR_TEMPLATE_BEGIN-->)((?:.|\n)+?)(<!--WP_SERVICE_WORKER_ERROR_TEMPLATE_END-->)/,
( details ) => {
if ( ! responseBody ) {
return ''; // Remove the details from the document entirely.
}
const src = 'data:text/html;base64,' + btoa( responseBody ); // The errorText encoded as a text/html data URL.
const srcdoc = responseBody
.replace( /&/g, '&amp;' )
.replace( /'/g, '&#39;' )
.replace( /"/g, '&quot;' )
.replace( /</g, '&lt;' )
.replace( />/g, '&gt;' );
const iframe = `<iframe style="width:100%" src="${src}" data-srcdoc="${srcdoc}"></iframe>`;
details = details.replace( '{{{error_details_iframe}}}', iframe );
// The following are in case the user wants to include the <iframe> in the template.
details = details.replace( '{{{iframe_src}}}', src );
details = details.replace( '{{{iframe_srcdoc}}}', srcdoc );

// Replace the comments.
details = details.replace( '<!--WP_SERVICE_WORKER_ERROR_TEMPLATE_BEGIN-->', '' );
details = details.replace( '<!--WP_SERVICE_WORKER_ERROR_TEMPLATE_END-->', '' );
return details;
}
);

return new Response( body, init );
} );
} );
} );
};

const sendOfflineResponse = () => {
Expand Down Expand Up @@ -120,7 +152,7 @@
const request = new Request( url.toString(), init );

const stream = wp.serviceWorker.streams.concatenateToResponse([
precacheStrategy.makeRequest({ request: streamHeaderFragmentURL }), // @todo This should be able to vary based on the request.url. No: just don't allow in paired mode.
precacheStrategy.makeRequest({ request: streamHeaderFragmentURL }),
fetch( request )
.then( handleResponse )
.catch( sendOfflineResponse ),
Expand All @@ -141,5 +173,8 @@

// Add fallback network-only navigation route to ensure preloadResponse is used if available.
wp.serviceWorker.routing.registerRoute( new wp.serviceWorker.routing.NavigationRoute(
wp.serviceWorker.strategies.networkOnly()
wp.serviceWorker.strategies.networkOnly(),
{
whitelist: BLACKLIST_PATTERNS.map( ( pattern ) => new RegExp( pattern ) )
}
) );
65 changes: 44 additions & 21 deletions wp-includes/js/service-worker-offline-commenting.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,52 @@
return response;
}

// @todo Replace depending on BroadcastChannel.
const channel = new BroadcastChannel( 'wordpress-server-errors' );
// @todo This is duplicated with code in service-worker-navigation-routing.js.
return response.text().then( function( errorText ) {
return caches.match( ERROR_500_URL ).then( function( errorResponse ) {

// Wait for client to request the error message.
channel.onmessage = ( event ) => {
if ( event.data && event.data.clientUrl && clone.url === event.data.clientUrl ) {
response.text().then( ( text ) => {
channel.postMessage({
requestUrl: clone.url,
bodyText: text,
status: response.status,
statusText: response.statusText
});
channel.close();
} );
}
};
if ( ! errorResponse ) {
return response;
}

return errorResponse.text().then( function( text ) {
let init = {
status: errorResponse.status,
statusText: errorResponse.statusText,
headers: errorResponse.headers
};

let body = text.replace( /<!--WP_SERVICE_WORKER_ERROR_MESSAGE-->/, errorMessages.error );
body = body.replace(
/(<!--WP_SERVICE_WORKER_ERROR_TEMPLATE_BEGIN-->)((?:.|\n)+?)(<!--WP_SERVICE_WORKER_ERROR_TEMPLATE_END-->)/,
( details ) => {
if ( ! errorText ) {
return ''; // Remove the details from the document entirely.
}
const src = 'data:text/html;base64,' + btoa( errorText ); // The errorText encoded as a text/html data URL.
const srcdoc = errorText
.replace( /&/g, '&amp;' )
.replace( /'/g, '&#39;' )
.replace( /"/g, '&quot;' )
.replace( /</g, '&lt;' )
.replace( />/g, '&gt;' );
const iframe = `<iframe style="width:100%" src="${src}" srcdoc="${srcdoc}"></iframe>`;
details = details.replace( '{{{error_details_iframe}}}', iframe );
// The following are in case the user wants to include the <iframe> in the template.
details = details.replace( '{{{iframe_src}}}', src );
details = details.replace( '{{{iframe_srcdoc}}}', srcdoc );

// Close the channel if client did not request the message within 30 seconds.
setTimeout( () => {
channel.close();
}, 30 * 1000 );
// Replace the comments.
details = details.replace( '<!--WP_SERVICE_WORKER_ERROR_TEMPLATE_BEGIN-->', '' );
details = details.replace( '<!--WP_SERVICE_WORKER_ERROR_TEMPLATE_END-->', '' );
return details;
}
);

return caches.match( ERROR_500_URL );
return new Response( body, init );
} );
} );
} );
} )
.catch( () => {
const bodyPromise = clone.blob();
Expand All @@ -57,6 +79,7 @@
}
);

// @todo This is duplicated with code in service-worker-navigation-routing.js.
return caches.match( ERROR_OFFLINE_URL ).then( function( response ) {

return response.text().then( function( text ) {
Expand Down
45 changes: 15 additions & 30 deletions wp-includes/service-workers.php
Original file line number Diff line number Diff line change
Expand Up @@ -190,36 +190,6 @@ function wp_print_service_workers() {
<?php
}

/**
* Print the script that is responsible for populating the details iframe with the error info from the service worker.
*
* Broadcast a request to obtain the original response text from the internal server error response and display it inside
* a details iframe if the 500 response included any body (such as an error message). This is used in a the 500.php template.
*
* @since 0.2
*
* @param string $callback Function in JS to invoke with the data. This may be either a global function name or method of another object, e.g. "mySite.handleServerError".
*/
function wp_print_service_worker_error_details_script( $callback ) {
?>
<script>
{
const clientUrl = location.href;
const channel = new BroadcastChannel( 'wordpress-server-errors' );
channel.onmessage = ( event ) => {
if ( event.data && event.data.requestUrl && clientUrl === event.data.requestUrl ) {
channel.onmessage = null;
channel.close();

<?php echo 'window[' . implode( '][', array_map( 'json_encode', explode( '.', $callback ) ) ) . ']( event.data );'; ?>
}
};
channel.postMessage( { clientUrl } )
}
</script>
<?php
}

/**
* Serve the service worker for the frontend if requested.
*
Expand Down Expand Up @@ -439,11 +409,26 @@ function wp_service_worker_get_error_messages() {
'wp_service_worker_error_messages',
array(
'default' => __( 'Please check your internet connection, and try again.', 'pwa' ),
'error' => __( 'Something prevented the page from being rendered. Please try again.', 'pwa' ),
'comment' => __( 'Your comment will be submitted once you are back online!', 'pwa' ),
)
);
}

/**
* Display service worker error details template.
*
* @param string $output Error details template output.
*/
function wp_service_worker_error_details_template( $output = '' ) {
if ( empty( $output ) ) {
$output = '<details id="error-details"><summary>' . esc_html__( 'More Details', 'pwa' ) . '</summary>{{{error_details_iframe}}}</details>';
}
echo '<!--WP_SERVICE_WORKER_ERROR_TEMPLATE_BEGIN-->'; // WPCS: XSS OK.
echo wp_kses_post( $output );
echo '<!--WP_SERVICE_WORKER_ERROR_TEMPLATE_END-->'; // WPCS: XSS OK.
}

/**
* Display service worker error message template tag.
*/
Expand Down
17 changes: 2 additions & 15 deletions wp-includes/theme-compat/500.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,8 @@
?>
<main>
<h1><?php esc_html_e( 'Oops! Something went wrong.', 'pwa' ); ?></h1>
<p><?php esc_html_e( 'Something prevented the page from being rendered. Please try again.', 'pwa' ); ?></p>
<details id="error-details" hidden>
<summary><?php esc_html_e( 'More details', 'pwa' ); ?></summary>
<iframe style="width:100%;" srcdoc=""></iframe>
<script>
function renderErrorDetails( data ) {
if ( data.bodyText.trim().length ) {
const details = document.getElementById( 'error-details' );
details.querySelector( 'iframe' ).srcdoc = data.bodyText;
details.hidden = false;
}
}
</script>
<?php wp_print_service_worker_error_details_script( 'renderErrorDetails' ); ?>
</details>
<?php wp_service_worker_error_message_placeholder(); ?>
<?php wp_service_worker_error_details_template(); ?>
</main>
<?php

Expand Down