Feature: offline: more features to be able to work fully offline

This commit is contained in:
Pieter Vander Vennet 2025-08-03 16:35:38 +02:00
parent 825efdee34
commit 06aa8a3406
23 changed files with 203 additions and 60 deletions

View file

@ -430,10 +430,10 @@
} }
}, },
{ {
"condition": "_favourite=yes", "id": "favourite_icon",
"description": "Only for rendering", "description": "Only for rendering",
"icon": "circle:white;heart:red", "icon": "circle:white;heart:red",
"id": "favourite_icon", "condition": "_favourite=yes",
"metacondition": "__showTimeSensitiveIcons!=no" "metacondition": "__showTimeSensitiveIcons!=no"
}, },
{ {

View file

@ -217,8 +217,8 @@
}, },
{ {
"id": "debug", "id": "debug",
"metacondition": "__featureSwitchIsDebugging=true", "render": "{all_tags()}",
"render": "{all_tags()}" "metacondition": "__featureSwitchIsDebugging=true"
} }
], ],
"filter": [ "filter": [

View file

@ -114,9 +114,9 @@
"lineRendering": [], "lineRendering": [],
"tagRenderings": [ "tagRenderings": [
{ {
"classes": "p-0",
"id": "conversation", "id": "conversation",
"render": "{visualize_note_comments()}" "render": "{visualize_note_comments()}",
"classes": "p-0"
}, },
{ {
"id": "add_image", "id": "add_image",

View file

@ -66,16 +66,16 @@
], ],
"tagRenderings": [ "tagRenderings": [
{ {
"condition": "level=country",
"description": "The name of the country",
"id": "country_name", "id": "country_name",
"render": "{nameEn} {emojiFlag}" "description": "The name of the country",
"render": "{nameEn} {emojiFlag}",
"condition": "level=country"
}, },
{ {
"condition": "_community_links~*",
"description": "Community Links (Discord, meetups, Slack groups, IRC channels, mailing lists etc...)",
"id": "community_links", "id": "community_links",
"render": "{_community_links}" "description": "Community Links (Discord, meetups, Slack groups, IRC channels, mailing lists etc...)",
"render": "{_community_links}",
"condition": "_community_links~*"
} }
], ],
"filter": [ "filter": [

View file

@ -1898,6 +1898,46 @@
"render": { "render": {
"*": "{storage_all_tags()}" "*": "{storage_all_tags()}"
} }
},
{
"id": "debug_serviceworker_accordeon",
"render": {
"special": {
"header": "debug_serviceworker_accordeon_title",
"labels": "debug_serviceworker",
"type": "group"
}
},
"condition": "mapcomplete-show_debug=yes"
},
{
"id": "debug_serviceworker_accordeon_title",
"labels": [
"hidden"
],
"render": {
"en": "Debug information about the service worker"
}
},
{
"id": "expl",
"labels": [
"debug_serviceworker",
"hidden"
],
"render": {
"en": "To clear the service worker data, use the 'clear caches' button"
}
},
{
"id": "service_worker_tags",
"labels": [
"debug_serviceworker",
"hidden"
],
"render": {
"*": "{serviceworker_all_tags()}"
}
} }
], ],
"allowMove": false "allowMove": false

View file

@ -43,8 +43,6 @@
<script src="./src/Logic/Web/AndroidPolyfill.ts" type="module"></script> <script src="./src/Logic/Web/AndroidPolyfill.ts" type="module"></script>
<script type="module" src="./src/all_themes_index.ts"></script> <script type="module" src="./src/all_themes_index.ts"></script>
<script async src="./src/InstallServiceWorker.ts" type="module"></script>
</body> </body>
</html> </html>

View file

@ -335,6 +335,7 @@
"next": "Next", "next": "Next",
"noTagsSelected": "No tags selected", "noTagsSelected": "No tags selected",
"number": "number", "number": "number",
"offline": "Your device is offline",
"openTheMap": "Open the map", "openTheMap": "Open the map",
"openTheMapReason": "to view, edit and add information", "openTheMapReason": "to view, edit and add information",
"opening_hours": { "opening_hours": {

View file

@ -14095,6 +14095,9 @@
"debug_accordeon_title": { "debug_accordeon_title": {
"render": "Debug information" "render": "Debug information"
}, },
"debug_serviceworker_accordeon_title": {
"render": "Debug information about the service worker"
},
"debug_storage_accordeon_title": { "debug_storage_accordeon_title": {
"render": "Debug information about local storage" "render": "Debug information about local storage"
}, },
@ -14105,6 +14108,9 @@
} }
} }
}, },
"expl": {
"render": "To clear the service worker data, use the 'clear caches' button"
},
"fixate-north": { "fixate-north": {
"mappings": { "mappings": {
"0": { "0": {

View file

@ -1,7 +1,7 @@
import * as fs from "fs" import * as fs from "fs"
import Script from "./Script" import Script from "./Script"
function genImages(dryrun = false) { function genImages() {
console.log("Generating images") console.log("Generating images")
const dir = fs.readdirSync("./assets/svg") const dir = fs.readdirSync("./assets/svg")
for (const path of dir) { for (const path of dir) {
@ -64,7 +64,7 @@ class GenerateIncludedImages extends Script {
super("Converts all images from assets/svg into svelte-classes.") super("Converts all images from assets/svg into svelte-classes.")
} }
async main(args: string[]): Promise<void> { async main(): Promise<void> {
genImages() genImages()
} }
} }

View file

@ -8,7 +8,7 @@ import {
DoesImageExist, DoesImageExist,
PrevalidateTheme, PrevalidateTheme,
ValidateLayer, ValidateLayer,
ValidateThemeEnsemble, ValidateThemeEnsemble
} from "../src/Models/ThemeConfig/Conversion/Validation" } from "../src/Models/ThemeConfig/Conversion/Validation"
import { Translation } from "../src/UI/i18n/Translation" import { Translation } from "../src/UI/i18n/Translation"
import { OrderLayer, PrepareLayer } from "../src/Models/ThemeConfig/Conversion/PrepareLayer" import { OrderLayer, PrepareLayer } from "../src/Models/ThemeConfig/Conversion/PrepareLayer"
@ -19,7 +19,7 @@ import {
DesugaringStep, DesugaringStep,
Each, Each,
Fuse, Fuse,
On, On
} from "../src/Models/ThemeConfig/Conversion/Conversion" } from "../src/Models/ThemeConfig/Conversion/Conversion"
import { Utils } from "../src/Utils" import { Utils } from "../src/Utils"
import Script from "./Script" import Script from "./Script"
@ -182,7 +182,7 @@ class LayerBuilder extends Conversion<object, Map<string, LayerConfigJson>> {
return `./assets/layers/${id}/${id}.json` return `./assets/layers/${id}/${id}.json`
} }
writeLayer(layer: LayerConfigJson) { public writeLayer(layer: LayerConfigJson) {
if (layer.labels?.some((l) => this._labelBlacklist.has(l))) { if (layer.labels?.some((l) => this._labelBlacklist.has(l))) {
console.log("Not writing layer " + layer.id + ", censored") console.log("Not writing layer " + layer.id + ", censored")
return return
@ -191,6 +191,15 @@ class LayerBuilder extends Conversion<object, Map<string, LayerConfigJson>> {
if (!existsSync(LayerOverviewUtils.layerPath)) { if (!existsSync(LayerOverviewUtils.layerPath)) {
mkdirSync(LayerOverviewUtils.layerPath) mkdirSync(LayerOverviewUtils.layerPath)
} }
const usedImages = Lists.dedup(new ExtractImages(true, new Set(this._desugaringState.tagRenderings.keys()))
.convertStrict({ layers: [layer], id: "dummy", icon: undefined, title: undefined })
.map((x) => x.path))
usedImages.sort()
layer["_usedImages"] = usedImages
writeFileSync(LayerBuilder.targetPath(layer.id), JSON.stringify(layer, null, " "), { writeFileSync(LayerBuilder.targetPath(layer.id), JSON.stringify(layer, null, " "), {
encoding: "utf8", encoding: "utf8",
}) })

View file

@ -9,7 +9,7 @@ class PrepareServiceWorker extends Script {
} }
public async main() { public async main() {
const v = Constants.vNumber const v = Constants.vNumber + "-" + new Date().getTime()
writeFileSync("./src/service-worker/SWGenerated.ts", writeFileSync("./src/service-worker/SWGenerated.ts",
["export class SWGenerated {", ["export class SWGenerated {",
"// generated by scripts/prepareServiceWorker.ts", "// generated by scripts/prepareServiceWorker.ts",

View file

@ -1,13 +1,17 @@
export {} export class InstallServiceWorker {
window.addEventListener("load", async () => {
static async installServiceWorker() {
if (!("serviceWorker" in navigator)) { if (!("serviceWorker" in navigator)) {
console.log("Service workers are not supported") throw ("Service workers are not supported")
return
} }
try {
await navigator.serviceWorker.register("/service-worker.js", { type: "module" }) await navigator.serviceWorker.register("/service-worker.js", { type: "module" })
console.log("Service worker registration successful") console.log("Service worker registration successful")
} catch (err) {
console.error("Service worker registration failed", err)
} }
})
static async precache(assets: string[]) {
if (assets?.length > 0) {
await fetch("./service-worker/precache?assets=" + assets.join(";"))
}
}
}

View file

@ -8,6 +8,7 @@ import Constants from "../../Models/Constants"
import { Feature, Point } from "geojson" import { Feature, Point } from "geojson"
import { AndroidPolyfill } from "../Web/AndroidPolyfill" import { AndroidPolyfill } from "../Web/AndroidPolyfill"
import { QueryParameters } from "../Web/QueryParameters" import { QueryParameters } from "../Web/QueryParameters"
import { IsOnline } from "../Web/IsOnline"
interface OsmUserInfo { interface OsmUserInfo {
id: number id: number
@ -131,6 +132,7 @@ export class OsmConnection {
* Details of the currently logged-in user; undefined if not logged in * Details of the currently logged-in user; undefined if not logged in
*/ */
public userDetails: UIEventSource<UserDetails | undefined> public userDetails: UIEventSource<UserDetails | undefined>
public isLoggedIn: Store<boolean> public isLoggedIn: Store<boolean>
public gpxServiceIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>( public gpxServiceIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
"unknown" "unknown"
@ -182,7 +184,7 @@ export class OsmConnection {
this._oauth_config.oauth_secret = import.meta.env.VITE_OSM_OAUTH_SECRET this._oauth_config.oauth_secret = import.meta.env.VITE_OSM_OAUTH_SECRET
} }
this.userDetails = new UIEventSource<UserDetails>(undefined, "userDetails") this.userDetails = UIEventSource.asObject<UserDetails>(LocalStorageSource.get("user_details"), undefined)
if (options.fakeUser) { if (options.fakeUser) {
const ud = this.userDetails.data const ud = this.userDetails.data
ud.csCount = 5678 ud.csCount = 5678
@ -197,13 +199,7 @@ export class OsmConnection {
} }
this.updateCapabilities() this.updateCapabilities()
this.isLoggedIn = this.userDetails.map( this.isLoggedIn = this.userDetails.map((user) => !!user)
(user) =>
!!user &&
(this.apiIsOnline.data === "unknown" || this.apiIsOnline.data === "online"),
[this.apiIsOnline]
)
this._dryRun = options.dryRun ?? new UIEventSource<boolean>(false) this._dryRun = options.dryRun ?? new UIEventSource<boolean>(false)
if (options?.shared_cookie) { if (options?.shared_cookie) {
@ -284,6 +280,9 @@ export class OsmConnection {
} }
public async AttemptLogin() { public async AttemptLogin() {
if (!IsOnline.isOnline.data) {
return
}
this.updateCapabilities() this.updateCapabilities()
if (this.loadingStatus.data !== "logged-in") { if (this.loadingStatus.data !== "logged-in") {
this.loadingStatus.setData("loading") this.loadingStatus.setData("loading")
@ -308,6 +307,9 @@ export class OsmConnection {
} }
private async loadUserInfo() { private async loadUserInfo() {
if (!IsOnline.isOnline.data) {
return
}
try { try {
const result = await this.interact("user/details.json") const result = await this.interact("user/details.json")
if (result === null) { if (result === null) {

View file

@ -14,10 +14,15 @@
osmConnection: OsmConnection osmConnection: OsmConnection
featureSwitches?: { featureSwitchEnableLogin?: UIEventSource<boolean> } featureSwitches?: { featureSwitchEnableLogin?: UIEventSource<boolean> }
} }
/**
* Do show this element when in offline mode
*/
export let offline = false
/** /**
* If set, 'loading' will act as if we are already logged in. * If set, 'loading' will act as if we are already logged in.
*/ */
export let ignoreLoading: boolean = false export let ignoreLoading: boolean = offline // If it works in offline mode, it'll work while we are logging in too
/** /**
* If set and the OSM-api fails, do _not_ show any error messages nor the successful state, just hide. * If set and the OSM-api fails, do _not_ show any error messages nor the successful state, just hide.
* Will still show the "not-logged-in"-slot * Will still show the "not-logged-in"-slot
@ -32,23 +37,26 @@
unknown: t.loginFailedUnreachableMode, unknown: t.loginFailedUnreachableMode,
readonly: t.loginFailedReadonlyMode, readonly: t.loginFailedReadonlyMode,
} }
const apiState: Store<string> = const apiState: Store<OsmServiceState> =
state?.osmConnection?.apiIsOnline ?? new ImmutableStore<OsmServiceState>("online") state?.osmConnection?.apiIsOnline ?? new ImmutableStore<OsmServiceState>("online")
const online = IsOnline.isOnline const online = IsOnline.isOnline
let loggedIn = state?.osmConnection?.isLoggedIn
</script> </script>
{#if $badge} {#if $badge}
{#if !$online} {#if !$online && !offline}
{#if !hiddenFail} {#if !hiddenFail}
<div class="alert"> <div class="alert">
Your device is offline <Tr t={t.offline} />
</div> </div>
{/if} {/if}
{:else if !ignoreLoading && !hiddenFail && $loadingStatus === "loading"} {:else if !ignoreLoading && !hiddenFail && $loadingStatus === "loading"}
<slot name="loading"> <slot name="loading">
<Loading /> <Loading />
</slot> </slot>
{:else if ($loadingStatus === "error" || $apiState === "readonly" || $apiState === "offline")} {:else if $loggedIn}
<slot />
{:else if ($loadingStatus === "error" || $apiState === "readonly" || $apiState === "offline" || $apiState === "unreachable")}
{#if !hiddenFail} {#if !hiddenFail}
<slot name="error"> <slot name="error">
<div class="alert flex flex-col items-center"> <div class="alert flex flex-col items-center">
@ -63,8 +71,7 @@
</div> </div>
</slot> </slot>
{/if} {/if}
{:else if $loadingStatus === "logged-in"}
<slot />
{:else if $loadingStatus === "not-attempted"} {:else if $loadingStatus === "not-attempted"}
<slot name="not-logged-in" /> <slot name="not-logged-in" />
{/if} {/if}

View file

@ -77,11 +77,9 @@
let usersettingslayer = new LayerConfig(<LayerConfigJson>usersettings, "usersettings", true) let usersettingslayer = new LayerConfig(<LayerConfigJson>usersettings, "usersettings", true)
let theme = state.theme
let featureSwitches = state.featureSwitches let featureSwitches = state.featureSwitches
let showHome = featureSwitches?.featureSwitchBackToThemeOverview let showHome = featureSwitches?.featureSwitchBackToThemeOverview
let pg = state.guistate.pageStates let pg = state.guistate.pageStates
let location = state.mapProperties?.location
export let onlyLink: boolean export let onlyLink: boolean
const t = Translations.t.general.menu const t = Translations.t.general.menu
let shown = new UIEventSource(state.guistate.pageStates.menu.data || !onlyLink) let shown = new UIEventSource(state.guistate.pageStates.menu.data || !onlyLink)
@ -133,7 +131,7 @@
<!-- User related: avatar, settings, favourits, logout --> <!-- User related: avatar, settings, favourits, logout -->
<SidebarUnit> <SidebarUnit>
<LoginToggle {state}> <LoginToggle {state} offline>
<LoginButton osmConnection={state.osmConnection} slot="not-logged-in" /> <LoginButton osmConnection={state.osmConnection} slot="not-logged-in" />
<div class="flex items-center gap-x-4 w-full m-2"> <div class="flex items-center gap-x-4 w-full m-2">
{#if $userdetails.img} {#if $userdetails.img}
@ -143,7 +141,7 @@
{/if} {/if}
<div class="flex flex-col w-full gap-y-2"> <div class="flex flex-col w-full gap-y-2">
<b>{$userdetails.name}</b> <b>{$userdetails?.name ?? '<Username>'}</b>
<LogoutButton clss="as-link small subtle text-sm" osmConnection={state.osmConnection} /> <LogoutButton clss="as-link small subtle text-sm" osmConnection={state.osmConnection} />
</div> </div>
</div> </div>
@ -281,7 +279,7 @@
<Tr t={Translations.t.inspector.menu} /> <Tr t={Translations.t.inspector.menu} />
</a> </a>
{#if !state.theme} {#if !state?.theme}
<a class="flex" href={($isAndroid ? "https://mapcomplete.org" : ".") +`/statistics.html`} <a class="flex" href={($isAndroid ? "https://mapcomplete.org" : ".") +`/statistics.html`}
target="_blank"> target="_blank">
<ChartBar class="h-6 w-6" /> <ChartBar class="h-6 w-6" />

View file

@ -4,7 +4,7 @@
function clearCaches() { function clearCaches() {
IdbLocalStorage.clearAll() IdbLocalStorage.clearAll()
Utils.download("./service-worker-clear") Utils.download("./service-worker/clear_caches.json")
window.location.reload() window.location.reload()
} }
export let msg: string export let msg: string

View file

@ -128,7 +128,7 @@
{layer} {layer}
extraClasses="my-2" extraClasses="my-2"
/> />
{#if (!editingEnabled || $editingEnabled) && $apiState !== "readonly" && $apiState !== "offline"} {#if !editingEnabled || $editingEnabled}
<EditButton <EditButton
arialabel={config.editButtonAriaLabel} arialabel={config.editButtonAriaLabel}
ariaLabelledBy={answerId} ariaLabelledBy={answerId}

View file

@ -359,7 +359,7 @@
{#if question !== undefined && $apiState !== "readonly" && $apiState !== "offline"} {#if question !== undefined && $apiState !== "readonly" && $apiState !== "offline"}
<div class={clss}> <div class={clss}>
{#if layer?.isNormal()} {#if layer?.isNormal()}
<LoginToggle {state}> <LoginToggle {state} offline hiddenFail>
<DotMenu hideBackground={true} open={menuIsOpened}> <DotMenu hideBackground={true} open={menuIsOpened}>
<SidebarUnit> <SidebarUnit>
{#if $disabledInTheme.indexOf(config.id) >= 0} {#if $disabledInTheme.indexOf(config.id) >= 0}
@ -538,7 +538,7 @@
{/if} {/if}
<!-- Save and cancel buttons, in a logintoggle --> <!-- Save and cancel buttons, in a logintoggle -->
<LoginToggle {state} ignoreLoading> <LoginToggle {state} ignoreLoading offline>
<div class="flex w-full justify-end" slot="not-logged-in"> <div class="flex w-full justify-end" slot="not-logged-in">
{#if config.alwaysForceSaveButton} {#if config.alwaysForceSaveButton}
<button <button

View file

@ -13,6 +13,8 @@
import { WithSearchState } from "../Models/ThemeViewState/WithSearchState" import { WithSearchState } from "../Models/ThemeViewState/WithSearchState"
import ThemeConfig from "../Models/ThemeConfig/ThemeConfig" import ThemeConfig from "../Models/ThemeConfig/ThemeConfig"
import { AndroidPolyfill } from "../Logic/Web/AndroidPolyfill" import { AndroidPolyfill } from "../Logic/Web/AndroidPolyfill"
import { InstallServiceWorker } from "../InstallServiceWorker"
import type { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
function webgl_support() { function webgl_support() {
try { try {
@ -51,6 +53,26 @@
let availableLayers = UIEventSource.fromPromise(getAvailableLayers()) let availableLayers = UIEventSource.fromPromise(getAvailableLayers())
const state = new WithSearchState(theme, availableLayers) const state = new WithSearchState(theme, availableLayers)
InstallServiceWorker.installServiceWorker().then(() => {
if (theme.source._usedImages) {
window.requestIdleCallback(() => {
InstallServiceWorker.precache(theme.source._usedImages?.filter(i => i.startsWith("./")))
})
}
for (const layer of (<LayerConfigJson[]>theme.source.layers)) {
if (!Constants.isPriviliged(layer)) {
continue
}
if (!layer["_usedImages"]) {
continue
}
// The priviliged layers, which are injected, might have assets not included in the '_usedImages' of the theme
window.requestIdleCallback(() => {
InstallServiceWorker.precache(layer["_usedImages"]?.filter(i => i.startsWith("./")))
})
}
}).catch(e => console.error("Could not install service worker:", e))
</script> </script>
{#if !webgl_supported} {#if !webgl_supported}

View file

@ -79,7 +79,7 @@ class GpsAllTags extends SpecialVisualizationSvelte {
} }
} }
class StorageAlLTags extends SpecialVisualizationSvelte { class StorageAllTags extends SpecialVisualizationSvelte {
funcName = "storage_all_tags" funcName = "storage_all_tags"
group = "settings" group = "settings"
docs = "Shows the current state of storage" docs = "Shows the current state of storage"
@ -103,6 +103,27 @@ class StorageAlLTags extends SpecialVisualizationSvelte {
} }
} }
class ServiceWorkerAllTags extends SpecialVisualizationSvelte {
funcName = "serviceworker_all_tags"
group = "settings"
docs = "Shows the current state of service worker"
args = []
constr(state: SpecialVisualizationState): SvelteUIElement {
const data = {}
for (const key in localStorage) {
data[key] = localStorage[key]
}
const tags = UIEventSource.fromPromise<Record<string, any>>(
Utils.downloadJson(
"./service-worker/status.json")
)
return new SvelteUIElement(AllTagsPanel, { state, tags })
}
}
export class ClearCachesVis extends SpecialVisualizationSvelte { export class ClearCachesVis extends SpecialVisualizationSvelte {
funcName = "clear_caches" funcName = "clear_caches"
docs = docs =
@ -234,7 +255,8 @@ export class SettingsVisualisations {
new DisabledQuestionsVis(), new DisabledQuestionsVis(),
new GyroscopeAllTags(), new GyroscopeAllTags(),
new GpsAllTags(), new GpsAllTags(),
new StorageAlLTags(), new StorageAllTags(),
new ServiceWorkerAllTags(),
new ClearCachesVis(), new ClearCachesVis(),
new LoginButtonVis(), new LoginButtonVis(),
new QrLogin(), new QrLogin(),

View file

@ -1,5 +1,6 @@
import { QueryParameters } from "./Logic/Web/QueryParameters" import { QueryParameters } from "./Logic/Web/QueryParameters"
import AllThemesGui from "./UI/AllThemesGui.svelte" import AllThemesGui from "./UI/AllThemesGui.svelte"
import { InstallServiceWorker } from "./InstallServiceWorker"
const theme = QueryParameters.GetQueryParameter("layout", undefined).data ?? "" const theme = QueryParameters.GetQueryParameter("layout", undefined).data ?? ""
const customLayout = QueryParameters.GetQueryParameter("userlayout", undefined).data ?? "" const customLayout = QueryParameters.GetQueryParameter("userlayout", undefined).data ?? ""
@ -26,7 +27,7 @@ if (theme !== "") {
l.protocol + "//" + window.location.host + "/theme.html" + l.search + l.hash l.protocol + "//" + window.location.host + "/theme.html" + l.search + l.hash
) )
} }
InstallServiceWorker.installServiceWorker().catch(e => console.error(e))
new AllThemesGui({ new AllThemesGui({
target: document.getElementById("main"), target: document.getElementById("main"),
}) })

View file

@ -1,4 +1,4 @@
export class SWGenerated { export class SWGenerated {
// generated by scripts/prepareServiceWorker.ts // generated by scripts/prepareServiceWorker.ts
static vNumber = "0.54.4" static vNumber = "0.54.6-1754230245885"
} }

View file

@ -37,6 +37,14 @@ async function listCachedRequests(): Promise<string[]> {
return requests.map(req => req.url) return requests.map(req => req.url)
} }
async function clearCaches(): Promise<void> {
const cache = await caches.open(SWGenerated.vNumber)
const keys = await cache.keys()
for (const key of keys) {
await cache.delete(key)
}
}
class Router { class Router {
private readonly _endpoints: Record<string, (event: FetchEvent) => void> private readonly _endpoints: Record<string, (event: FetchEvent) => void>
private readonly _subpaths: Record<string, (event: FetchEvent, rest: string) => void> private readonly _subpaths: Record<string, (event: FetchEvent, rest: string) => void>
@ -71,12 +79,33 @@ class Router {
const allOffline = new Router({ const allOffline = new Router({
"clear_caches.Json": (event) => {
event.respondWith(
clearCaches().then(() =>
jsonResponse({ status: "ok" })
)
)
},
"precache": (event) => {
const url = new URL(event.request.url)
const assets = url.searchParams.get("assets")?.split(";")
if (assets) {
console.log("Precaching:", assets)
event.waitUntil(caches.open(SWGenerated.vNumber).then(cache =>
cache.addAll(assets)
))
}
event.respondWith(jsonResponse({ status: "ok" }))
},
"status.json": (event) => { "status.json": (event) => {
event.respondWith( event.respondWith(
listCachedRequests().then(cached => listCachedRequests().then(cached =>
jsonResponse( jsonResponse(
{ {
status: "ok", cached, status: "ok",
vnumber: SWGenerated.vNumber,
domain: selfDomain,
cached
} }
)) ))
) )
@ -89,7 +118,6 @@ self.addEventListener("fetch", (event) => {
if (url.endsWith("/service-worker.js")) { if (url.endsWith("/service-worker.js")) {
return // Installation of a new version, we don't interfere return // Installation of a new version, we don't interfere
} }
console.log("Intercepting event", event.request.url)
if (url.indexOf("/service-worker/") >= 0) { if (url.indexOf("/service-worker/") >= 0) {
allOffline.route(event) allOffline.route(event)
return return
@ -99,6 +127,11 @@ self.addEventListener("fetch", (event) => {
respondFromCache(event) respondFromCache(event)
return return
} }
if (urlObj.hostname === "data.mapcomplete.org") {
respondFromCache(event)
return
}
}) })
self.addEventListener("install", () => self.skipWaiting()) self.addEventListener("install", () => self.skipWaiting())