@budarin/psw-plugin-opfs-serve-range 1.0.5 → 1.0.6
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.
- package/LICENSE +21 -21
- package/README.md +215 -215
- package/README.ru.md +207 -207
- package/dist/opfsCacheOnFetch.js +18 -124
- package/docs/opfs-cache-behavior.md +346 -346
- package/docs/opfs-cache-behavior.ru.md +114 -114
- package/package.json +2 -2
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Вадим Бударин
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Вадим Бударин
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,215 +1,215 @@
|
|
|
1
|
-
# @budarin/psw-plugin-opfs-serve-range
|
|
2
|
-
|
|
3
|
-
[Русская версия](https://github.com/budarin/psw-plugin-opfs-serve-range/blob/master/README.ru.md)
|
|
4
|
-
|
|
5
|
-
Service Worker plugins and utilities for [@budarin/pluggable-serviceworker](https://www.npmjs.com/package/@budarin/pluggable-serviceworker) that serve HTTP Range requests from files stored in Origin Private File System (OPFS).
|
|
6
|
-
|
|
7
|
-
[](https://github.com/budarin/psw-plugin-opfs-serve-range/actions/workflows/ci.yml)
|
|
8
|
-
[](https://www.npmjs.com/package/@budarin/psw-plugin-opfs-serve-range)
|
|
9
|
-
[](https://www.npmjs.com/package/@budarin/psw-plugin-opfs-serve-range)
|
|
10
|
-
[](https://bundlephobia.com/result?p=@budarin/psw-plugin-opfs-serve-range)
|
|
11
|
-
[](https://www.npmjs.com/package/@budarin/psw-plugin-opfs-serve-range)
|
|
12
|
-
|
|
13
|
-
Large media files and other heavy assets are almost always requested in chunks via HTTP Range rather than as a single download. When such files live in a regular HTTP cache (Cache API), the service worker often has to read and process the entire file to serve a small range, which is wasteful in terms of memory and CPU and quickly hits storage limits on low‑end devices.
|
|
14
|
-
|
|
15
|
-
This package takes a different approach: it uses the Origin Private File System (OPFS) as the primary storage for large resources and range responses. Files are stored in OPFS in a custom format (one file per URL plus a metadata footer), and ranges are read directly from the file system instead of Cache API. On top of that, the package provides plugins for precaching, background downloads, and serving range requests.
|
|
16
|
-
|
|
17
|
-
Unlike `@budarin/psw-plugin-serve-range-requests`, which works on top of the regular browser cache (Cache API) and serves ranges for already cached responses, this package uses OPFS as the cache backend: it gives you explicit control over quota and eviction policy (limits, LRU, notifications to tabs), supports “download first, then play offline for a long time” scenarios (via Background Fetch and precache), and exposes utilities for implementing your own OPFS writers and plugins.
|
|
18
|
-
|
|
19
|
-
### What this package provides
|
|
20
|
-
|
|
21
|
-
- **opfsServeRange** – reads files from OPFS and serves byte ranges.
|
|
22
|
-
- **opfsPrecache** – during SW install, fetches a list of URLs and writes them to OPFS. Downloading large files at install time may take a long time – the UI should either warn users to wait or avoid putting huge files into precache. It is also important to note that if OPFS runs out of space while writing during the `install` phase and the operation fails, the whole service worker install fails (the SW is not installed). Use `opfsPrecache` only for resources that are guaranteed to fit even on small, partially filled devices; use `opfsRangeFromNetworkAndCache` or background downloads for heavy files.
|
|
23
|
-
- **opfsRangeFromNetworkAndCache** – handles requests that `opfsServeRange` did not serve (resource not in cache yet): goes to the network, streams the response to the client, and optionally starts a full background download into OPFS; only fully downloaded files are cached. If the tab or browser is closed or the network drops, the download is aborted; the next request for the same URL starts a new full download (which may be slow or expensive for large files). If you need downloads that survive tab or browser closes, or your files are very large, use the Background Fetch API utilities from `@budarin/pluggable-serviceworker`.
|
|
24
|
-
- **opfsBackgroundFetch** – on successful Background Fetch completion, writes responses into OPFS; subsequent Range requests for these URLs are served by `opfsServeRange`.
|
|
25
|
-
- **writeToOpfs**, **metadataFromResponse**, **urlToOpfsKey**, **isOpfsAvailable** – low‑level utilities for writing your own OPFS plugins; **isOpfsAvailable()** provides a synchronous check for OPFS support.
|
|
26
|
-
|
|
27
|
-
In environments without OPFS support, plugin factories return `undefined`.
|
|
28
|
-
|
|
29
|
-
All cache files live under a single OPFS directory. The directory name is configured once via **configureOpfs({ folderName })** before registering plugins (defaults to `'range-requests-cache'`). To clear the cache, call **clearOpfsCache()** – the whole directory is removed. Inside, there is one file per URL; all metadata is stored in the file footer.
|
|
30
|
-
|
|
31
|
-
Detailed cache behavior (limits, LRU, eviction, notifications) is described in [docs/opfs-cache-behavior.md](https://github.com/budarin/psw-plugin-opfs-serve-range/blob/master/docs/opfs-cache-behavior.md) (Russian version: [docs/opfs-cache-behavior.ru.md](https://github.com/budarin/psw-plugin-opfs-serve-range/blob/master/docs/opfs-cache-behavior.ru.md)).
|
|
32
|
-
|
|
33
|
-
## Install
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
pnpm add @budarin/psw-plugin-opfs-serve-range
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Usage
|
|
40
|
-
|
|
41
|
-
The following example shows how to configure media (video, map tiles, etc.) so that on the first request the content is loaded and stored in the local OPFS cache, and on subsequent requests – once fully downloaded – it is served from OPFS without hitting the network.
|
|
42
|
-
|
|
43
|
-
```typescript
|
|
44
|
-
import { initServiceWorker } from '@budarin/pluggable-serviceworker';
|
|
45
|
-
import {
|
|
46
|
-
configureOpfs,
|
|
47
|
-
opfsServeRange,
|
|
48
|
-
opfsRangeFromNetworkAndCache,
|
|
49
|
-
} from '@budarin/psw-plugin-opfs-serve-range';
|
|
50
|
-
|
|
51
|
-
configureOpfs({
|
|
52
|
-
folderName: 'ranges-media-cache',
|
|
53
|
-
maxCacheFraction: 0.5, // fraction of origin quota reserved for this cache (default 0.5)
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
initServiceWorker(
|
|
57
|
-
[
|
|
58
|
-
opfsServeRange({
|
|
59
|
-
order: -15,
|
|
60
|
-
include: ['*.mp4', '*.webm'
|
|
61
|
-
}),
|
|
62
|
-
opfsRangeFromNetworkAndCache({
|
|
63
|
-
order: -10,
|
|
64
|
-
include: ['*.mp4', '*.webm'
|
|
65
|
-
}),
|
|
66
|
-
],
|
|
67
|
-
{ version: '1.0.0' }
|
|
68
|
-
);
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Here `opfsServeRange` serves ranges from OPFS when the file is already cached; `opfsRangeFromNetworkAndCache` goes to the network when the file is not cached yet, streams the response to the client, and optionally fills OPFS in the background so that subsequent requests are served from OPFS. You can add **opfsPrecache** or **opfsBackgroundFetch** as needed; the set and order of plugins are fully configurable.
|
|
72
|
-
|
|
73
|
-
### Example: “Download for offline” (Background Fetch) + Range playback
|
|
74
|
-
|
|
75
|
-
**Scenario:** The user clicks “Download for offline” → a large file (video, map) is downloaded in the background; the tab may be closed. After the download finishes, a player or map viewer issues Range requests for this URL – responses come from OPFS, without re‑downloading the file. The goal is the full cycle “button → background download → offline playback from cache”.
|
|
76
|
-
|
|
77
|
-
**Client (page)** – trigger a background download based on user action:
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
import {
|
|
81
|
-
startBackgroundFetch,
|
|
82
|
-
isBackgroundFetchSupported,
|
|
83
|
-
} from '@budarin/pluggable-serviceworker/client/background-fetch';
|
|
84
|
-
|
|
85
|
-
async function downloadForOffline(
|
|
86
|
-
url: string,
|
|
87
|
-
title: string,
|
|
88
|
-
downloadTotal?: number
|
|
89
|
-
) {
|
|
90
|
-
const supported = await isBackgroundFetchSupported();
|
|
91
|
-
if (!supported) {
|
|
92
|
-
console.warn('Background Fetch API is not supported');
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
const reg = await navigator.serviceWorker.ready;
|
|
96
|
-
const id = `offline-${Date.now()}`;
|
|
97
|
-
await startBackgroundFetch(reg, id, [url], { title, downloadTotal });
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
**Service worker** – register plugins (after Background Fetch completes, the file is written into the range cache, and subsequent Range requests are served by `opfsServeRange`):
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
import { initServiceWorker } from '@budarin/pluggable-serviceworker';
|
|
105
|
-
import {
|
|
106
|
-
configureOpfs,
|
|
107
|
-
opfsServeRange,
|
|
108
|
-
opfsRangeFromNetworkAndCache,
|
|
109
|
-
opfsBackgroundFetch,
|
|
110
|
-
} from '@budarin/psw-plugin-opfs-serve-range';
|
|
111
|
-
|
|
112
|
-
configureOpfs({ folderName: 'range-requests-cache', maxCacheFraction: 0.5 });
|
|
113
|
-
|
|
114
|
-
initServiceWorker(
|
|
115
|
-
[
|
|
116
|
-
opfsServeRange({
|
|
117
|
-
order: -15,
|
|
118
|
-
include: ['*.mp4', '*.webm'
|
|
119
|
-
}),
|
|
120
|
-
opfsRangeFromNetworkAndCache({
|
|
121
|
-
order: -10,
|
|
122
|
-
include: ['*.mp4', '*.webm'
|
|
123
|
-
}),
|
|
124
|
-
opfsBackgroundFetch({
|
|
125
|
-
include: ['*.mp4', '*.webm'
|
|
126
|
-
enableLogging: true,
|
|
127
|
-
}),
|
|
128
|
-
],
|
|
129
|
-
{ version: '1.0.0' }
|
|
130
|
-
);
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
## OPFS storage format
|
|
134
|
-
|
|
135
|
-
If you implement your own writer plugin or serve files from OPFS directly (bypassing these plugins), the format details are important. The cache key is `hex(SHA-256(URL))` (64 characters). There is one OPFS file per URL: the file layout is `[body][JSON metadata][4‑byte JSON length (uint32 LE)]`. To clear the cache, delete a file by key or remove the entire directory via `clearOpfsCache`.
|
|
136
|
-
|
|
137
|
-
**Important:** if you serve a file from OPFS **as a whole** (e.g. `200` without Range) to a player or other code, you must strip the footer and only return the body: first read the footer, compute `bodySize`, then do `new Response(file.slice(0, bodySize), ...)`. The `opfsServeRange` plugin only serves body ranges (`206`) and never exposes the footer.
|
|
138
|
-
|
|
139
|
-
Metadata example (JSON footer): `url`, `size`, `type`, `etag`, `lastModified`. All plugins in this package use the same format and the shared `urlToOpfsKey`.
|
|
140
|
-
|
|
141
|
-
## Writing your own OPFS plugin
|
|
142
|
-
|
|
143
|
-
If you need to write into OPFS following the same format as the built‑in plugins, you can use **getOpfsDir**, **urlToOpfsKey**, **writeToOpfs**, **metadataFromResponse**. Example:
|
|
144
|
-
|
|
145
|
-
```typescript
|
|
146
|
-
import {
|
|
147
|
-
getOpfsDir,
|
|
148
|
-
urlToOpfsKey,
|
|
149
|
-
writeToOpfs,
|
|
150
|
-
metadataFromResponse,
|
|
151
|
-
} from '@budarin/psw-plugin-opfs-serve-range';
|
|
152
|
-
|
|
153
|
-
const root = await navigator.storage.getDirectory();
|
|
154
|
-
const dir = await getOpfsDir(root, true);
|
|
155
|
-
const key = await urlToOpfsKey(url);
|
|
156
|
-
const metadata = metadataFromResponse(response, url);
|
|
157
|
-
await writeToOpfs(dir, key, response.body, metadata);
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
The response may not have a `Content-Length` header – when writing the full body, the size is determined automatically from the bytes written. When using limits, pass the fifth `options` argument to `writeToOpfs`: `{ url, knownSize }` (for example, `knownSize: metadata.size > 0 ? metadata.size : undefined`).
|
|
161
|
-
|
|
162
|
-
## Client utilities
|
|
163
|
-
|
|
164
|
-
Client‑side helpers are exported from the entry point `@budarin/psw-plugin-opfs-serve-range/client`.
|
|
165
|
-
|
|
166
|
-
### Tab notifications about quota and limits
|
|
167
|
-
|
|
168
|
-
The service worker sends messages to clients when quota is exceeded, writes are refused, eviction happens, etc. You can subscribe using typed handlers from the client entry point `@budarin/psw-plugin-opfs-serve-range/client`:
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
import {
|
|
172
|
-
onOPFSQuotaExceeded,
|
|
173
|
-
onOPFSWriteSkipped,
|
|
174
|
-
onOPFSSkipQuotaExceeded,
|
|
175
|
-
} from '@budarin/psw-plugin-opfs-serve-range/client';
|
|
176
|
-
|
|
177
|
-
onOPFSQuotaExceeded((event) => {
|
|
178
|
-
console.warn('OPFS: quota exceeded', event.data?.url);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
onOPFSSkipQuotaExceeded((event) => {
|
|
182
|
-
console.warn('OPFS: resource not cached (quota)', event.data?.url);
|
|
183
|
-
});
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
See [docs/opfs-cache-behavior.md](https://github.com/budarin/psw-plugin-opfs-serve-range/blob/master/docs/opfs-cache-behavior.md) for details (Russian version: [docs/opfs-cache-behavior.ru.md](https://github.com/budarin/psw-plugin-opfs-serve-range/blob/master/docs/opfs-cache-behavior.ru.md)).
|
|
187
|
-
|
|
188
|
-
### Clearing the cache and managing individual resources
|
|
189
|
-
|
|
190
|
-
To wipe the whole cache (e.g. from a UI button or on logout), call `clearOpfsCache()` from the service worker or client – the entire cache directory will be deleted.
|
|
191
|
-
|
|
192
|
-
If you need finer‑grained control (show a list of cached resources and let users delete specific ones), use the client utilities from the entry point `@budarin/psw-plugin-opfs-serve-range/client`. The list is built from metadata in the footer (each file stores its original `url`):
|
|
193
|
-
|
|
194
|
-
- get a list of resources stored in OPFS with sizes and types – `listOpfsCachedResources()`;
|
|
195
|
-
- check whether a particular URL is cached – `hasInOpfsCache(url)`;
|
|
196
|
-
- delete a single resource by URL – `deleteFromOpfsCache(url)`.
|
|
197
|
-
|
|
198
|
-
These helpers are described in more detail in the **Client utilities** section of this README and in the TypeScript definitions.
|
|
199
|
-
|
|
200
|
-
## Plugin options
|
|
201
|
-
|
|
202
|
-
The cache folder name and quota fraction are configured via **configureOpfs({ folderName, maxCacheFraction })**.
|
|
203
|
-
|
|
204
|
-
- **opfsServeRange:** `order`, `enableLogging`, `include`, `exclude`, `rangeResponseCacheControl` – to restrict which URLs are served and how 206 responses are cached by the browser.
|
|
205
|
-
- **opfsPrecache:** `urls` (array or function returning an array), `order`, `enableLogging` – which URLs to fetch at SW install.
|
|
206
|
-
- **opfsRangeFromNetworkAndCache:** `order` (e.g. `-10`, after `opfsServeRange`), `include`, `exclude`, `enableLogging` – which requests to cache; on Range requests it streams the response immediately and optionally fills OPFS in the background. With `enableLogging`, a warning is logged when a file already exists in OPFS but the Range response is served from network (e.g. because of If-Range mismatch or plugin ordering).
|
|
207
|
-
- **opfsBackgroundFetch:** `order`, `include`, `exclude`, `enableLogging` – which URLs to write into OPFS when Background Fetch completes. `fail`/`abort`/`click` events are logged with `enableLogging`; you can register your own plugin with the same hooks (e.g. to show UI on fail). To trigger downloads from the client, use utilities from `@budarin/pluggable-serviceworker/client/background-fetch`.
|
|
208
|
-
|
|
209
|
-
## Requirements
|
|
210
|
-
|
|
211
|
-
- A browser with OPFS support (Chrome 108+, Edge 108+, Firefox 111+, Safari 16.4+) and a secure context (HTTPS).
|
|
212
|
-
|
|
213
|
-
## License
|
|
214
|
-
|
|
215
|
-
MIT
|
|
1
|
+
# @budarin/psw-plugin-opfs-serve-range
|
|
2
|
+
|
|
3
|
+
[Русская версия](https://github.com/budarin/psw-plugin-opfs-serve-range/blob/master/README.ru.md)
|
|
4
|
+
|
|
5
|
+
Service Worker plugins and utilities for [@budarin/pluggable-serviceworker](https://www.npmjs.com/package/@budarin/pluggable-serviceworker) that serve HTTP Range requests from files stored in Origin Private File System (OPFS).
|
|
6
|
+
|
|
7
|
+
[](https://github.com/budarin/psw-plugin-opfs-serve-range/actions/workflows/ci.yml)
|
|
8
|
+
[](https://www.npmjs.com/package/@budarin/psw-plugin-opfs-serve-range)
|
|
9
|
+
[](https://www.npmjs.com/package/@budarin/psw-plugin-opfs-serve-range)
|
|
10
|
+
[](https://bundlephobia.com/result?p=@budarin/psw-plugin-opfs-serve-range)
|
|
11
|
+
[](https://www.npmjs.com/package/@budarin/psw-plugin-opfs-serve-range)
|
|
12
|
+
|
|
13
|
+
Large media files and other heavy assets are almost always requested in chunks via HTTP Range rather than as a single download. When such files live in a regular HTTP cache (Cache API), the service worker often has to read and process the entire file to serve a small range, which is wasteful in terms of memory and CPU and quickly hits storage limits on low‑end devices.
|
|
14
|
+
|
|
15
|
+
This package takes a different approach: it uses the Origin Private File System (OPFS) as the primary storage for large resources and range responses. Files are stored in OPFS in a custom format (one file per URL plus a metadata footer), and ranges are read directly from the file system instead of Cache API. On top of that, the package provides plugins for precaching, background downloads, and serving range requests.
|
|
16
|
+
|
|
17
|
+
Unlike `@budarin/psw-plugin-serve-range-requests`, which works on top of the regular browser cache (Cache API) and serves ranges for already cached responses, this package uses OPFS as the cache backend: it gives you explicit control over quota and eviction policy (limits, LRU, notifications to tabs), supports “download first, then play offline for a long time” scenarios (via Background Fetch and precache), and exposes utilities for implementing your own OPFS writers and plugins.
|
|
18
|
+
|
|
19
|
+
### What this package provides
|
|
20
|
+
|
|
21
|
+
- **opfsServeRange** – reads files from OPFS and serves byte ranges.
|
|
22
|
+
- **opfsPrecache** – during SW install, fetches a list of URLs and writes them to OPFS. Downloading large files at install time may take a long time – the UI should either warn users to wait or avoid putting huge files into precache. It is also important to note that if OPFS runs out of space while writing during the `install` phase and the operation fails, the whole service worker install fails (the SW is not installed). Use `opfsPrecache` only for resources that are guaranteed to fit even on small, partially filled devices; use `opfsRangeFromNetworkAndCache` or background downloads for heavy files.
|
|
23
|
+
- **opfsRangeFromNetworkAndCache** – handles requests that `opfsServeRange` did not serve (resource not in cache yet): goes to the network, streams the response to the client, and optionally starts a full background download into OPFS; only fully downloaded files are cached. If the tab or browser is closed or the network drops, the download is aborted; the next request for the same URL starts a new full download (which may be slow or expensive for large files). If you need downloads that survive tab or browser closes, or your files are very large, use the Background Fetch API utilities from `@budarin/pluggable-serviceworker`.
|
|
24
|
+
- **opfsBackgroundFetch** – on successful Background Fetch completion, writes responses into OPFS; subsequent Range requests for these URLs are served by `opfsServeRange`.
|
|
25
|
+
- **writeToOpfs**, **metadataFromResponse**, **urlToOpfsKey**, **isOpfsAvailable** – low‑level utilities for writing your own OPFS plugins; **isOpfsAvailable()** provides a synchronous check for OPFS support.
|
|
26
|
+
|
|
27
|
+
In environments without OPFS support, plugin factories return `undefined`.
|
|
28
|
+
|
|
29
|
+
All cache files live under a single OPFS directory. The directory name is configured once via **configureOpfs({ folderName })** before registering plugins (defaults to `'range-requests-cache'`). To clear the cache, call **clearOpfsCache()** – the whole directory is removed. Inside, there is one file per URL; all metadata is stored in the file footer.
|
|
30
|
+
|
|
31
|
+
Detailed cache behavior (limits, LRU, eviction, notifications) is described in [docs/opfs-cache-behavior.md](https://github.com/budarin/psw-plugin-opfs-serve-range/blob/master/docs/opfs-cache-behavior.md) (Russian version: [docs/opfs-cache-behavior.ru.md](https://github.com/budarin/psw-plugin-opfs-serve-range/blob/master/docs/opfs-cache-behavior.ru.md)).
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pnpm add @budarin/psw-plugin-opfs-serve-range
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
The following example shows how to configure media (video, map tiles, etc.) so that on the first request the content is loaded and stored in the local OPFS cache, and on subsequent requests – once fully downloaded – it is served from OPFS without hitting the network.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { initServiceWorker } from '@budarin/pluggable-serviceworker';
|
|
45
|
+
import {
|
|
46
|
+
configureOpfs,
|
|
47
|
+
opfsServeRange,
|
|
48
|
+
opfsRangeFromNetworkAndCache,
|
|
49
|
+
} from '@budarin/psw-plugin-opfs-serve-range';
|
|
50
|
+
|
|
51
|
+
configureOpfs({
|
|
52
|
+
folderName: 'ranges-media-cache',
|
|
53
|
+
maxCacheFraction: 0.5, // fraction of origin quota reserved for this cache (default 0.5)
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
initServiceWorker(
|
|
57
|
+
[
|
|
58
|
+
opfsServeRange({
|
|
59
|
+
order: -15,
|
|
60
|
+
include: ['*.mp4', '*.webm'],
|
|
61
|
+
}),
|
|
62
|
+
opfsRangeFromNetworkAndCache({
|
|
63
|
+
order: -10,
|
|
64
|
+
include: ['*.mp4', '*.webm'],
|
|
65
|
+
}),
|
|
66
|
+
],
|
|
67
|
+
{ version: '1.0.0' }
|
|
68
|
+
);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Here `opfsServeRange` serves ranges from OPFS when the file is already cached; `opfsRangeFromNetworkAndCache` goes to the network when the file is not cached yet, streams the response to the client, and optionally fills OPFS in the background so that subsequent requests are served from OPFS. You can add **opfsPrecache** or **opfsBackgroundFetch** as needed; the set and order of plugins are fully configurable.
|
|
72
|
+
|
|
73
|
+
### Example: “Download for offline” (Background Fetch) + Range playback
|
|
74
|
+
|
|
75
|
+
**Scenario:** The user clicks “Download for offline” → a large file (video, map) is downloaded in the background; the tab may be closed. After the download finishes, a player or map viewer issues Range requests for this URL – responses come from OPFS, without re‑downloading the file. The goal is the full cycle “button → background download → offline playback from cache”.
|
|
76
|
+
|
|
77
|
+
**Client (page)** – trigger a background download based on user action:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import {
|
|
81
|
+
startBackgroundFetch,
|
|
82
|
+
isBackgroundFetchSupported,
|
|
83
|
+
} from '@budarin/pluggable-serviceworker/client/background-fetch';
|
|
84
|
+
|
|
85
|
+
async function downloadForOffline(
|
|
86
|
+
url: string,
|
|
87
|
+
title: string,
|
|
88
|
+
downloadTotal?: number
|
|
89
|
+
) {
|
|
90
|
+
const supported = await isBackgroundFetchSupported();
|
|
91
|
+
if (!supported) {
|
|
92
|
+
console.warn('Background Fetch API is not supported');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const reg = await navigator.serviceWorker.ready;
|
|
96
|
+
const id = `offline-${Date.now()}`;
|
|
97
|
+
await startBackgroundFetch(reg, id, [url], { title, downloadTotal });
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Service worker** – register plugins (after Background Fetch completes, the file is written into the range cache, and subsequent Range requests are served by `opfsServeRange`):
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { initServiceWorker } from '@budarin/pluggable-serviceworker';
|
|
105
|
+
import {
|
|
106
|
+
configureOpfs,
|
|
107
|
+
opfsServeRange,
|
|
108
|
+
opfsRangeFromNetworkAndCache,
|
|
109
|
+
opfsBackgroundFetch,
|
|
110
|
+
} from '@budarin/psw-plugin-opfs-serve-range';
|
|
111
|
+
|
|
112
|
+
configureOpfs({ folderName: 'range-requests-cache', maxCacheFraction: 0.5 });
|
|
113
|
+
|
|
114
|
+
initServiceWorker(
|
|
115
|
+
[
|
|
116
|
+
opfsServeRange({
|
|
117
|
+
order: -15,
|
|
118
|
+
include: ['*.mp4', '*.webm'],
|
|
119
|
+
}),
|
|
120
|
+
opfsRangeFromNetworkAndCache({
|
|
121
|
+
order: -10,
|
|
122
|
+
include: ['*.mp4', '*.webm'],
|
|
123
|
+
}),
|
|
124
|
+
opfsBackgroundFetch({
|
|
125
|
+
include: ['*.mp4', '*.webm'],
|
|
126
|
+
enableLogging: true,
|
|
127
|
+
}),
|
|
128
|
+
],
|
|
129
|
+
{ version: '1.0.0' }
|
|
130
|
+
);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## OPFS storage format
|
|
134
|
+
|
|
135
|
+
If you implement your own writer plugin or serve files from OPFS directly (bypassing these plugins), the format details are important. The cache key is `hex(SHA-256(URL))` (64 characters). There is one OPFS file per URL: the file layout is `[body][JSON metadata][4‑byte JSON length (uint32 LE)]`. To clear the cache, delete a file by key or remove the entire directory via `clearOpfsCache`.
|
|
136
|
+
|
|
137
|
+
**Important:** if you serve a file from OPFS **as a whole** (e.g. `200` without Range) to a player or other code, you must strip the footer and only return the body: first read the footer, compute `bodySize`, then do `new Response(file.slice(0, bodySize), ...)`. The `opfsServeRange` plugin only serves body ranges (`206`) and never exposes the footer.
|
|
138
|
+
|
|
139
|
+
Metadata example (JSON footer): `url`, `size`, `type`, `etag`, `lastModified`. All plugins in this package use the same format and the shared `urlToOpfsKey`.
|
|
140
|
+
|
|
141
|
+
## Writing your own OPFS plugin
|
|
142
|
+
|
|
143
|
+
If you need to write into OPFS following the same format as the built‑in plugins, you can use **getOpfsDir**, **urlToOpfsKey**, **writeToOpfs**, **metadataFromResponse**. Example:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import {
|
|
147
|
+
getOpfsDir,
|
|
148
|
+
urlToOpfsKey,
|
|
149
|
+
writeToOpfs,
|
|
150
|
+
metadataFromResponse,
|
|
151
|
+
} from '@budarin/psw-plugin-opfs-serve-range';
|
|
152
|
+
|
|
153
|
+
const root = await navigator.storage.getDirectory();
|
|
154
|
+
const dir = await getOpfsDir(root, true);
|
|
155
|
+
const key = await urlToOpfsKey(url);
|
|
156
|
+
const metadata = metadataFromResponse(response, url);
|
|
157
|
+
await writeToOpfs(dir, key, response.body, metadata);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The response may not have a `Content-Length` header – when writing the full body, the size is determined automatically from the bytes written. When using limits, pass the fifth `options` argument to `writeToOpfs`: `{ url, knownSize }` (for example, `knownSize: metadata.size > 0 ? metadata.size : undefined`).
|
|
161
|
+
|
|
162
|
+
## Client utilities
|
|
163
|
+
|
|
164
|
+
Client‑side helpers are exported from the entry point `@budarin/psw-plugin-opfs-serve-range/client`.
|
|
165
|
+
|
|
166
|
+
### Tab notifications about quota and limits
|
|
167
|
+
|
|
168
|
+
The service worker sends messages to clients when quota is exceeded, writes are refused, eviction happens, etc. You can subscribe using typed handlers from the client entry point `@budarin/psw-plugin-opfs-serve-range/client`:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import {
|
|
172
|
+
onOPFSQuotaExceeded,
|
|
173
|
+
onOPFSWriteSkipped,
|
|
174
|
+
onOPFSSkipQuotaExceeded,
|
|
175
|
+
} from '@budarin/psw-plugin-opfs-serve-range/client';
|
|
176
|
+
|
|
177
|
+
onOPFSQuotaExceeded((event) => {
|
|
178
|
+
console.warn('OPFS: quota exceeded', event.data?.url);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
onOPFSSkipQuotaExceeded((event) => {
|
|
182
|
+
console.warn('OPFS: resource not cached (quota)', event.data?.url);
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
See [docs/opfs-cache-behavior.md](https://github.com/budarin/psw-plugin-opfs-serve-range/blob/master/docs/opfs-cache-behavior.md) for details (Russian version: [docs/opfs-cache-behavior.ru.md](https://github.com/budarin/psw-plugin-opfs-serve-range/blob/master/docs/opfs-cache-behavior.ru.md)).
|
|
187
|
+
|
|
188
|
+
### Clearing the cache and managing individual resources
|
|
189
|
+
|
|
190
|
+
To wipe the whole cache (e.g. from a UI button or on logout), call `clearOpfsCache()` from the service worker or client – the entire cache directory will be deleted.
|
|
191
|
+
|
|
192
|
+
If you need finer‑grained control (show a list of cached resources and let users delete specific ones), use the client utilities from the entry point `@budarin/psw-plugin-opfs-serve-range/client`. The list is built from metadata in the footer (each file stores its original `url`):
|
|
193
|
+
|
|
194
|
+
- get a list of resources stored in OPFS with sizes and types – `listOpfsCachedResources()`;
|
|
195
|
+
- check whether a particular URL is cached – `hasInOpfsCache(url)`;
|
|
196
|
+
- delete a single resource by URL – `deleteFromOpfsCache(url)`.
|
|
197
|
+
|
|
198
|
+
These helpers are described in more detail in the **Client utilities** section of this README and in the TypeScript definitions.
|
|
199
|
+
|
|
200
|
+
## Plugin options
|
|
201
|
+
|
|
202
|
+
The cache folder name and quota fraction are configured via **configureOpfs({ folderName, maxCacheFraction })**.
|
|
203
|
+
|
|
204
|
+
- **opfsServeRange:** `order`, `enableLogging`, `include`, `exclude`, `rangeResponseCacheControl` – to restrict which URLs are served and how 206 responses are cached by the browser.
|
|
205
|
+
- **opfsPrecache:** `urls` (array or function returning an array), `order`, `enableLogging` – which URLs to fetch at SW install.
|
|
206
|
+
- **opfsRangeFromNetworkAndCache:** `order` (e.g. `-10`, after `opfsServeRange`), `include`, `exclude`, `enableLogging` – which requests to cache; on Range requests it streams the response immediately and optionally fills OPFS in the background. With `enableLogging`, a warning is logged when a file already exists in OPFS but the Range response is served from network (e.g. because of If-Range mismatch or plugin ordering).
|
|
207
|
+
- **opfsBackgroundFetch:** `order`, `include`, `exclude`, `enableLogging` – which URLs to write into OPFS when Background Fetch completes. `fail`/`abort`/`click` events are logged with `enableLogging`; you can register your own plugin with the same hooks (e.g. to show UI on fail). To trigger downloads from the client, use utilities from `@budarin/pluggable-serviceworker/client/background-fetch`.
|
|
208
|
+
|
|
209
|
+
## Requirements
|
|
210
|
+
|
|
211
|
+
- A browser with OPFS support (Chrome 108+, Edge 108+, Firefox 111+, Safari 16.4+) and a secure context (HTTPS).
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
MIT
|