coldwire-rails 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/VERSION +1 -1
- data/app/views/coldwire/service_worker/show.js.erb +1 -1
- data/docs/configuration.md +18 -12
- data/docs/setup.md +1 -1
- data/lib/coldwire/configuration.rb +29 -15
- data/lib/coldwire/worker/rules.js +4 -4
- data/lib/coldwire/worker/serve.js +1 -1
- data/lib/generators/coldwire/install/templates/coldwire.rb +3 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e48a15a424535143cc620dbbe65eec4a367c9e1feaa60addc104587ee835c836
|
|
4
|
+
data.tar.gz: b85fc7cfc8b80db94b14e994afbb258cc997d0b161986738e80c33a8a49704d5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 543c39e2d9999a45ce8041d4c87cb2853908056611bdc189d49e0fde87309373260131f433f92715d29943fcd44eb1d429aa6f270ef639287c0d45ce86787f11
|
|
7
|
+
data.tar.gz: 31bc9a01f008bd987c79700058b49682cae9d1020e873bf9aab03949fe9d15d9619c420c7b5c9efe7032a6b07eddaa090b54d43adecd9cf2f503983d4e702632
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [0.3.0]
|
|
6
|
+
|
|
7
|
+
- **`cache_origins` is `cacheable_hosts`**, and takes bare hosts: `"tiles.example.com"` rather
|
|
8
|
+
than `"https://tiles.example.com"`. The scheme was never carrying information — a worker
|
|
9
|
+
runs only on a secure page, and a secure page cannot fetch `http` — so it was a required
|
|
10
|
+
prefix with exactly one possible value. Requests are matched on a URL's `host`, so a port
|
|
11
|
+
belongs where it is not the default and `localhost:3001` matches that port and no other.
|
|
12
|
+
A scheme raises at boot, naming what to write instead.
|
|
13
|
+
|
|
14
|
+
## [0.2.0]
|
|
15
|
+
|
|
16
|
+
- **Garbage collection** is on by default. `config.garbage_collection` periodically sweeps the cache, deleting entries that haven't been accessed for `max_age` (default: 60 days), and if the cache grows over `max_size` (default: 250 MB), it continues pruning the least recently accessed until the cache fits. Sweeps run only when confirmed online by pinging `probe_path`, since deletions are irreversible. Cached archives and offline page assets are never collected, and the age of an entry is renewed anytime it or its referenced subresources are accessed or stored. The size ceiling can be adjusted in the offline settings page and is remembered per device.
|
|
17
|
+
- The garbage collector runs safely and automatically; you generally do not need to configure it. But you can tune `max_age`, `max_size`, and `interval` to fit your app's needs.
|
|
18
|
+
|
|
19
|
+
|
|
3
20
|
## [0.1.0]
|
|
4
21
|
|
|
5
22
|
First release. The API may still change before 1.0.
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.3.0
|
|
@@ -21,7 +21,7 @@ const CACHE_AS_YOU_GO = compileRules(<%= raw Coldwire.cache_rules(Coldwire.confi
|
|
|
21
21
|
const NEVER_CACHE = compileRules(<%= raw Coldwire.cache_rules(Coldwire.config.never_cache).to_json %>)
|
|
22
22
|
// Origins besides ours that may be cached at all, and the URLs whose byte ranges are cached
|
|
23
23
|
// piece by piece.
|
|
24
|
-
const
|
|
24
|
+
const CACHEABLE_HOSTS = <%= raw Coldwire.config.cacheable_hosts.to_json %>
|
|
25
25
|
const CACHE_RANGES = compileRules(<%= raw Coldwire.cache_rules(Coldwire.config.cache_ranges).to_json %>)
|
|
26
26
|
// A range entry is stored under its own key, with the range in the query and the archive's
|
|
27
27
|
// total size in a header — the two things needed to rebuild a 206 that was never storable.
|
data/docs/configuration.md
CHANGED
|
@@ -36,7 +36,7 @@ Coldwire.configure do |config|
|
|
|
36
36
|
config.never_cache = []
|
|
37
37
|
config.never_intercept = [ "/up" ]
|
|
38
38
|
|
|
39
|
-
config.
|
|
39
|
+
config.cacheable_hosts = []
|
|
40
40
|
config.cache_ranges = []
|
|
41
41
|
config.cache_archives = []
|
|
42
42
|
|
|
@@ -71,7 +71,7 @@ down with it.
|
|
|
71
71
|
| [`cache_as_you_go`](#cache_as_you_go) | `["/*"]` | Pages stored as somebody browses. `/*` is everything |
|
|
72
72
|
| [`never_cache`](#never_cache) | `[]` | Never stored, by any route in. The one veto |
|
|
73
73
|
| [`never_intercept`](#never_intercept) | `["/up"]` | Paths the worker does not touch at all |
|
|
74
|
-
| [`
|
|
74
|
+
| [`cacheable_hosts`](#cacheable_hosts) | `[]` | Other hosts the worker may cache |
|
|
75
75
|
| [`cache_ranges`](#cache_ranges) | `[]` | URLs whose `Range` requests are cached piece by piece |
|
|
76
76
|
| [`cache_archives`](#cache_archives) | `[]` | Large files somebody can choose to download |
|
|
77
77
|
| [`ignore_query_params`](#ignore_query_params) | `true` | Treat `/map` and `/map?zoom=9` as one page |
|
|
@@ -497,22 +497,28 @@ config.never_cache = [ %r{^/admin(/|$)}, %r{^/users/[^/]+/edit$} ]
|
|
|
497
497
|
|
|
498
498
|
---
|
|
499
499
|
|
|
500
|
-
## `
|
|
500
|
+
## `cacheable_hosts`
|
|
501
501
|
|
|
502
502
|
**Default:** `[]`
|
|
503
503
|
|
|
504
|
-
|
|
505
|
-
your app, or the response arrives opaque — status 0, no headers, no readable body — and
|
|
506
|
-
|
|
504
|
+
The hosts besides your own that the worker may cache. Each has to send CORS headers naming
|
|
505
|
+
your app, or the response arrives opaque — status 0, no headers, no readable body — and there
|
|
506
|
+
is nothing worth storing. Ranged sources must also expose `Content-Range`.
|
|
507
507
|
|
|
508
508
|
```ruby
|
|
509
|
-
config.
|
|
509
|
+
config.cacheable_hosts = [ "tiles.example.com", "localhost:3001" ]
|
|
510
510
|
```
|
|
511
511
|
|
|
512
|
-
|
|
513
|
-
|
|
512
|
+
Just the host. No scheme: a worker runs only on a secure page, and a secure page cannot fetch
|
|
513
|
+
`http`, so there was never a second scheme for one to tell apart. Writing one raises at boot
|
|
514
|
+
rather than being stripped — a scheme quietly accepted is a config that looks migrated and is
|
|
515
|
+
not, and the error names what to write instead.
|
|
514
516
|
|
|
515
|
-
|
|
517
|
+
A port only where it is not the default. A request is matched on its URL's `host`, against
|
|
518
|
+
exactly what you wrote: `localhost:3001` matches that port and no other, and a subdomain is a
|
|
519
|
+
different host.
|
|
520
|
+
|
|
521
|
+
Requests to anywhere else are passed straight through — not intercepted, not stored.
|
|
516
522
|
|
|
517
523
|
---
|
|
518
524
|
|
|
@@ -530,7 +536,7 @@ config.cache_ranges = [ "/tiles/*", %r{\.pmtiles$} ]
|
|
|
530
536
|
|
|
531
537
|
Patterns match the URL path, same as the other lists — not the full URL. A cross-origin
|
|
532
538
|
tile at `https://tiles.example.com/basemap.pmtiles` is allowed only when that origin is in
|
|
533
|
-
[`
|
|
539
|
+
[`cacheable_hosts`](#cacheable_hosts) *and* its path matches a rule here.
|
|
534
540
|
|
|
535
541
|
This pairs with [`cache_archives`](#cache_archives): `cache_ranges` caches the slices
|
|
536
542
|
actually read, so the places you have already opened work offline, and downloading the
|
|
@@ -561,7 +567,7 @@ Files arrive in 8 MB chunks, which is what makes a dropped connection cost secon
|
|
|
561
567
|
of the whole download. A `Range` request against a downloaded archive is answered by
|
|
562
568
|
slicing the chunks.
|
|
563
569
|
|
|
564
|
-
If the file lives on another
|
|
570
|
+
If the file lives on another host, list it in [`cacheable_hosts`](#cacheable_hosts).
|
|
565
571
|
|
|
566
572
|
---
|
|
567
573
|
|
data/docs/setup.md
CHANGED
|
@@ -176,7 +176,7 @@ app-bound mode and takes service workers with it.
|
|
|
176
176
|
## Optional next steps
|
|
177
177
|
|
|
178
178
|
- Restrict what browsing stores with [`cache_as_you_go`](configuration.md#cache_as_you_go)
|
|
179
|
-
- Nominate other
|
|
179
|
+
- Nominate other hosts or `Range` URLs with [`cacheable_hosts`](configuration.md#cacheable_hosts)
|
|
180
180
|
and [`cache_ranges`](configuration.md#cache_ranges)
|
|
181
181
|
- Offer large files for download with [`cache_archives`](configuration.md#cache_archives)
|
|
182
182
|
- Override the offline fallback by creating
|
|
@@ -88,13 +88,19 @@ module Coldwire
|
|
|
88
88
|
# cached pages hold whatever the previous session could see.
|
|
89
89
|
attr_writer :cache_identity
|
|
90
90
|
|
|
91
|
-
#
|
|
91
|
+
# The hosts besides your own that the worker may cache. Each has to send CORS headers naming
|
|
92
92
|
# your app, or the response arrives opaque — status 0, no headers, no readable body — and
|
|
93
93
|
# there is nothing worth storing. Ranged sources must also expose Content-Range.
|
|
94
|
-
|
|
94
|
+
#
|
|
95
|
+
# config.cacheable_hosts = [ "tiles.example.com" ]
|
|
96
|
+
#
|
|
97
|
+
# A hostname, with a port only where it is not the default — which is what a URL's `host`
|
|
98
|
+
# reads as. No scheme: a worker runs only on a secure page, and a secure page cannot fetch
|
|
99
|
+
# http, so there was never a second scheme for one to tell apart.
|
|
100
|
+
attr_reader :cacheable_hosts
|
|
95
101
|
|
|
96
|
-
def
|
|
97
|
-
@
|
|
102
|
+
def cacheable_hosts=(hosts)
|
|
103
|
+
@cacheable_hosts = Array(hosts).map { |host| validate_host(host) }
|
|
98
104
|
end
|
|
99
105
|
|
|
100
106
|
# URLs whose Range requests are cached piece by piece, keyed by the range — for a large
|
|
@@ -278,7 +284,7 @@ module Coldwire
|
|
|
278
284
|
@register_if = -> { true }
|
|
279
285
|
@caching_enabled_by_default = true
|
|
280
286
|
@cache_identity = -> { nil }
|
|
281
|
-
@
|
|
287
|
+
@cacheable_hosts = []
|
|
282
288
|
@cache_ranges = []
|
|
283
289
|
@cache_archives = []
|
|
284
290
|
end
|
|
@@ -330,19 +336,27 @@ module Coldwire
|
|
|
330
336
|
|
|
331
337
|
# An origin and nothing more: no path, no trailing slash. Anything else silently fails to
|
|
332
338
|
# match a request's origin, which is the same quiet failure as a malformed path pattern.
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
339
|
+
# A host, and a port only where it is not the default — which is exactly what a URL's
|
|
340
|
+
# `host` reads as, so the worker compares what you wrote against what it is handed.
|
|
341
|
+
HOST = /\A[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*(?::\d+)?\z/
|
|
342
|
+
SCHEME = %r{\A[a-z][a-z0-9+.\-]*://}
|
|
343
|
+
|
|
344
|
+
# Raised rather than quietly stripped. Everyone arriving here is renaming `cache_origins`,
|
|
345
|
+
# and a scheme silently accepted is a config that looks migrated and is not — the next
|
|
346
|
+
# person to read it learns the wrong shape.
|
|
347
|
+
def validate_host(host)
|
|
348
|
+
value = host.to_s.strip.downcase
|
|
349
|
+
|
|
350
|
+
if value.match?(SCHEME)
|
|
351
|
+
raise ArgumentError,
|
|
352
|
+
"Coldwire cacheable_hosts takes a host with no scheme — " \
|
|
353
|
+
"#{value.sub(SCHEME, '').chomp('/').inspect} rather than #{host.inspect}"
|
|
340
354
|
end
|
|
341
355
|
|
|
342
|
-
unless
|
|
356
|
+
unless value.match?(HOST)
|
|
343
357
|
raise ArgumentError,
|
|
344
|
-
"Coldwire
|
|
345
|
-
"\"
|
|
358
|
+
"Coldwire cacheable_hosts takes bare hosts like " \
|
|
359
|
+
"\"tiles.example.com\": #{host.inspect}"
|
|
346
360
|
end
|
|
347
361
|
|
|
348
362
|
value
|
|
@@ -3,7 +3,7 @@ function shouldHandle(request) {
|
|
|
3
3
|
if (request.method !== "GET") return false
|
|
4
4
|
|
|
5
5
|
const url = new URL(request.url)
|
|
6
|
-
if (!
|
|
6
|
+
if (!cacheableHost(url)) return false
|
|
7
7
|
|
|
8
8
|
// A Range request cannot be stored as it arrives — cache.put refuses a 206 — so it is
|
|
9
9
|
// stored as a 200 under a key naming the range, and answered with a 206 built here. Only
|
|
@@ -16,8 +16,8 @@ function shouldHandle(request) {
|
|
|
16
16
|
|
|
17
17
|
// Our own origin, plus any the host app has nominated. A worker sees every request a page
|
|
18
18
|
// makes, and caching other people's responses uninvited is not its business.
|
|
19
|
-
function
|
|
20
|
-
return url.origin === self.location.origin ||
|
|
19
|
+
function cacheableHost(url) {
|
|
20
|
+
return url.origin === self.location.origin || CACHEABLE_HOSTS.includes(url.host)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function matchesPath(url, paths) {
|
|
@@ -83,7 +83,7 @@ function isAutoCacheable(request) {
|
|
|
83
83
|
|
|
84
84
|
// A nominated origin is the opt-in; the path lists describe this app's own surfaces and say
|
|
85
85
|
// nothing useful about somebody else's.
|
|
86
|
-
if (url.origin !== self.location.origin) return
|
|
86
|
+
if (url.origin !== self.location.origin) return CACHEABLE_HOSTS.includes(url.host)
|
|
87
87
|
|
|
88
88
|
if (isNeverCached(url)) return false
|
|
89
89
|
|
|
@@ -43,7 +43,7 @@ function urlsFromHtml(html, pageUrl) {
|
|
|
43
43
|
// Any origin we are allowed to cache, not just our own. A page whose map library
|
|
44
44
|
// comes off a CDN is not offline-ready without it: precaching the page and skipping
|
|
45
45
|
// the script it cannot run without leaves a blank screen and a full cache.
|
|
46
|
-
if (!
|
|
46
|
+
if (!cacheableHost(url)) return
|
|
47
47
|
if (matchesPath(url, NEVER_INTERCEPT)) return
|
|
48
48
|
urls.add(url.href)
|
|
49
49
|
} catch {}
|
|
@@ -54,8 +54,9 @@ Coldwire.configure do |config|
|
|
|
54
54
|
# Never intercepted, so these fail outright offline. Coldwire's own routes are added for you.
|
|
55
55
|
config.never_intercept = [ "/up" ] # probe_path is added for you
|
|
56
56
|
|
|
57
|
-
#
|
|
58
|
-
|
|
57
|
+
# The hosts besides your own the worker may cache, and URLs whose Range requests it caches.
|
|
58
|
+
# No scheme — "tiles.example.com" — and a port only where it is not the default.
|
|
59
|
+
config.cacheable_hosts = []
|
|
59
60
|
config.cache_ranges = []
|
|
60
61
|
|
|
61
62
|
# Large files somebody can download for offline use. Nothing downloads on its own.
|