You are here

pwa.module in Progressive Web App 7

Same filename and directory in other branches
  1. 8 pwa.module
  2. 7.2 pwa.module
  3. 2.x pwa.module

File

pwa.module
View source
<?php

/**
 * @file
 */
define('PWA_MODULE_ACTIVE_ROUTE', 'pwa/module-active');
define('PWA_SW_CACHE_EXCLUDE', [
  'admin/.*',
  'user/reset/.*',
]);
define('PWA_SW_REGISTRATION_EVENT_DEFAULT', 'windowonload');

/**
 * Implements hook_permission().
 */
function pwa_permission() {
  return [
    'administer pwa' => [
      'title' => t('Administer Progressive Web App configuration'),
      'restrict access' => TRUE,
    ],
    'access pwa' => [
      'title' => t('Access Progressive Web App'),
    ],
  ];
}

/**
 * Implements hook_menu().
 */
function pwa_menu() {
  $items = [];
  $items['pwa/serviceworker/js'] = [
    'page callback' => 'pwa_serviceworker_file_data',
    'access arguments' => [
      'access pwa',
    ],
    'delivery callback' => 'pwa_deliver_js_file',
    'file' => 'pwa.pages.inc',
    'type' => MENU_CALLBACK,
  ];
  $items['offline'] = [
    'page callback' => 'pwa_offline_page',
    'access arguments' => [
      'access pwa',
    ],
    'file' => 'pwa.pages.inc',
    'type' => MENU_CALLBACK,
  ];
  $items[PWA_MODULE_ACTIVE_ROUTE] = [
    'page callback' => 'pwa_module_active',
    'access arguments' => [
      'access pwa',
    ],
    'file' => 'pwa.pages.inc',
    'type' => MENU_CALLBACK,
  ];

  // Admin settings for manifest.json
  $items['admin/config/system/pwa/manifest'] = [
    'title' => t('Add to Homescreen'),
    'description' => 'Control how your website appears on mobile devices when used as a PWA.',
    'page callback' => 'drupal_get_form',
    'page arguments' => [
      'pwa_admin_configuration_manifest',
    ],
    'access arguments' => [
      'administer pwa',
    ],
    'file' => 'pwa.admin.inc',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  ];

  // Admin settings to control how SW is loaded and cached.
  $items['admin/config/system/pwa/serviceworker'] = [
    'title' => 'Service Worker',
    'description' => 'Technical configuration for PWA Service Worker.',
    'page callback' => 'drupal_get_form',
    'page arguments' => [
      'pwa_admin_configuration_sw',
    ],
    'access arguments' => [
      'administer pwa',
    ],
    'file' => 'pwa.admin.inc',
    'type' => MENU_LOCAL_TASK,
  ];

  // Specify manifest settings as default landing page when using system nav.
  $items['admin/config/system/pwa'] = [
    'title' => t('Progressive Web App'),
    'description' => 'Control how your website appears on mobile devices when used as a PWA.',
    'page callback' => 'drupal_get_form',
    'page arguments' => [
      'pwa_admin_configuration_manifest',
    ],
    'access arguments' => [
      'administer pwa',
    ],
    'file' => 'pwa.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  ];
  return $items;
}

/**
 * Implements hook_library().
 */
function pwa_library() {
  $path = drupal_get_path('module', 'pwa');
  return [
    'serviceworker' => [
      'version' => '0.2',
      'js' => [
        $path . '/js/serviceworker-load.js' => [
          'scope' => 'footer',
        ],
        0 => [
          'data' => [
            'pwa' => [
              'path' => url('/pwa/serviceworker/js'),
              'registrationEvent' => variable_get('pwa_sw_registration_event', PWA_SW_REGISTRATION_EVENT_DEFAULT),
            ],
          ],
          'type' => 'setting',
        ],
      ],
      'dependencies' => [],
    ],
  ];
}

/**
 * Generate the for the manifest file.
 *
 * @return array
 */
function _pwa_manifest_data() {
  $path = drupal_get_path('module', 'pwa');
  global $language;
  $manifest = [
    'name' => variable_get('pwa_name', variable_get('site_name')),
    'short_name' => variable_get('pwa_short_name', variable_get('site_name')),
    'description' => variable_get('pwa_description', ''),
    'lang' => $language->language,
    'dir' => $language->direction == LANGUAGE_LTR ? 'ltr' : 'rtl',
    'background_color' => variable_get('pwa_background_color', '#ffffff'),
    'theme_color' => variable_get('pwa_theme_color', '#ffffff'),
    'start_url' => variable_get('pwa_start_url', '/'),
    'orientation' => variable_get('pwa_orientation', 'portrait'),
    'display' => variable_get('pwa_display', 'standalone'),
    // Custom icons have to be defined in hook_pwa_manifest_alter().
    //
    // @see https://www.drupal.org/project/pwa/issues/2983031
    // @see pwa.api.php
    'icons' => [
      [
        'src' => url($path . '/assets/druplicon-512.png'),
        'sizes' => '512x512',
        'type' => 'image/png',
      ],
      [
        'src' => url($path . '/assets/druplicon-192.png'),
        'sizes' => '192x192',
        'type' => 'image/png',
      ],
      [
        'src' => url($path . '/assets/druplicon-144.png'),
        'sizes' => '144x144',
        'type' => 'image/png',
      ],
      [
        'src' => url($path . '/assets/druplicon-vector.svg'),
        'type' => 'image/svg+xml',
      ],
    ],
  ];
  drupal_alter('pwa_manifest', $manifest);
  return $manifest;
}

/**
 * Generate JSON of the manifest data. This is the final file used by browsers.
 *
 * @return string
 */
function _pwa_manifest_file() {
  $manifest = _pwa_manifest_data();
  return drupal_json_encode($manifest);
}

/**
 * Take the serviceworker template file and replace all the variables needed.
 *
 * @return string
 */
function _pwa_serviceworker_file() {
  $path = drupal_get_path('module', 'pwa');
  $sw = file_get_contents($path . '/js/serviceworker.js');
  $cacheUrls = (array) preg_split("/\r\n|\n|\r/", trim(variable_get('pwa_sw_cache_urls', '')));

  // Get icons list and convert into array of sources.
  $manifest = _pwa_manifest_data();
  $cacheIcons = [];
  foreach ($manifest['icons'] as $icon) {
    $cacheIcons[] = $icon['src'];
  }

  // Combine URLs from admin UI with manifest icons.
  $cacheWhitelist = array_merge($cacheUrls, $cacheIcons);

  // Turn all the paths into fully-qualified URLs.
  foreach ($cacheWhitelist as &$url) {
    $url = url($url);
  }
  foreach ($cacheUrls as &$url) {
    $url = url($url);
  }

  // Look up module release from package info.
  $pwa_module_info = system_get_info('module', 'pwa');
  $pwa_module_version = $pwa_module_info['version'];

  // Packaging script will always provide the published module version. Checking
  // for NULL is only so maintainers have something predictable to test against.
  if ($pwa_module_version == null) {
    $pwa_module_version = '7.x-1.x-dev';
  }

  // Set up placeholders.
  $replace = [
    '[/*cacheConditionsExclude*/]' => drupal_json_encode((array) preg_split("/\r\n|\n|\r/", trim(variable_get('pwa_sw_cache_exclude', '[\'' . PWA_MODULE_ACTIVE_ROUTE . '\']')))),
    '[/*cacheUrls*/]' => drupal_json_encode($cacheWhitelist),
    '[/*cacheUrlsAssets*/]' => drupal_json_encode((array) _pwa_fetch_offline_page_resources($cacheUrls)),
    '1/*cacheVersion*/' => '\'' . $pwa_module_version . '-v' . variable_get('pwa_sw_cache_version', 1) . '\'',
    '/offline' => url('/offline'),
    'offline-image.png' => file_create_url(drupal_get_path('module', 'pwa') . '/assets/offline-image.png'),
  ];

  // Fill placeholders and return final file.
  return str_replace(array_keys($replace), array_values($replace), $sw);
}

/**
 * Discover CSS/JS assets present in offline URLs so they can be cached during
 * the Service Worker install event.
 *
 * @param $pages
 *
 * @return array
 */
function _pwa_fetch_offline_page_resources($pages) {
  $resources = [];

  // For each Drupal path, request the HTML response and parse any CSS/JS found
  // within the HTML. Since this is the pure HTML response, any DOM modifications
  // that trigger new requests cannot be accounted for. An example would be an
  // asynchronously-loaded webfont.
  foreach ($pages as $page) {
    $response = drupal_http_request(url($page, [
      'absolute' => TRUE,
    ]));
    $dom = new DOMDocument();
    @$dom
      ->loadHTML($response->data);
    $xpath = new DOMXPath($dom);
    foreach ($xpath
      ->query('//script[@src]') as $script) {
      $resources[] = $script
        ->getAttribute('src');
    }
    foreach ($xpath
      ->query('//link[@rel="stylesheet"][@href]') as $stylesheet) {
      $resources[] = $stylesheet
        ->getAttribute('href');
    }
    foreach ($xpath
      ->query('//img[@src]') as $image) {
      $resources[] = $image
        ->getAttribute('src');
    }
  }
  $dedupe = array_unique($resources);
  return $dedupe;
}

/**
 * Implements hook_page_alter().
 */
function pwa_preprocess_html(&$variables) {
  if (!user_access('access pwa')) {
    return;
  }

  // Add manifest.json to HTML
  drupal_add_html_head([
    '#tag' => 'link',
    '#attributes' => [
      'rel' => 'manifest',
      'href' => variable_get('pwa_filecache_manifest', ''),
    ],
  ], 'manifest');

  // Add backup <meta> tag for branding colors. It should always match the
  // variable for the manifest.
  drupal_add_html_head([
    '#tag' => 'meta',
    '#attributes' => [
      'name' => 'theme-color',
      'content' => variable_get('pwa_theme_color', '#ffffff'),
    ],
  ], 'theme_color');

  // Load the Service Worker
  drupal_add_library('pwa', 'serviceworker');
  drupal_add_js([
    'pwa' => [
      'path' => url('pwa/serviceworker/js'),
    ],
  ], 'setting');
}

/**
 * Implements hook_flush_caches().
 */
function pwa_flush_caches() {
  $scheme = file_default_scheme();
  $directory = $scheme . '://pwa';
  file_prepare_directory($directory, FILE_CREATE_DIRECTORY);

  // Create the static manifest file with all the data.
  $manifest = _pwa_manifest_file();
  $manifest_uri = file_unmanaged_save_data($manifest, $directory . '/manifest.json', FILE_EXISTS_REPLACE);
  $manifest_url = file_create_url($manifest_uri);
  variable_set('pwa_filecache_manifest', str_replace($GLOBALS['base_url'] . '/', base_path(), $manifest_url));

  // Create the serviceworker file in cache so it can be served from a menu
  // callback so additional headers can be sent with the file.
  // @see pwa_deliver_js_file().
  cache_set('pwa:serviceworker', _pwa_serviceworker_file(), 'cache');
}

/**
 * Implements hook_modernizr_info().
 */
function pwa_modernizr_info() {
  return [
    'serviceworker',
  ];
}

Functions

Namesort descending Description
pwa_flush_caches Implements hook_flush_caches().
pwa_library Implements hook_library().
pwa_menu Implements hook_menu().
pwa_modernizr_info Implements hook_modernizr_info().
pwa_permission Implements hook_permission().
pwa_preprocess_html Implements hook_page_alter().
_pwa_fetch_offline_page_resources Discover CSS/JS assets present in offline URLs so they can be cached during the Service Worker install event.
_pwa_manifest_data Generate the for the manifest file.
_pwa_manifest_file Generate JSON of the manifest data. This is the final file used by browsers.
_pwa_serviceworker_file Take the serviceworker template file and replace all the variables needed.

Constants