feedkeeper 0.27.2 → 0.28.1
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/package.json +1 -1
- package/server/dist/api/v1/index.js +1 -0
- package/server/dist/db/migrations/0040_empty_gallery.sql +10 -0
- package/server/dist/feeds/articleContent.js +1 -1
- package/server/dist/feeds/contentNoise.js +73 -3
- package/server/dist/feeds/fullTextQueue.js +2 -0
- package/server/dist/feeds/galleryContent.js +33 -0
- package/server/dist/feeds/lead.js +13 -5
- package/server/dist/feeds/repository.js +11 -2
- package/server/dist/native/editions.js +4 -3
- package/server/dist/push.js +2 -0
- package/server/dist/pushOutbox.js +1 -1
- package/server/dist/sync/changeLog.js +2 -0
- package/server/package.json +1 -1
- package/web/dist/assets/index-CTTNHCfi.js +32 -0
- package/web/dist/index.html +1 -1
- package/web/dist/sw.js +62 -13
- package/web/dist/assets/index-Ci8_fWis.js +0 -32
package/web/dist/index.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<meta name="twitter:image" content="/logo.svg" />
|
|
31
31
|
<!-- Inline so the splash paints before any script or stylesheet loads -->
|
|
32
32
|
<style>html{background:#f8fafc}html[data-theme=dark]{background:#0b0f17}@media (prefers-color-scheme:dark){html:not([data-theme=light]){background:#0b0f17}}#splash{z-index:100;background:inherit;justify-content:center;align-items:center;transition:opacity .3s;display:flex;position:fixed;inset:0}#splash img{width:62px;height:62px;animation:.4s both splash-in}#splash.splash-hidden{opacity:0;pointer-events:none}@keyframes splash-in{0%{opacity:0;transform:scale(.92)}}@media (prefers-reduced-motion:reduce){#splash,#splash img{transition:none;animation:none}}</style>
|
|
33
|
-
<script type="module" crossorigin src="/assets/index-
|
|
33
|
+
<script type="module" crossorigin src="/assets/index-CTTNHCfi.js"></script>
|
|
34
34
|
<link rel="stylesheet" crossorigin href="/assets/index-BprbGvrl.css">
|
|
35
35
|
</head>
|
|
36
36
|
<body>
|
package/web/dist/sw.js
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
// FeedKeeper service worker. Built into dist/sw.js, where the placeholder below is
|
|
2
2
|
// replaced with a hash of the build's file names so every release gets its own app cache.
|
|
3
|
-
const BUILD_ID = "
|
|
3
|
+
const BUILD_ID = "0038c2a9c847";
|
|
4
4
|
const STATIC_CACHE = `fk-static-${BUILD_ID}`;
|
|
5
5
|
const API_CACHE = "fk-api";
|
|
6
|
+
let privateGeneration = 0;
|
|
7
|
+
let authenticationChanges = 0;
|
|
8
|
+
|
|
9
|
+
async function invalidatePrivateCache() {
|
|
10
|
+
privateGeneration++;
|
|
11
|
+
await caches.delete(API_CACHE);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function authenticate(request) {
|
|
15
|
+
authenticationChanges++;
|
|
16
|
+
try {
|
|
17
|
+
await invalidatePrivateCache();
|
|
18
|
+
return await fetch(request);
|
|
19
|
+
} finally {
|
|
20
|
+
// Responses that started before the cookie changed must not refill the new session's cache.
|
|
21
|
+
await invalidatePrivateCache();
|
|
22
|
+
authenticationChanges--;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
6
25
|
const SHELL = "/index.html";
|
|
7
26
|
const PRECACHE = ["/theme-init.js", "/manifest.webmanifest", "/logo.svg", "/favicon-32.png?v=2", "/app-icon-180.png?v=2", "/app-icon-192.png?v=2", "/app-icon-512.png?v=2"];
|
|
8
27
|
// The server answers with `Vary: Origin`, but entries cached without that header must still match.
|
|
@@ -30,6 +49,9 @@ self.addEventListener("activate", (event) => {
|
|
|
30
49
|
(async () => {
|
|
31
50
|
const names = await caches.keys();
|
|
32
51
|
await Promise.all(names.filter((name) => name.startsWith("fk-static-") && name !== STATIC_CACHE).map((name) => caches.delete(name)));
|
|
52
|
+
// Older releases stored personal responses without checking account ownership.
|
|
53
|
+
await invalidatePrivateCache();
|
|
54
|
+
await caches.delete("fk-offline");
|
|
33
55
|
await self.clients.claim();
|
|
34
56
|
})(),
|
|
35
57
|
);
|
|
@@ -42,9 +64,16 @@ self.addEventListener("message", (event) => {
|
|
|
42
64
|
|
|
43
65
|
self.addEventListener("fetch", (event) => {
|
|
44
66
|
const { request } = event;
|
|
45
|
-
if (request.method !== "GET") return;
|
|
46
67
|
const url = new URL(request.url);
|
|
47
68
|
if (url.origin !== self.location.origin) return;
|
|
69
|
+
if (request.method !== "GET") {
|
|
70
|
+
if (url.pathname.startsWith("/api/auth/") || url.pathname.startsWith("/auth/") || url.pathname.startsWith("/api/onboarding/")) {
|
|
71
|
+
event.respondWith(authenticate(request));
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// While a login changes the cookie, personal requests must bypass all offline responses.
|
|
76
|
+
if (authenticationChanges && url.pathname.startsWith("/api/")) return;
|
|
48
77
|
// Server-rendered OAuth pages and discovery documents must never be answered with the app shell.
|
|
49
78
|
if (url.pathname.startsWith("/oauth/") || url.pathname.startsWith("/.well-known/")) return;
|
|
50
79
|
|
|
@@ -106,11 +135,12 @@ async function navigate(request) {
|
|
|
106
135
|
}
|
|
107
136
|
|
|
108
137
|
async function cacheFirst(cacheName, request, limit = 0) {
|
|
138
|
+
const generation = privateGeneration;
|
|
109
139
|
const cache = await caches.open(cacheName);
|
|
110
140
|
const cached = await cache.match(request, MATCH);
|
|
111
|
-
if (cached) return cached;
|
|
141
|
+
if (cached && (cacheName !== API_CACHE || generation === privateGeneration)) return cached;
|
|
112
142
|
const response = await fetch(request);
|
|
113
|
-
if (response.ok) {
|
|
143
|
+
if (response.ok && (cacheName !== API_CACHE || generation === privateGeneration)) {
|
|
114
144
|
await cache.put(request, response.clone());
|
|
115
145
|
if (limit) await trim(cache, (key) => /\/api\/(archive\/images\/|items\/\d+\/image)/.test(key.url), limit);
|
|
116
146
|
}
|
|
@@ -118,12 +148,13 @@ async function cacheFirst(cacheName, request, limit = 0) {
|
|
|
118
148
|
}
|
|
119
149
|
|
|
120
150
|
async function mutableImage(request) {
|
|
151
|
+
const generation = privateGeneration;
|
|
121
152
|
const cache = await caches.open(API_CACHE);
|
|
122
153
|
const cached = await cache.match(request, MATCH);
|
|
123
154
|
const date = cached && Date.parse(cached.headers.get("date") ?? "");
|
|
124
155
|
const maxAge = Number(cached?.headers.get("cache-control")?.match(/max-age=(\d+)/)?.[1] ?? 0);
|
|
125
156
|
// Old immutable proxy responses must also expire at the new six-hour boundary.
|
|
126
|
-
if (cached && !cached.headers.get("cache-control")?.includes("no-store") && Date.now() - date < Math.min(maxAge, 21600) * 1000) return cached;
|
|
157
|
+
if (cached && generation === privateGeneration && !cached.headers.get("cache-control")?.includes("no-store") && Date.now() - date < Math.min(maxAge, 21600) * 1000) return cached;
|
|
127
158
|
try {
|
|
128
159
|
const headers = new Headers(request.headers);
|
|
129
160
|
if (cached?.headers.get("etag")) headers.set("If-None-Match", cached.headers.get("etag"));
|
|
@@ -134,40 +165,58 @@ async function mutableImage(request) {
|
|
|
134
165
|
refreshed.set("date", response.headers.get("date") ?? new Date().toUTCString());
|
|
135
166
|
response = new Response(await cached.arrayBuffer(), { status: 200, headers: refreshed });
|
|
136
167
|
}
|
|
137
|
-
if (response.ok) {
|
|
168
|
+
if (response.ok && generation === privateGeneration) {
|
|
138
169
|
if (response.headers.get("cache-control")?.includes("no-store")) await cache.delete(request);
|
|
139
170
|
else { await cache.put(request, response.clone()); await trim(cache, key => /\/api\/(archive\/images\/|items\/\d+\/image)/.test(key.url), MAX_CACHED_IMAGES); }
|
|
140
171
|
}
|
|
141
172
|
return response; // An online denial must never fall back to the previous account's bytes.
|
|
142
|
-
} catch { return cached ?? Response.error(); }
|
|
173
|
+
} catch { return generation === privateGeneration ? cached ?? Response.error() : Response.error(); }
|
|
143
174
|
}
|
|
144
175
|
|
|
145
176
|
async function networkFirst(cacheName, request, limit) {
|
|
146
|
-
|
|
177
|
+
let generation = privateGeneration;
|
|
178
|
+
let cache = await caches.open(cacheName);
|
|
147
179
|
try {
|
|
148
180
|
const response = await withTimeout(fetch(request), NETWORK_TIMEOUT_MS);
|
|
149
|
-
if (
|
|
181
|
+
if (generation !== privateGeneration) throw new Error("session_changed");
|
|
182
|
+
if (new URL(request.url).pathname === "/api/auth/me" && generation === privateGeneration) {
|
|
183
|
+
if (response.ok) {
|
|
184
|
+
const user = await response.clone().json();
|
|
185
|
+
const previous = await cache.match(request, MATCH);
|
|
186
|
+
const previousUser = previous ? await previous.json() : null;
|
|
187
|
+
if (!Number.isSafeInteger(user.id) || user.id <= 0) return Response.error();
|
|
188
|
+
if (previousUser?.id !== user.id) {
|
|
189
|
+
await invalidatePrivateCache();
|
|
190
|
+
generation = privateGeneration;
|
|
191
|
+
cache = await caches.open(cacheName);
|
|
192
|
+
}
|
|
193
|
+
} else if (response.status === 401) {
|
|
194
|
+
await invalidatePrivateCache();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (response.ok && generation === privateGeneration && !authenticationChanges) {
|
|
150
198
|
await cache.put(request, response.clone());
|
|
151
199
|
if (limit) await trim(cache, (key) => new URL(key.url).pathname === new URL(request.url).pathname, limit);
|
|
152
200
|
}
|
|
153
201
|
return response;
|
|
154
202
|
} catch (error) {
|
|
155
|
-
const cached = await cache.match(request, MATCH);
|
|
156
|
-
if (cached) return cached;
|
|
203
|
+
const cached = generation === privateGeneration && !authenticationChanges ? await cache.match(request, MATCH) : null;
|
|
204
|
+
if (cached && generation === privateGeneration) return cached;
|
|
157
205
|
throw error;
|
|
158
206
|
}
|
|
159
207
|
}
|
|
160
208
|
|
|
161
209
|
async function staleWhileRevalidate(cacheName, request) {
|
|
210
|
+
const generation = privateGeneration;
|
|
162
211
|
const cache = await caches.open(cacheName);
|
|
163
212
|
const cached = await cache.match(request, MATCH);
|
|
164
213
|
const refresh = fetch(request)
|
|
165
214
|
.then((response) => {
|
|
166
|
-
if (response.ok) cache.put(request, response.clone());
|
|
215
|
+
if (response.ok && (cacheName !== API_CACHE || generation === privateGeneration)) return cache.put(request, response.clone()).then(() => response);
|
|
167
216
|
return response;
|
|
168
217
|
})
|
|
169
218
|
.catch(() => undefined);
|
|
170
|
-
return cached ?? (await refresh) ?? Response.error();
|
|
219
|
+
return (cacheName !== API_CACHE || generation === privateGeneration ? cached : null) ?? (await refresh) ?? Response.error();
|
|
171
220
|
}
|
|
172
221
|
|
|
173
222
|
/** Drop the oldest entries so a long-lived install does not grow without bound. */
|