line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mojolicious::Plugin::ServiceWorker; |
2
|
1
|
|
|
1
|
|
830
|
use Mojo::Base 'Mojolicious::Plugin'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
8
|
|
3
|
1
|
|
|
1
|
|
228
|
use Mojo::JSON; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
591
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
our $VERSION = '0.02'; |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
my $SW_URL = 'serviceworker.js'; |
8
|
|
|
|
|
|
|
my @COPY_KEYS = qw(debug precache_urls network_only cache_only network_first); |
9
|
|
|
|
|
|
|
my %DEFAULT_LISTENERS = ( |
10
|
|
|
|
|
|
|
install => [ <<'EOF' ], |
11
|
|
|
|
|
|
|
event => { |
12
|
|
|
|
|
|
|
console.log("Installing SW..."); |
13
|
|
|
|
|
|
|
event.waitUntil(caches.open(cachename).then(cache => { |
14
|
|
|
|
|
|
|
console.log("Caching: ", config.precache_urls); |
15
|
|
|
|
|
|
|
return cache.addAll(config.precache_urls); |
16
|
|
|
|
|
|
|
}).then(() => console.log("The SW is now installed"))); |
17
|
|
|
|
|
|
|
} |
18
|
|
|
|
|
|
|
EOF |
19
|
|
|
|
|
|
|
fetch => [ <<'EOF' ], |
20
|
|
|
|
|
|
|
event => { |
21
|
|
|
|
|
|
|
var url = event.request.url; |
22
|
|
|
|
|
|
|
if (maybeMatch(config, 'network_only', url)) { |
23
|
|
|
|
|
|
|
if (config.debug) console.log('network_only', url); |
24
|
|
|
|
|
|
|
return event.respondWith(fetch(event.request).catch(() => {})); |
25
|
|
|
|
|
|
|
} |
26
|
|
|
|
|
|
|
return caches.open(cachename).then( |
27
|
|
|
|
|
|
|
cache => cache.match(event.request) |
28
|
|
|
|
|
|
|
).then(cacheResponse => { |
29
|
|
|
|
|
|
|
if (cacheResponse && maybeMatch(config, 'cache_only', url)) { |
30
|
|
|
|
|
|
|
if (config.debug) console.log('cache_only', url); |
31
|
|
|
|
|
|
|
return cacheResponse; |
32
|
|
|
|
|
|
|
} |
33
|
|
|
|
|
|
|
if (maybeMatch(config, 'network_first', url)) { |
34
|
|
|
|
|
|
|
if (config.debug) console.log('network_first', url); |
35
|
|
|
|
|
|
|
return cachingFetchOrCached(event.request, cacheResponse); |
36
|
|
|
|
|
|
|
} |
37
|
|
|
|
|
|
|
if (config.debug) console.log('cache_first', url); |
38
|
|
|
|
|
|
|
var cF = cachingFetch(event.request).catch(() => {}); |
39
|
|
|
|
|
|
|
return cacheResponse || cF; |
40
|
|
|
|
|
|
|
}); |
41
|
|
|
|
|
|
|
} |
42
|
|
|
|
|
|
|
EOF |
43
|
|
|
|
|
|
|
); |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
sub register { |
46
|
2
|
|
|
2
|
1
|
2278
|
my ($self, $app, $conf) = @_; |
47
|
2
|
50
|
|
|
|
6
|
my %config = %{ $conf || {} }; |
|
2
|
|
|
|
|
13
|
|
48
|
2
|
|
66
|
|
|
13
|
my $sw_route = $conf->{route_sw} || $SW_URL; |
49
|
2
|
|
|
|
|
10
|
my $r = $app->routes; |
50
|
|
|
|
|
|
|
$r->get($sw_route => sub { |
51
|
2
|
|
|
2
|
|
98633
|
my ($c) = @_; |
52
|
2
|
|
|
|
|
19
|
$c->render( |
53
|
|
|
|
|
|
|
template => 'serviceworker', |
54
|
|
|
|
|
|
|
format => 'js', |
55
|
|
|
|
|
|
|
listeners => $c->serviceworker->event_listeners, |
56
|
|
|
|
|
|
|
); |
57
|
2
|
|
|
|
|
36
|
}, 'serviceworker.route'); |
58
|
2
|
|
|
2
|
|
946
|
$app->helper('serviceworker.route' => sub { $sw_route }); |
|
2
|
|
|
|
|
1890
|
|
59
|
|
|
|
|
|
|
$config{precache_urls} = [ |
60
|
2
|
100
|
|
|
|
919
|
@{ $config{precache_urls} || [] }, |
|
2
|
|
|
|
|
16
|
|
61
|
|
|
|
|
|
|
$sw_route, |
62
|
|
|
|
|
|
|
]; |
63
|
2
|
100
|
|
|
|
6
|
my %config_copy = map {$config{$_} ? ($_ => $config{$_}) : ()} @COPY_KEYS; |
|
10
|
|
|
|
|
32
|
|
64
|
2
|
|
|
4
|
|
13
|
$app->helper('serviceworker.config' => sub { \%config_copy }); |
|
4
|
|
|
|
|
17511
|
|
65
|
2
|
|
|
|
|
671
|
push @{ $app->renderer->classes }, __PACKAGE__; |
|
2
|
|
|
|
|
7
|
|
66
|
2
|
|
|
|
|
27
|
my %event_listeners = %DEFAULT_LISTENERS; |
67
|
2
|
|
|
2
|
|
15
|
$app->helper('serviceworker.event_listeners' => sub { \%event_listeners }); |
|
2
|
|
|
|
|
63
|
|
68
|
|
|
|
|
|
|
$app->helper('serviceworker.add_event_listener' => sub { |
69
|
1
|
|
|
1
|
|
1330
|
my ($c, $event, $expr) = @_; |
70
|
1
|
50
|
|
|
|
4
|
$event_listeners{$event} = [ @{ $event_listeners{$event} || [] }, $expr ]; |
|
1
|
|
|
|
|
10
|
|
71
|
2
|
|
|
|
|
735
|
}); |
72
|
2
|
|
|
|
|
693
|
$self; |
73
|
|
|
|
|
|
|
} |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
1; |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=encoding utf8 |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=head1 NAME |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
Mojolicious::Plugin::ServiceWorker - plugin to add a Service Worker |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=head1 SYNOPSIS |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
# Mojolicious::Lite |
86
|
|
|
|
|
|
|
plugin 'ServiceWorker' => { |
87
|
|
|
|
|
|
|
route_sw => '/sw2.js', |
88
|
|
|
|
|
|
|
precache_urls => [ |
89
|
|
|
|
|
|
|
], |
90
|
|
|
|
|
|
|
}; |
91
|
|
|
|
|
|
|
app->serviceworker->add_event_listener(push => <<'EOF'); |
92
|
|
|
|
|
|
|
function(event) { |
93
|
|
|
|
|
|
|
if (event.data) { |
94
|
|
|
|
|
|
|
console.log('This push event has data: ', event.data.text()); |
95
|
|
|
|
|
|
|
} else { |
96
|
|
|
|
|
|
|
console.log('This push event has no data.'); |
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
EOF |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
=head1 DESCRIPTION |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
L is a L plugin. |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=head1 METHODS |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
L inherits all methods from |
108
|
|
|
|
|
|
|
L and implements the following new ones. |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=head2 register |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
my $p = $plugin->register(Mojolicious->new, \%conf); |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
Register plugin in L application, returning the plugin |
115
|
|
|
|
|
|
|
object. Takes a hash-ref as configuration, see L for keys. |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=head1 OPTIONS |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
=head2 route_sw |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
The service worker route. Defaults to C. Note that |
122
|
|
|
|
|
|
|
you need this to be in your app's top level, since the service worker |
123
|
|
|
|
|
|
|
can only affect URLs at or below its "scope". |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=head2 debug |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
If a true value, C will be used to indicate various events |
128
|
|
|
|
|
|
|
including SW caching choices. |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
=head2 precache_urls |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
An array-ref of URLs that are relative to the SW's scope to load into |
133
|
|
|
|
|
|
|
the SW's cache on installation. The SW URL will always be added to this. |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=head2 network_only |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
An array-ref of URLs. Any fetched URL in this list will never be cached, |
138
|
|
|
|
|
|
|
and always fetched over the network. |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
=head2 cache_only |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
As above, except the matching URL will never be re-checked. Use only |
143
|
|
|
|
|
|
|
where you cache-bust by including a hash in the filename. |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
=head2 network_first |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
As above, except the matching URL will be fetched from the network |
148
|
|
|
|
|
|
|
every time and used if possible. The cached value will only be used if |
149
|
|
|
|
|
|
|
that fails. |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
B will be treated with a |
152
|
|
|
|
|
|
|
"cache first" strategy, also known as "stale while revalidate": the cached |
153
|
|
|
|
|
|
|
version will immediately by returned to the web client for performance, |
154
|
|
|
|
|
|
|
but also fetched over the network and re-cached for freshness. |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
=head1 HELPERS |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
=head2 serviceworker.route |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
my $route_name = $c->serviceworker->route; |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
The configured L route. |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
=head2 serviceworker.config |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
my $config = $c->serviceworker->config; |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
The SW configuration (a hash-ref). Keys: C, C, |
169
|
|
|
|
|
|
|
C, C, C. |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
=head2 serviceworker.add_event_listener |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
my $config = $c->serviceworker->add_event_listener(push => <<'EOF'); |
174
|
|
|
|
|
|
|
function(event) { |
175
|
|
|
|
|
|
|
if (event.data) { |
176
|
|
|
|
|
|
|
console.log('This push event has data: ', event.data.text()); |
177
|
|
|
|
|
|
|
} else { |
178
|
|
|
|
|
|
|
console.log('This push event has no data.'); |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
EOF |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
Add to the service worker an event listener. Arguments are the event |
184
|
|
|
|
|
|
|
name, and a JavaScript function expression that takes the correct args |
185
|
|
|
|
|
|
|
for that event. |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
=head2 serviceworker.event_listeners |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
my $listeners = $c->serviceworker->event_listeners; |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
Returns a hash-ref mapping event name to array-ref of function |
192
|
|
|
|
|
|
|
expressions as above. C and C are provided by default. |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=head1 TEMPLATES |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
Various templates are available for including in the app's templates: |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=head2 serviceworker-install.html.ep |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
A snippet of JavaScript that will install the supplied service |
201
|
|
|
|
|
|
|
worker. Include it within a C |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
=head1 SEE ALSO |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
L, L, L. |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=cut |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
__DATA__ |