Progressive-Web-Application/lib/Progressive/Web/Application.pm
package Progressive::Web::Application;
use 5.006;
use strict;
use warnings;
use Carp qw//;
use JSON;
use Colouring::In;
use Image::Scale;
use Cwd qw/abs_path/;
our $VERSION = '0.09';
our (%TOOL, %MANIFEST_SCHEMA);
BEGIN {
%TOOL = (
tainted => qr/^([\d\/\-\@\w.]+)$/,
array_check => sub {
my $ref = ref $_[0];
Carp::croak(sprintf q/Value is not an ARRAY for field %s/, $_[1]) if !$ref || ref $_[0] ne 'ARRAY';
return $_[0];
},
scalar_check => sub {
Carp::croak(sprintf q/Value is not a scalar for field %s/, $_[1]) if ref $_[0];
return $_[0];
},
colour_check => sub { Colouring::In->new($_[0])->toCSS; },
JSON => JSON->new->utf8->pretty,
to_json => sub { $TOOL{JSON}->encode($_[0]); },
from_json => sub { return $TOOL{JSON}->decode($_[0]); },
make_path => sub {
my $path = abs_path();
for (split '/', $_[0]) {
$path .= "/$_";
$path =~ $TOOL{tainted};
if (! -d $1) {
mkdir $1 or Carp::croak(qq/
Cannot open file for writing $!
/);
}
}
return $path;
},
remove_abs => sub {
my $abs = abs_path();
$_[0] =~ s/$abs//;
return $_[0];
},
abs => sub {
return $_[0] if ($_[0] =~ m/^\//);
return sprintf "%s/%s", abs_path(), $_[0];
},
write_file => sub {
$TOOL{abs}->($_[0]) =~ $TOOL{tainted};
open my $fh, '>', $1 or Carp::croak(qq/
Cannot open file for writing $!
/);
print $fh $_[1];
close $fh;
},
read_file => sub {
$TOOL{abs}->($_[0]) =~ $TOOL{tainted};
open my $fh, '<', $1 or Carp::croak(qq/
Cannot open file for reading $!
/);
my $content = do { local $/; <$fh> };
close $fh;
chomp($content);
return $content;
},
remove_file => sub { $TOOL{remove_directory}->($TOOL{abs}->($_[0])); },
read_directory => sub {
my ($val, %opts) = $TOOL{parse_params}->(@_);
$val = $TOOL{abs}->($val);
$val =~ $TOOL{tainted};
opendir(my $DIR, $1) or die "Can't open $1$!";
my @files;
for my $file (grep {$_ !~ /^\./} readdir($DIR)) {
my $path = sprintf "%s/%s", $val, $file;
$path =~ $opts{blacklist_regex} && next if $opts{blacklist_regex};
$path =~ $opts{whitelist_regex} || next if $opts{whitelist_regex};
push @files, $opts{recurse} && -d $path
? map {
sprintf "%s/%s", $file, $_;
} $TOOL{read_directory}->($path, %opts)
: $path;
}
closedir ($DIR) or die "Cant close $1$!";
return @files;
},
remove_directory => sub {
$TOOL{abs}->($_[0]) =~ $TOOL{tainted};
if (-d $1) {
my $d = $1;
opendir(my $DIR, $1) or Carp::croak "Can't open $1$!";
for my $file (grep { $_ !~ /^\./} readdir($DIR)) {
my $path = sprintf "%s/%s", $_[0], $file;
$TOOL{remove_directory}->($path);
}
closedir($DIR) or Carp::craok( "Can't close $1$!");
rmdir($d);
} else {
unlink $1 or Carp::croak(qq/
Cannot remove file $1$!
/);
}
return 1;
},
valid_icon_sizes => { map {
$_ => 1
} "36x36", "48x48", "57x57", "60x60", "70x70", "72x72", "76x76",
"96x96", "114x114", "120x120", "128x128", "150x150", "144x144",
"152x152", "180x180", "192x192", "310x310" },
valid_icon_types => { map {
$_ => 1
} "image/png" },
generate_icons => sub {
my ($icon, %options) = $TOOL{parse_params}->(@_);
Carp::croak('No initial icon passed to generate_icons') if !$icon;
my $img = Image::Scale->new($icon);
$options{outpath} = substr($icon, 0, rindex($icon, '/')) if !$options{outpath};
$options{outpath} =~ s/\/$//;
$TOOL{make_path}->($options{outpath});
my @iconSizes = sort { $b->{width} <=> $a->{width} }
map {
my @s = split 'x', $_;
{ width => $s[0] + 0, height => $s[0] + 0 }
} keys %{ $TOOL{valid_icon_sizes} };
my @files;
for my $size (@iconSizes) {
$img->resize_gd_fixed_point({
width => $size->{width},
height => $size->{width},
keep_aspect => 1
});
my $file = sprintf(
'%s/%sx%s-%s.png',
$options{outpath},
$size->{width},
$size->{height},
$options{icon_name} || q|icon|
);
$TOOL{write_file}->(
$file,
$img->as_png()
);
push @files, {
sizes => sprintf(q|%sx%s|, $size->{width}, $size->{height}),
src => q|/| . $file,
type => q|image/png|
};
}
return @files;
},
identify_icon_size => sub {
my $img = Image::Scale->new($TOOL{abs}->($_[0]));
my $size = sprintf "%sx%s", $img->width(), $img->height();
return $TOOL{valid_icon_sizes}->{$size} ? $size : undef;;
},
identify_icon_information => sub {
my $root = shift;
my @files;
for my $file (@_) {
my $type = sprintf( "image/%s", substr($file, rindex($file, q|.|) + 1));
my $size;
next unless ($TOOL{valid_icon_types}->{$type});
next unless $size = $TOOL{identify_icon_size}->($file);
$root ? $file =~ s/(.*$root)// : $TOOL{remove_abs}->($file);
$file = q|/| . $file if $file !~ m/^\//;
push @files, {
src => $file,
type => $type,
sizes => $size
};
}
return @files;
},
validate_icon_information => sub {
for (qw/src sizes type/) {
if (! defined $_[0]->{$_} ) {
Carp::croak(
sprintf
q/Required field not passed for icon missing %s for %s/,
$_,
$TOOL{to_json}->($_[0])
);
}
$TOOL{scalar_check}->($_[0]->{$_}, $_);
}
$_[0]->{src} = q|/| . $_[0]->{src} unless $_[0]->{src} =~ m/^\//;
$TOOL{valid_icon_sizes}->{$_[0]->{sizes}}
|| Carp::croak(
sprintf
q/Invalid size %s for icon %s/,
$_[0]->{sizes},
$TOOL{to_json}->($_[0])
);
$TOOL{valid_icon_types}->{$_[0]->{type}}
|| Carp::croak(
sprintf
q/Invalid icon type %s for icon %s/,
$_[0]->{types},
$TOOL{to_json}->($_[0])
);
return $_[0];
},
parse_params => sub {
return ( shift, (ref($_[0]) || "") eq q|HASH| ? %{$_[0]} : @_ );
},
identify_files_to_cache => sub {
my ($directory, %options) = $TOOL{parse_params}->(@_);
if (!$directory) {
Carp::croak(
q/no directory passed into identify_files_to_cache/
);
}
my @files_to_cache;
for my $dir (ref $directory ? @{$directory} : $directory) {
push @files_to_cache, sort { $a cmp $b } map {
$options{root} ? $_ =~ s/(.*$options{root})// : $TOOL{remove_abs}->($_);
$_ = q|/| . $_ if $_ !~ m/^\//;
$_ = sprintf("/%s%s", $options{pathpart}, $_) if $options{pathpart};
$_;
} $TOOL{read_directory}->($dir, %options);
}
return @files_to_cache;
},
valid_orientation => {
any => 1,
natural => 1,
landscape => 1,
q|landscape-primary| => 1,
q|landscape-secondary| => 1,
portrait => 1,
q|portrait-primary| => 1,
q|portrait-secondary| => 1
}
);
%MANIFEST_SCHEMA = (
name => $TOOL{scalar_check},
short_name => $TOOL{scalar_check},
description => $TOOL{scalar_check},
lang => $TOOL{scalar_check}, #TODO,
dir => sub {
$TOOL{scalar_check}->(@_);
$_[0] =~ m/^(auto|ltr|rtl)$/;
return $1 ? $_[0] : Carp::croak(sprintf q|Invalid display value passed %s|, $_[0]);
},
orientation => sub {
$TOOL{scalar_check}->(@_);
return $TOOL{valid_orientation}->{$_[0]} ? $_[0] : Carp::croak(sprintf q|Invalid orientation value passed %s|, $_[0]);
},
prefer_related_applications => sub { return (ref $_[0] ? ${$_[0]} : $_[0]) ? \1 : \0; },
related_applications => sub {
$TOOL{array_check}->(@_);
for my $app (@{$_[0]}) {
Carp::croak(q|related_applicaiton is not a HASH|) unless ref $app eq q|HASH|;
for (qw/platform url/, ($app->{platform} || "") =~ m/play/i ? 'id' : ()) {
Carp::croak(sprintf
q|Missing required param %s in related_application %s|,
$_,
$TOOL{to_json}->($app)
) unless $app->{$_};
}
}
return $_[0];
},
iarc_rating_id => $TOOL{scalar_check}, # TODO parenting
scope => $TOOL{scalar_check}, # TODO should validate a path
screenshots => sub {
$TOOL{array_check}->(@_);
for my $screenshot (@{$_[0]}) {
Carp::croak(q|screenshot is not a HASH|) unless ref $screenshot eq q|HASH|;
map {
Carp::croak(sprintf
q|Missing required param %s in screenshot %s|,
$_,
$TOOL{to_json}->($screenshot)
) unless $screenshot->{$_};
} qw/src sizes type/;
}
# TODO - once known to be fully supported...
# Mechanize->screenshot
# validate src sizes type can likely reuse icons
return $_[0];
},
categories => $TOOL{array_check},
start_url => $TOOL{scalar_check}, # TODO should validate a path
icons => sub {
my ($ref, @icons) = ref $_[0];
push @icons, !$ref
? $TOOL{identify_icon_information}->(
$_[2], sort { $b cmp $a } $TOOL{read_directory}->($_[0])
)
: $ref eq q|ARRAY|
? map {
$MANIFEST_SCHEMA{icons}->($_);
} @{$_[0]}
: $ref eq q|HASH|
? ($_[0]->{file}
? $TOOL{generate_icons}->($_[0]->{file},
root => $_[2], %{$_[0]}
)
: $TOOL{validate_icon_information}->($_[0])
)
: map { $TOOL{validate_icon_information}->($_) } $_[0]->(\%TOOL);
if ($_[3]) {
@icons = map {
$_->{src} = q|/| . $_[3] . $_->{src} if $_->{src} !~ m/^\/$_[3]/;
$_;
} @icons;
}
return wantarray ? @icons : \@icons;
},
display => sub {
$TOOL{scalar_check}->($_[0], q|display|);
Carp::croak(sprintf
q/Invalid display value passed %s must be one of standalone, minimal-ui, fullscreen or browser/, $_[0]
) unless $_[0] =~ m/^(standalone|minimal\-ui|fullscreen|browser)$/;
return $_[0];
},
background_color => $TOOL{colour_check},
theme_color => $TOOL{colour_check},
# TODO: once available https://developer.mozilla.org/en-US/docs/Web/Manifest/serviceworker
);
}
sub new {
my ($package, %args) = $TOOL{parse_params}->(@_);
my $new = bless {}, $package;
for (qw/root pathpart/) { $new->{$_} = $args{$_} if exists $args{$_}; }
$new->set_manifest($args{manifest}) if $args{manifest};
$new->set_params($args{params}) if $args{params};
$new->set_template(%args);
return $new;
}
sub set_template {
my ($self, %args) = $TOOL{parse_params}->(@_);
$self->{template_class} = sprintf(
q|Progressive::Web::Application::Template::%s|,
$args{template} || q|General|
);
eval "require $self->{template_class}" || Carp::croak $@;
$self->{template} = $self->{template_class}->new();
}
sub set_pathpart { $_[0]->{pathpart} = $_[-1]; }
sub set_root { $_[0]->{root} = $_[-1]; }
sub has_manifest { $_[0]->{manifest} ? 1 : 0 }
sub manifest { $_[1] ? $TOOL{to_json}->($_[0]->{manifest}) : $_[0]->{manifest}; }
sub set_manifest {
my ($self, %args) = $TOOL{parse_params}->(@_);
for my $key (keys %args) {
my $validate = $MANIFEST_SCHEMA{$key}
or Carp::croak(sprintf q/Invalid key passed to setup manifest %s/, $key);
my $val = $validate->($args{$key}, $key, $self->{root}, $self->{pathpart});
$self->{manifest}{$key} = $val;
}
$self->{manifest};
}
sub clear_manifest { delete $_[0]->{manifest}; }
sub has_params { $_[0]->{params} ? 1 : 0 }
sub params { $_[1] ? $TOOL{to_json}->($_[0]->{params}) : $_[0]->{params}; }
sub set_params {
my ($self, %args) = $TOOL{parse_params}->(@_);
my $ftc = delete $args{files_to_cache};
$self->{params} = {%{$self->{params} || {}}, %args};
if ($ftc) {
my %map = (
ARRAY => sub { @{$ftc} },
CODE => sub { @{$ftc->(\%TOOL)} },
HASH => sub {
$TOOL{identify_files_to_cache}->(
$ftc->{directory},
root => $self->{root},
pathpart => $self->{pathpart},
%{$ftc}
);
}
);
my $ref = ref $ftc;
Carp::croak(q|currently set_params files_to_cache cannot handle | . $ref) unless $map{$ref};
my @filesToCache = $map{$ref}->();
unshift @filesToCache, $self->{params}{offline_path} if $self->{params}{offline_path};
$self->{params}{files_to_cache} = \@filesToCache;
}
$self->{params}{cache_name} ||= q|Set-a-cache-name-v1|;
$self->{params};
}
sub clear_params { delete $_[0]->{params}; }
sub template { $_[0]->{template} }
sub templates { $_[0]->{template}->render($_[0]->{params}); }
sub compile {
my ($self, $root) = @_;
$root ||= $self->{root};
Carp::croak(
q/No root directory provided to compile manifest and service worker/
) unless $root;
$TOOL{make_path}->($root);
my %build = (
($self->has_manifest ? (manifest => $self->manifest(1)) : ()),
($self->has_params ? (templates => $self->templates()) : ()),
);
$TOOL{write_file}->(sprintf("%s/manifest.json", $root), $build{manifest}) if $build{manifest};
if ($build{templates}) {
for my $template (keys %{$build{templates}}) {
$TOOL{write_file}->(
sprintf("%s/%s", $root, $template),
$build{templates}{$template}
);
}
}
}
sub tools { return \%TOOL; }
sub manifest_schema { return \%MANIFEST_SCHEMA; }
__END__
=head1 NAME
Progressive::Web::Application - Utility for making an application 'progressive'
=head1 VERSION
Version 0.09
=cut
=head1 SYNOPSIS
# vim MyApp/pwa.pl
use Progressive::Web::Application;
my $pwa = Progressive::Web::Application->new({
root => 'root',
pathpart => 'payments',
manifest => {
name => 'Progressive Web Application Demo',
short_name => 'PWA Demo',
icons => '/root/static/images/icons',
start_url => '/',
display => 'standalone',
background_color => '#2b3e50',
theme_color => '#2c3e50'
},
params => {
offline_path => '/offline.html',
cache_name => 'my-cache-name',
files_to_cache => [
'/manifest.json',
'/favicon.ico',
'/static/css/bootstrap.min.css',
'/static/css/lnation.css',
...
],
}
});
$pwa->compile();
...
=head1 DESCRIPTION
This module is a Utility for aiding you in creating Progressive Web Applications (PWA's). Progressive web apps use modern browser APIs along with traditional progressive enhancement strategy to create cross-platform applications. These apps work everywhere that a browser runs and provide several features that give them the same user experience advantages as native apps.
PWAs should be discoverable, installable, linkable, network independent, progressive, re-engageable, responsive, and safe.
=head2 Discoverable
The eventual aim is that web apps should have better representation in search engines, be easier to expose, catalog and rank, and have metadata usable by browsers to give them special capabilities.
=head2 Installable
A core part of the apps experience is for users to have app icons on their home screen, that launch in a native container that feels integrated with the underlying platform.
=head2 Linkable
One of the most powerful features of the Web is to be able to link to an app at a specific URL - no app store needed, no complex installation process. This is how it has always been.
=head2 Network Independent
Can work when the network is unreliable, or even non-existent.
=head2 Progressive
Can be developed to provide a modern experience to fully capable browsers, and an acceptable (although not quite as shiny) experience in less capable browsers.
=head2 Re-engageable
One major advantage of native platforms is the ease with which users can be re-engaged by updates and new content, even when they aren't looking at the app or using their devices. Modern Web APIs allow us to do this too, using new technologies such as Service Workers for controlling pages, the Web Push API for sending updates straight from server to app via a service worker, and the Notifications API for generating system notifications to help engage users when they're not in the browser.
=head2 Responsive
Responsive web apps use technologies like media queries and viewport to make sure that their UIs will fit any form factor: desktop, mobile, tablet, or whatever comes next.
=head2 Safe
You can verify the true nature of a PWA by confirming that it is at the correct URL, whereas apps in apps stores can often look like one thing, but be another. Example - L<https://twitter.com/andreasbovens/status/926965095296651264>
=cut
=head1 Methods
=cut
=head2 new
Instantiate a new Progressive::Web::Application Object.
Progressive::Web::Application->new();
=head3 options
=head4 root
The root(/) directory of your application, this is used to validate links and where any output from this module will be compiled/written.
lnation:High lnation$ ls
MyApp
.......
Progressive::Web::Application->new(
root => 'MyApp/root'
);
=cut
=head4 pathpart
Is your application proxied to a path.
https://localhost/admin/*
.......
Progressive::Web::Application->new(
pathpart => 'admin'
);
=cut
=head4 manifest
A Hash reference of params to build the web app manifest. See manifest_schema for more information.
Progressive::Web::Application->new(
manifest => {
name => 'Progressive Web Application Demo',
short_name => 'PWA Demo',
icons => '/root/static/images/icons',
start_url => '/',
display => 'standalone',
background_color => '#2b3e50',
theme_color => '#2c3e50'
},
);
=cut
=head4 template
A string that represents a template class. See Progressive::Web::Application::Template namespace for options.
Progressive::Web::Application->new(
template => 'General' # the default
);
=cut
=head4 params
A Hash reference of params that are passed into the template. These are dynamic based upon the selected template
so check the documentation for more information on what is required. However the following have some addtional logic to aide you.
=over
=item cache_name
All templates will have a cache name, it is used to keep track of the version of the cache. Each release that has resource changes
will require an update to the cache_name. This ensures your end users service workers clears the existing cache and re-fetch's the
resources from your server.
Progressive::Web::Application->new(
params => {
cache_name => 'my_cache_name_v2'
}
);
=item files_to_cache
You can pass in as an Array reference, Hash reference or Code block.
ARRAY - expects a list of files to be cached
Progressive::Web::Application->new(
params => {
files_to_cache => [
'resources/css/app.css',
'resources/js/app.js',
...
]
}
);
HASH - params to be passed to $pwa->tools->{identify_files_to_cache}, see documentation for more information.
Progressive::Web::Application->new(
params => {
files_to_cache => {
directory => 'root/static',
recurse => 1
}
}
);
CODE - a custom code block that returns an Array references of files to cache.
Progressive::Web::Application->new(
params => {
files_to_cache => sub {
my $tool = @_;
my @files = (
$tool->{read_directory}->('root/static/js'),
$tool->{read_directory}->('root/static/css'),
);
...
return \@files;
}
}
);
=back
=cut
=head2 set_template
Set the template class.
$pwa->set_template(template => 'General');
=cut
=head2 set_pathpart
Set the pathpart of your application.
$pwa->set_pathpart('somepath');
=cut
=head2 set_root
Set the root directory of your application.
$pwa->set_root('MyApp/root');
=cut
=head2 has_manifest
Does the Progressive::Web::Applcation object have a manifest configured, returns true or false value (1|0).
$pwa->has_manifest();
=cut
=head2 manifest
Return the configured manifest, if a true param is passed then this is returned as json.
$pwa->manifest(1);
=cut
=head2 set_manifest
Set the manifest params see manifest_schema for full options.
$pwa->set_manifest(
name => 'Progressive Web Application Demo',
short_name => 'PWA Demo',
icons => '/root/static/images/icons',
start_url => '/',
display => 'standalone',
background_color => '#2b3e50',
theme_color => '#2c3e50'
);
=cut
=head2 clear_manifest
Clear/Delete manifest from the object, this is usefull for when you call 'compile' to different outpaths multiple times within a script.
$pwa->clear_manifest();
=cut
=head2 has_params
Does the Progressive::Web::Application have params configured, returns true or false value (1|0).
$pwa->has_params();
=cut
=head2 params
Return the configured manifest, if a true param is passed then this is returned as json.
$pwa->params(1);
=cut
=head2 set_params
Set the template params see the configured template for what is required.
$pwa->set_params(
offline_path => '/offline.html',
cache_name => 'my-cache-name',
files_to_cache => [
'/manifest.json',
'/favicon.ico',
'/static/css/bootstrap.min.css',
'/static/css/lnation.css',
...
],
);
=cut
=head2 clear_params
Clear/Delete params from the object, this is usefull for when you call 'compile' to different outpaths multiple times within a script.
$pwa->clear_params();
=cut
=head2 template
Returns the template object.
$pwa->template();
=cut
=head2 templates
Returns the rendered templates as a hash reference.
$pwa->templates();
=cut
=head2 compile
This will write the manifest.json and any templates configured in the Template class into the root directory. You can optionally pass in a different path for the resources to be written.
$pwa->compile();
...
$pwa->compile('MyApp/root/resources');
=cut
=head2 manifest_schema
=head3 name
Scalar - The applications 'full' name.
$pwa->set_manifest(name => 'My Application Name');
The name member is a string that represents the name of the web application as it is usually displayed to the user (e.g., amongst a list of other applications, or as a label for an icon). name is directionality-capable, which means it can be displayed left-to-right or right-to-left based on the values of the dir and lang manifest members.
L<https://developer.mozilla.org/en-US/docs/Web/Manifest/name>
=head3 short_name
Scalar - The applications 'abbreviation' name
$pwa->set_manifest(short_name => 'MyApp');
The short_name member is a string that represents the name of the web application displayed to the user if there is not enough space to display name (e.g., as a label for an icon on the phone home screen). short_name is directionality-capable, which means it can be displayed left-to-right or right-to-left based on the value of the dir and lang manifest members.
L<https://developer.mozilla.org/en-US/docs/Web/Manifest/short_name>
=head3 description
Scalar - Description of the application.
$pwa->set_manifest(description => 'A description about my application');
The description member is a string in which developers can explain what the application does. description is directionality-capable, which means it can be displayed left to right or right to left based on the values of the dir and lang manifest members.
L<https://developer.mozilla.org/en-US/docs/Web/Manifest/description>
=head3 lang
Scalar - Language of the application
$pwa->set_manifest(lang => 'en-GB');
The lang member is a string containing a single language tag (L<https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang>). It specifies the primary language for the values of the manifest's directionality-capable members, and together with dir determines their directionality.
L<https://developer.mozilla.org/en-US/docs/Web/Manifest/lang>
=head3 dir
Scalar - Direction of Lang (auto|ltr|rtl)
$pwa->set_manifest(dir => 'ltr');
The base direction in which to display direction-capable members of the manifest. Together with the lang member, it helps to correctly display right-to-left languages.
L<https://developer.mozilla.org/en-US/docs/Web/Manifest/dir>
=head3 orientation
Scalar - orientation of the screen (any|natural|landscape|landscape-primary|landscape-secondary|portrait|portrait-primary|portrait-secondary)
$pwa->set_manifest(orientation => 'natural');
The orientation member defines the default orientation for all the website's top-level browsing contexts. The orientation can be changed at runtime via the Screen Orientation API (L<https://developer.mozilla.org/en-US/docs/Web/API/Screen/orientation>)
L<https://developer.mozilla.org/en-US/docs/Web/Manifest/orientation>
=head3 prefer_related_applications
Boolean - Prefer a related web application to recommend to 'install'.
$pwa->set_manifest(prefer_related_applications => \1)
The prefer_related_applications member is a boolean value that specifies that applications listed in related_applications should be preferred over the web application. If the prefer_related_applications member is set to true, the user agent might suggest installing one of the related applications instead of this web app.
L<https://developer.mozilla.org/en-US/docs/Web/Manifest/prefer_related_applications>
=head3 related_applications
ArrayRef - A list of related application to recommend
$pwa->set_manifest(related_applications => [
{
platform => "play",
url => "https://play.google.com/store/apps/details?id=com.example.app1",
id => "com.example.app1"
}
]);
The related_applications field is an array of objects specifying native applications that are installable by, or accessible to, the underlying platform - for example, a native Android application obtainable through the Google Play Store. Such applications are intended to be alternatives to the manifest's website that provides similar/equivalent functionality - like the native app equivalent.
L<https://developer.mozilla.org/en-US/docs/Web/Manifest/related_applications>
=head3 iarc_rating_id
Scalar - International Age Rating Coalition (IARC) certification code
$pwa->set_manifest(iarc_rating_id => 'e84b072d-71b3-4d3e-86ae-31a8ce4e53b7');
The iarc_rating_id member is a string that represents the International Age Rating Coalition (https://www.globalratings.com) certification code of the web application. It is intended to be used to determine which ages the web application is appropriate for.
L<https://developer.mozilla.org/en-US/docs/Web/Manifest/iarc_rating_id>
=head3 scope
Scalar - Applications scope
$pwa->set_manifest(scope => '/app/');
The scope member is a string that defines the navigation scope of this web application's application context. It restricts what web pages can be viewed while the manifest is applied. If the user navigates outside the scope, it reverts to a normal web page inside a browser tab or window.
L<https://developer.mozilla.org/en-US/docs/Web/Manifest/scope>
=head3 screenshots
ArrayRef - Application screenshot previews
$pwa->set_manifest(screenshots => [
{
src => "screenshot1.webp",
sizes => "1280x720",
type => "image/webp"
}
]);
The screenshots member defines an array of screenshots intended to showcase the application. These images are intended to be used by progressive web app stores.
L<https://developer.mozilla.org/en-US/docs/Web/Manifest/screenshots>
=head3 categories
ArrayRef - Application categories
$pwa->set_manifest(categories => [
"finance"
]);
The categories member is an array of strings defining the names of categories that the application supposedly belongs to. There is no standard list of possible values, but the W3C maintains a list of known categories. (https://github.com/w3c/manifest/wiki/Categories)
L<https://developer.mozilla.org/en-US/docs/Web/Manifest/categories>
=head3 start_url
Scalar - Start path the application will launch
$pwa->set_manifest(start_url => '/home');
The start_url member is a string that represents the start URL of the web application - the prefered URL that should be loaded when the user launches the web application (e.g., when the user taps on the web application's icon from a device's application menu or homescreen).
L<https://developer.mozilla.org/en-US/docs/Web/Manifest/start_url>
=head3 icons
Scalar - a directory path passed into $TOOL{identify_icon_information}
$pwa->set_manifest(icons => 'root/static/images/icons');
HashRef - generate_icons or validate hash as an icon.
$pwa->set_manifest(icons => {
file => 'root/static/images/320x320-icon.png',
outpath => 't/resources/icons',
});
...
$pwa->set_manidest(icons => {
sizes => '310x310',
src => '/t/resources/320x320-icon.png',
type => 'image/png'
});
Code - Custom coderef that returns an array of icons
$pwa->set_manidest(icons => sub {
my $tool = shift;
});
ArrayRef - itterate Scalar/Hash/Code
The icons member specifies an array of objects representing image files that can serve as application icons for different contexts. For example, they can be used to represent the web application amongst a list of other applications, or to integrate the web application with an OS's task switcher and/or system preferences.
L<https://developer.mozilla.org/en-US/docs/Web/Manifest/icons>
=head3 display
Scalar - Browser display mode (standalone|minimal-ui|fullscreen|browser)
The display member is a string that determines the developers preferred display mode for the website. The display mode changes how much of browser UI is shown to the user and can range from "browser" (when the full browser window is shown) to "fullscreen" (when the app is full-screened).
L<https://developer.mozilla.org/en-US/docs/Web/Manifest/display>
=head3 background_color
Scalar - Background colour
The background_color member defines a placeholeder background color for the application page to display before its stylesheet is loaded. This value is used by the user agent to draw the background color of a shortcut when the manifest is available before the stylesheet has loaded.
L<https://developer.mozilla.org/en-US/docs/Web/Manifest/background_color>
=head3 theme_color
Scalar - Theme colour
The theme_color member is a string that defines the default theme color for the application. This sometimes affects how the OS displays the site (e.g., on Android's task switcher, the theme color surrounds the site).
L<https://developer.mozilla.org/en-US/docs/Web/Manifest/theme_color>
=cut
=head2 tools
=head3 tainted
Returns a regex used to make paths/files taint safe.
my $filename =~ $tools->{tainted};
$1 # taint safe *\o/*
=head3 array_check
Validate the param is an Array, returns the param if valid and it will die/croak if invalid.
my $arrayref = [qw/a b c/];
$tool->{array_check}->($arrayref);
=head3 scalar_check
Validate the param is a Scalar (not a scalar reference), return the param if vaid and it will die/croak if invalid.
my $string = 'abc';
$tool->{scalar_check}->($string);
=head3 colour_check
Validate the param is a valid colour, returns the param if valid and it will die/croak if invalid.
my $colour = '#000';
$tool->{colour_check}->($colour);
=head3 JSON
Returns a JSON object with 'pretty' mode turned on.
$tool->{JSON}->encode($ref);
=head3 to_json
Encodes the passed param as JSON.
my $ref = { a => 'b' };
$tool->{to_json}->($ref);
=head3 from_json
Decodes the passed json into a perl struct.
my $json = q|{"a":"b"}|;
$tool->{from_json}->($ref);
=head3 make_path
Creates the path in the file system.
$tool->{make_path}->('root/static/new/path');
=head3 remove_abs
Removes the absolute path from a file path.
my $path = '/var/www/MyApp/root/static/images/icon/one.png';
my $relative = $tool->{remove_abs}->($path);
# 'root/static/images/icon/one.png'
=head3 abs
Converts a relative path into an absolute path.
my $rel = 'root/static/images/icon/one.png';
my $abs = $tool->{abs}->($rel);
# '/var/www/MyApp/root/static/images/icon/one.png'
=head3 write_file
Creates/Writes a file.
$tool->{write_file}->('root/static/images/icons/two.png', $png_data);
=head3 read_file
Reads a file into memory.
$tool->{read_file}->('root/static/images/icons/two.png');
=head3 remove_file
Removes/Deletes a file.
$tool->{remove_file}->('root/static/images/icons/two.png');
=head3 read_directory
Reads a directory into an array of filenames. First argument should be the directory path, you can optionally pass the following options.
=over
=item recurse
Boolean to determine whether directories within the passed directory should be recursed.
=item blacklist_regex
A blacklist regex which will skip file paths that match it.
=item whitelist_regex
A whitelist regex which will skip file paths that do not match it.
=back
$tool->{read_directory}->('root/static',
recurse => 1,
blacklist_regex => qr/documentation/
);
=head3 remove_directory
Removes/Deletes a directory.
$tool->{remove_directory}->('root/static/images/icons');
=head3 valid_icon_sizes
Returns an Hash reference of known valid icon sizes, this is to cater for the various devices your application may be installed on.
$tool->{valid_icon_sizes}->{'36x36'}; # valid
$tool->{valid_icon_sizes}->{'1000x1000'}; # invalid
=head3 vaid_icon_types
Returns an Hash reference of currently supported icon types.
$tool->{valid_icon_types}->{'image/png'};
=head3 generate_icons
Accepts an path to an icon and generates an icon for each of the valid_icon_sizes. You can optionally pass the following options.
=over
=item outpath
The directory the icons will be generated/written too (default is the directory of the original icon).
=item icon_name
The suffix that will applied to the icon name (default is 'icon')
=back
$tool->{generate_icons}->('root/static/images/one.png',
outpath => 'root/static/images/icons'
);
=head3 identify_icon_size
Identify an icons size and validate tagains valid_icon_sizes.
$tool->{identify_icon_size}->('root/static/images/icon/my-icon.png');
=head3 identify_icon_information
Identify icon information from a passed file.
$tool->{identify_icon_size}->('root', 'root/static/images/icon/my-icon.png');
# {
# src => '/static/images/icon/my-icon.png',
# type => 'image/png',
# sizes => '36x36'
# }
=head3 validate_icon_information
Validate a passed hashref is a valid 'icon' definition.
my $icon = {
src => '/static/images/icon/my-icon.png',
type => 'image/png',
sizes => '36x36'
};
$tool->{validate_icon_information}->($icon);
=head3 parse_params
Parse params, from a hash reference into a hash 'list'.
my ($self, %hash) = $tool->{parse_params}->($self, { a => "b" });
my ($self, %hash) = $tool->{parse_params}->($self, a => "b");
=head3 identify_files_to_cache
Returns a list of files/resources to cache. Accepts a directory or an Arrayref of directorys and optionally
params that are passed to $tool{reaD_directory} see documentation for more information.
my @files_to_cache = $tool->{identify_files_to_cache}->([
'root/static/js',
'root/static/css',
'root/static/images'
]);
=head3 valid_orientation
Returns an Hash reference of known orientations.
$tool->{valid_icon_sizes}->{'landscape'}; # valid
$tool->{valid_icon_sizes}->{'introverted'}; # invalid
=cut
=head1 AUTHOR
LNATION, C<< <email at lnation.org> >>
=head1 BUGS
Please report any bugs or feature requests to C<bug-progressive-web-application at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Progressive-Web-Application>. I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.
=head1 SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Progressive::Web::Application
You can also look for information at:
=over 4
=item * RT: CPAN's request tracker (report bugs here)
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Progressive-Web-Application>
=item * Search CPAN
L<http://search.cpan.org/dist/Progressive-Web-Application/>
=back
=head1 ACKNOWLEDGEMENTS
=head1 LICENSE AND COPYRIGHT
Copyright 2019->2025 LNATION.
This program is free software; you can redistribute it and/or modify it
under the terms of the the Artistic License (2.0). You may obtain a
copy of the full license at:
L<http://www.perlfoundation.org/artistic_license_2_0>
Any use, modification, and distribution of the Standard or Modified
Versions is governed by this Artistic License. By using, modifying or
distributing the Package, you accept this license. Do not use, modify,
or distribute the Package, if you do not accept this license.
If your Modified Version has been derived from a Modified Version made
by someone other than you, you are nevertheless required to ensure that
your Modified Version complies with the requirements of this license.
This license does not grant you the right to use any trademark, service
mark, tradename, or logo of the Copyright Holder.
This license includes the non-exclusive, worldwide, free-of-charge
patent license to make, have made, use, offer to sell, sell, import and
otherwise transfer the Package with respect to any patent claims
licensable by the Copyright Holder that are necessarily infringed by the
Package. If you institute patent litigation (including a cross-claim or
counterclaim) against any party alleging that the Package constitutes
direct or contributory patent infringement, then this Artistic License
to you shall terminate on the date that such litigation is filed.
Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=cut
1; # End of Progressive::Web::Application