-
Notifications
You must be signed in to change notification settings - Fork 99
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14 from xwp/add/7-sw_api_prototype
Prototype for Service Workers API
- Loading branch information
Showing
8 changed files
with
402 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
node_modules | ||
build/ | ||
composer.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
<?php | ||
/** | ||
* Tests for class WP_Service_Workers. | ||
* | ||
* @package PWA | ||
*/ | ||
|
||
/** | ||
* Tests for class WP_Web_App_Manifest. | ||
*/ | ||
class Test_WP_Service_Workers extends WP_UnitTestCase { | ||
|
||
/** | ||
* Tested instance. | ||
* | ||
* @var WP_Service_Workers | ||
*/ | ||
public $instance; | ||
|
||
/** | ||
* Setup. | ||
* | ||
* @inheritdoc | ||
*/ | ||
public function setUp() { | ||
parent::setUp(); | ||
$this->instance = new WP_Service_Workers(); | ||
} | ||
|
||
/** | ||
* Test class constructor. | ||
* | ||
* @covers WP_Service_Workers::__construct() | ||
*/ | ||
public function test_construct() { | ||
$service_workers = new WP_Service_Workers(); | ||
$this->assertEquals( 'WP_Service_Workers', get_class( $service_workers ) ); | ||
} | ||
|
||
/** | ||
* Test adding new service worker. | ||
* | ||
* @covers WP_Service_Workers::add() | ||
*/ | ||
public function test_register() { | ||
$this->instance->register( 'foo', '/test-sw.js', array( 'bar' ) ); | ||
|
||
$default_scope = site_url( '/', 'relative' ); | ||
|
||
$this->assertTrue( in_array( $default_scope, $this->instance->get_scopes(), true ) ); | ||
$this->assertTrue( isset( $this->instance->registered['foo'] ) ); | ||
|
||
$registered_sw = $this->instance->registered['foo']; | ||
|
||
$this->assertEquals( '/test-sw.js', $registered_sw->src ); | ||
$this->assertTrue( in_array( $default_scope, $registered_sw->args['scopes'], true ) ); | ||
$this->assertEquals( array( 'bar' ), $registered_sw->deps ); | ||
} | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
<?php | ||
/** | ||
* Dependencies API: WP_Service_Workers class | ||
* | ||
* @since ? | ||
* | ||
* @package PWA | ||
*/ | ||
|
||
/** | ||
* Class used to register service workers. | ||
* | ||
* @since ? | ||
* | ||
* @see WP_Dependencies | ||
*/ | ||
class WP_Service_Workers extends WP_Scripts { | ||
|
||
/** | ||
* Param for service workers. | ||
* | ||
* @var string | ||
*/ | ||
public $query_var = 'wp_service_worker'; | ||
|
||
/** | ||
* Output for service worker scope script. | ||
* | ||
* @var string | ||
*/ | ||
public $output = ''; | ||
|
||
/** | ||
* Initialize the class. | ||
*/ | ||
public function init() { | ||
/** | ||
* Fires when the WP_Service_Workers instance is initialized. | ||
* | ||
* @param WP_Service_Workers $this WP_Service_Workers instance (passed by reference). | ||
*/ | ||
do_action_ref_array( 'wp_default_service_workers', array( &$this ) ); | ||
} | ||
|
||
/** | ||
* Register service worker. | ||
* | ||
* Registers service worker if no item of that name already exists. | ||
* | ||
* @param string $handle Name of the item. Should be unique. | ||
* @param string|callable $src URL to the source in the WordPress install, or a callback that returns the JS to include in the service worker. | ||
* @param array $deps Optional. An array of registered item handles this item depends on. Default empty array. | ||
* @param array $scopes Optional. Scopes of the service worker. Default relative path. | ||
* @return bool Whether the item has been registered. True on success, false on failure. | ||
*/ | ||
public function register( $handle, $src, $deps = array(), $scopes = array() ) { | ||
|
||
// Set default scope if missing. | ||
if ( empty( $scopes ) ) { | ||
$scopes = array( site_url( '/', 'relative' ) ); | ||
} | ||
return parent::add( $handle, $src, $deps, false, compact( 'scopes' ) ); | ||
} | ||
|
||
/** | ||
* Get service worker logic for scope. | ||
* | ||
* @param string $scope Scope of the Service Worker. | ||
*/ | ||
public function serve_request( $scope ) { | ||
|
||
header( 'Content-Type: text/javascript; charset=utf-8' ); | ||
nocache_headers(); | ||
|
||
if ( ! in_array( $scope, $this->get_scopes(), true ) ) { | ||
status_header( 404 ); | ||
return; | ||
} | ||
|
||
$scope_items = array(); | ||
|
||
// Get handles from the relevant scope only. | ||
foreach ( $this->registered as $handle => $item ) { | ||
if ( in_array( $scope, $item->args['scopes'], true ) ) { | ||
$scope_items[] = $handle; | ||
} | ||
} | ||
|
||
$this->output = ''; | ||
$this->do_items( $scope_items ); | ||
|
||
$file_hash = md5( $this->output ); | ||
header( "Etag: $file_hash" ); | ||
|
||
$etag_header = isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ? trim( $_SERVER['HTTP_IF_NONE_MATCH'] ) : false; | ||
if ( $file_hash === $etag_header ) { | ||
status_header( 304 ); | ||
return; | ||
} | ||
|
||
echo $this->output; // phpcs:ignore WordPress.XSS.EscapeOutput, WordPress.Security.EscapeOutput | ||
} | ||
|
||
/** | ||
* Get all scopes. | ||
* | ||
* @return array Array of scopes. | ||
*/ | ||
public function get_scopes() { | ||
|
||
$scopes = array(); | ||
foreach ( $this->registered as $handle => $item ) { | ||
$scopes = array_merge( $scopes, $item->args['scopes'] ); | ||
} | ||
return array_unique( $scopes ); | ||
} | ||
|
||
/** | ||
* Process one registered script. | ||
* | ||
* @param string $handle Handle. | ||
* @param bool $group Group. | ||
* @return void | ||
*/ | ||
public function do_item( $handle, $group = false ) { | ||
|
||
$obj = $this->registered[ $handle ]; | ||
|
||
if ( is_callable( $obj->src ) ) { | ||
$this->output .= call_user_func( $obj->src ) . "\n"; | ||
} else { | ||
$validated_path = $this->get_validated_file_path( $obj->src ); | ||
if ( is_wp_error( $validated_path ) ) { | ||
_doing_it_wrong( | ||
__FUNCTION__, | ||
/* translators: %s is file URL */ | ||
sprintf( esc_html__( 'Service worker src is incorrect: %s', 'pwa' ), esc_html( $obj->src ) ), | ||
'0.1' | ||
); | ||
|
||
/* translators: %s is file URL */ | ||
$this->output .= "console.warn( '" . sprintf( esc_html__( 'Service worker src is incorrect: %s', 'pwa' ), esc_html( $obj->src ) ) . "' );\n"; | ||
} else { | ||
/* translators: %s is file URL */ | ||
$this->output .= sprintf( esc_html( "\n/* Source: %s */\n" ), esc_url( $obj->src ) ); | ||
|
||
$this->output .= @file_get_contents( $this->get_validated_file_path( $obj->src ) ) . "\n"; // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents, WordPress.WP.AlternativeFunctions.file_system_read_file_get_contents | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Remove URL scheme. | ||
* | ||
* @param string $schemed_url URL. | ||
* @return string URL. | ||
*/ | ||
public function remove_url_scheme( $schemed_url ) { | ||
return preg_replace( '#^\w+:(?=//)#', '', $schemed_url ); | ||
} | ||
|
||
/** | ||
* Get validated path to file. | ||
* | ||
* @param string $url Relative path. | ||
* @return null|string|WP_Error | ||
*/ | ||
public function get_validated_file_path( $url ) { | ||
if ( ! is_string( $url ) ) { | ||
return new WP_Error( 'incorrect_path_format', esc_html__( 'URL has to be a string', 'pwa' ) ); | ||
} | ||
|
||
$needs_base_url = ! preg_match( '|^(https?:)?//|', $url ); | ||
$base_url = site_url(); | ||
|
||
if ( $needs_base_url ) { | ||
$url = $base_url . $url; | ||
} | ||
|
||
// Strip URL scheme, query, and fragment. | ||
$url = $this->remove_url_scheme( preg_replace( ':[\?#].*$:', '', $url ) ); | ||
|
||
$content_url = $this->remove_url_scheme( content_url( '/' ) ); | ||
$allowed_host = wp_parse_url( $content_url, PHP_URL_HOST ); | ||
|
||
$url_host = wp_parse_url( $url, PHP_URL_HOST ); | ||
|
||
if ( $allowed_host !== $url_host ) { | ||
/* translators: %s is file URL */ | ||
return new WP_Error( 'external_file_url', sprintf( __( 'URL is located on an external domain: %s.', 'pwa' ), $url_host ) ); | ||
} | ||
|
||
$file_path = null; | ||
if ( 0 === strpos( $url, $content_url ) ) { | ||
$file_path = WP_CONTENT_DIR . substr( $url, strlen( $content_url ) - 1 ); | ||
} | ||
|
||
if ( ! $file_path || false !== strpos( '../', $file_path ) || 0 !== validate_file( $file_path ) || ! file_exists( $file_path ) ) { | ||
/* translators: %s is file URL */ | ||
return new WP_Error( 'file_path_not_found', sprintf( __( 'Unable to locate filesystem path for %s.', 'pwa' ), $url ) ); | ||
} | ||
|
||
return $file_path; | ||
} | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?php | ||
/** | ||
* Sets up the default filters and actions for PWA hooks. | ||
* | ||
* @package PWA | ||
*/ | ||
|
||
foreach ( array( 'wp_print_scripts', 'admin_print_scripts', 'customize_controls_print_scripts' ) as $filter ) { | ||
add_filter( $filter, 'wp_print_service_workers', 9 ); | ||
} | ||
|
||
add_action( 'parse_request', 'wp_service_worker_loaded' ); | ||
|
||
add_filter( 'query_vars', 'wp_add_service_worker_query_var' ); |
Oops, something went wrong.