Some Simple Podcast Player
Fleetingsome simple podcast tool, very specialized to my stack.
For audiobooks, prefer using Voice Audiobook Player, much more attractive (I think).
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="manifest" href="./podcastmanifest.json">
<script type="module" src="https://ipfs.konubinix.eu/bafkreiap4qt2hkswcidxgqnzdt4irhslfc6kbpa2bw2lscz7zsd3gwr7mu?filename=yjs.js"></script>
<script type="module">
import { Doc, WebsocketProvider, IndexeddbPersistence } from 'http://localhost:9682/ipfs/bafkreiap4qt2hkswcidxgqnzdt4irhslfc6kbpa2bw2lscz7zsd3gwr7mu?filename=yjs.js'
const doc = new Doc();
const ymap = doc.getMap("state")
const wsProvider = new WebsocketProvider(
'ws://192.168.2.14:9905', 'podcast',
doc,
)
const indexeddb = new IndexeddbPersistence('podcast', doc)
// I can export the Doc so that it will be used in the dom
window.doc = doc
window.ymap = ymap
window.wsProvider = wsProvider
window.indexeddb = indexeddb
</script>
<script defer src="https://ipfs.konubinix.eu/bafkreidregenuw7nhqewvimq7p3vwwlrqcxjcrs4tiocrujjzggg26gzcu?orig=https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.x.x/dist/cdn.min.js"></script>
<script defer src="https://ipfs.konubinix.eu/bafkreighmyou4lhqizpdzvutdeg6xnpvskwhfxgez7tfawason3hkwfspm?orig=https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
<script defer src="https://ipfs.konubinix.eu/bafkreic33rowgvvugzgzajagkuwidfnit2dyqyn465iygfs67agsukk24i?orig=https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="https://ipfs.konubinix.eu/bafybeihp5kzlgqt56dmy5l4z7kpymfc4kn3fnehrrtr7cid7cn7ra36yha?orig=https://cdn.tailwindcss.com/3.4.3"></script>
[[wip_ipfsdocs_slider_alpinejs.org:toast-code-ex()]]
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('./swpodcast.js')
.then(function() {
console.log('Service Worker Registered');
})
.catch(error => {
console.error('Service Worker registration failed:', error);
alert(`Service Worker registration failed: ${error}`);
});
}
function formatTime(seconds) {
// remove the milliseconds
seconds = Math.floor(seconds)
// Calculate hours, minutes, and remaining seconds
let hours = Math.floor(seconds / 3600);
let minutes = Math.floor((seconds % 3600) / 60);
let remainingSeconds = seconds % 60;
// Create an array to store parts of the time
let parts = [];
// Add hours if its greater than 0
if (hours > 0) {
parts.push(hours.toString().padStart(2, '0'));
}
// Always add minutes and seconds
parts.push(minutes.toString().padStart(2, '0'));
parts.push(remainingSeconds.toString().padStart(2, '0'));
// Join the parts with ':' and return
return parts.join(':');
}
document.addEventListener('alpine:init', () => {
Alpine.data('app', () => ({
src: "podcasts.json",
async init_app () {
try{
wsProvider.on('status', event => {
info(`WS: ${event.status}`) ;
})
indexeddb.on('synced', () => {
info(`IDB: ready`) ;
})
wsProvider.on('sync', event => {
// something new from outside
});
ymap.observe(ymapEvent => {
// set the values, such as show_old,
window.event = ymapEvent
for(const keychanged of ymapEvent.keysChanged) {
// info(`Setting: ${keychanged}`)
this[keychanged] = ymap.get(keychanged)
}
});
// no-store because the browser cache will hide changes in
// the file. This is dealt with by the service worker
// anyway
res = await fetch(`./${this.src}`, {cache: 'no-store'})
this.dirs = await res.json()
var lastpodcast
var lastdir
var keepcurrent = false
var keeplastdir
var keeplastpodcast
for(const dir in this.dirs) {
lastdir = this.dir_last_episode[dir]
keeplastdir = false
for(const podcast of this.dirs[dir]){
lastpodcast = this.podcast_last_episode[podcast.name]
keeplastpodcast = false
for(const episode of podcast.episodes){
if(lastdir && episode.url === lastdir.episode.url){
keeplastdir = true
}
if(lastpodcast && episode.url === lastpodcast.episode.url){
keeplastpodcast = true
}
if(this.current_episode && this.current_episode.url === episode.url) {
keepcurrent = true
}
}
if(lastpodcast && !keeplastpodcast) {
delete this.podcast_last_episode[podcast.name]
this.podcast_last_episode = {...this.podcast_last_episode}
}
}
if(lastdir && !keeplastdir) {
delete this.dir_last_episode[dir]
this.dir_last_episode = {...this.dir_last_episode}
}
}
if(! keepcurrent) {
this.current_episode = null
}
}
catch(err)
{
error(err)
}
},
dirs: undefined,
show_old: false, // Alpine.$persist(false).as("show_old"),
podcast_last_episode: {}, // Alpine.$persist({}).as("podcast_last_episode3"),
dir_last_episode: {}, // Alpine.$persist({}).as("dir_last_episode"),
current_speed: null,
current_progress: {cur: 0, duration: 0},
current_dir: null, // Alpine.$persist(null).as("dir"),
current_episode: null, // Alpine.$persist(null).as("episode"),
current_podcast: null, // Alpine.$persist(null).as("podcast"),
current_playing: false,
sync_state: {}, // Alpine.$persist({}).as("sync_state2"),
key(stuff) {
return `${stuff}_${this.current_episode.url}`
},
async goto_next() {
localStorage.setItem(this.key('time'), 0)
this.sync_state[this.key('time')] = 0
let pos_current_episode = this.current_podcast.episodes.findIndex(
(episode) => episode.url === this.current_episode.url
)
if(pos_current_episode + 1 === this.current_podcast.episodes.length) {
localStorage.setItem(this.current_podcast.name + "_looped_first_episode", this.current_podcast.episodes[0].url)
this.sync_state[this.current_podcast.name + "_looped_first_episode"] = this.current_podcast.episodes[0].url
}
this.current_episode = null
await this.$nextTick() // give time to the player to disappear
this.current_episode = this.current_podcast.episodes[(pos_current_episode + 1) % this.current_podcast.episodes.length]
await this.$nextTick() // give time to the player to appear
},
async goto_prev() {
localStorage.setItem(this.key('time'), 0)
this.sync_state[this.key('time')] = 0
let pos_current_episode = this.current_podcast.episodes.findIndex(
(episode) => episode.url === this.current_episode.url
)
this.current_episode = null
await this.$nextTick() // give time to the player to disapear
this.current_episode = this.current_podcast.episodes[(pos_current_episode - 1 + this.current_podcast.episodes.length) % this.current_podcast.episodes.length]
await this.$nextTick() // give time to the player to appear
},
localstoragegetter(key) {
//return localStorage.getItem(key)
return this.sync_state[key]
}
}))
})
</script>
</head>
<body x-data="app" x-init="init_app"
x-effect.podcast_last_episode="ymap.set('podcast_last_episode', podcast_last_episode)"
x-effect.dir_last_episode="ymap.set('dir_last_episode', dir_last_episode)"
x-effect.show_old="ymap.set('show_old', show_old)"
x-effect.current_episode="ymap.set('current_episode', current_episode)"
x-effect.current_podcast="ymap.set('current_podcast', current_podcast)"
x-effect.current_dir="ymap.set('current_dir', current_dir)"
x-effect.sync-state="ymap.set('sync_state', sync_state)"
@gotonext="
if($event.detail.playing){
current_playing = true
}
await goto_next()
"
@gotoprev="
if($event.detail.playing){
current_playing = true
}
await goto_prev()
"
>
<div id="listings" class="h-screen flex flex-col">
<div id="podcasts" class="flex-grow border-red-100 border-4 overflow-scroll"
x-effect.save-old-podcats="
if(current_podcast && current_episode && current_dir){
let old = {...podcast_last_episode}
old[current_podcast.name] = {episode: current_episode, dir: current_dir}
podcast_last_episode = old
old = {...dir_last_episode}
old[current_dir] = {episode: current_episode, podcast: current_podcast}
dir_last_episode = old
}
"
>
<template x-for="(podcasts, dir) in dirs">
<div class="dir"
x-data="{extended: false}">
<h1
:class="{
'bg-yellow-100': current_dir === dir
}"
class="rounded" @click="extended = !extended">
<span class="w-8 inline-block text-right" x-text="extended ? '↓' : '→'"></span>
<span class="text-2xl" x-text="dir"></span>
<button class="text-sm"
@click.stop="
current_podcast = podcasts[0]
current_dir = dir
current_episode = null // to trigger reload
await $nextTick()
current_episode = podcasts[0].episodes[0]
localStorage.setItem(`time_${current_episode.url}`, 0)
sync_state[`time_${current_episode.url}`] = 0
await $nextTick() // wait for it to be ready
$dispatch('pleaseplay')
$dispatch('pleaserewind')">▶</button>
<button class="text-sm" x-show="dir in dir_last_episode"
@click.stop="
let podcast = dir_last_episode[dir].podcast
let episode = dir_last_episode[dir].episode
current_podcast = podcast
current_dir = dir
current_episode = null // to trigger reload
await $nextTick()
current_episode = episode
await $nextTick() // wait for it to be ready
$dispatch('pleaseplay')
$dispatch('pleaserewind')"
>resume</button> <span class="text-xs" x-text="dir_last_episode[dir] && dir_last_episode[dir].podcast.name"></span>
</h1>
<div x-collapse x-show="extended" class="border-2 border-yellow-100 rounded">
<template x-for="podcast in podcasts">
<div class="podcast"
x-data="{extended: false}"
x-show="show_old || localstoragegetter(podcast.name + '_looped_first_episode') !== podcast.episodes[0].url"
>
<h2
:class="{
'bg-blue-100': current_podcast && current_podcast.name === podcast.name,
'text-red-100': localstoragegetter(podcast.name + '_looped_first_episode') === podcast.episodes[0].url,
'font-bold': dir_last_episode[dir] && (podcast.name === dir_last_episode[dir].podcast.name),
} "
@click="extended = !extended"
class="rounded pl-8"
>
<span class="w-8 inline-block text-right" x-text="extended ? '↓' : '→'"></span>
<span x-text="podcast.name" class="text-lg"></span>
<button class="text-sm"
@click.stop="
current_podcast = podcast
current_dir = dir
current_episode = null // to trigger reload
await $nextTick()
current_episode = podcast.episodes[0]
localStorage.setItem(`time_${current_episode.url}`, 0)
sync_state[`time_${current_episode.url}`] = 0
await $nextTick() // wait for it to be ready
$dispatch('pleaseplay')
$dispatch('pleaserewind')">▶</button>
<button class="text-sm" x-show="podcast.name in podcast_last_episode"
@click.stop="
let dir = podcast_last_episode[podcast.name].dir
let episode = podcast_last_episode[podcast.name].episode
current_dir = dir
current_podcast = podcast
current_episode = null // to trigger reload
await $nextTick()
current_episode = episode
await $nextTick() // wait for it to be ready
$dispatch('pleaseplay')
$dispatch('pleaserewind')">resume</button>
</h2>
<div id="episodes" class="bg-slate-100 border-2 overflow-x-scroll" x-collapse x-show="extended">
<template x-for="(episode, index) in podcast.episodes">
<div id="episode"
@dblclick="current_episode = null ; await $nextTick() ; current_episode = episode ; current_podcast = podcast ; current_dir = dir ; current_playing = true"
:class="{
'bg-green-100': current_episode && current_episode.url === episode.url,
'text-red-100': localstoragegetter(podcast.name + '_looped_first_episode') === podcast.episodes[0].url,
'font-bold': podcast.name in podcast_last_episode && podcast_last_episode[podcast.name].episode.url === episode.url,
}"
class="rounded w-fit text-nowrap"
>
<div>
<span x-show="episode.album">
<span x-text="episode.album"></span>:
<span x-text="episode.index"></span> -
</span>
<span x-text="episode.name"></span><span x-text="episode.date ? ' - ' + episode.date : ''"></span>
</div>
</div>
</template>
</div>
</div>
</template>
</div>
</div>
</template>
</div>
<div id="countdown"
x-show='current_progress.duration'
x-data="{motionreplaytimer: null, countdown: null, remaining_time: 0, time: $persist(1),
start() {
this.$dispatch('pleaseplay')
this.remaining_time = this.time * 60
if(this.countdown)
{
clearInterval(this.countdown)
this.countdown = null
}
this.countdown = setInterval(() => {
this.remaining_time-=1
if(this.remaining_time == 4)
{
this.$dispatch('pleasevolume', {volume: 0.8})
}
if(this.remaining_time == 3)
{
this.$dispatch('pleasevolume', {volume: 0.6})
}
if(this.remaining_time == 2)
{
this.$dispatch('pleasevolume', {volume: 0.4})
}
if(this.remaining_time == 1)
{
this.$dispatch('pleasevolume', {volume: 0.2})
}
if(this.remaining_time <= 0){
this.$dispatch('pleasepause')
this.$dispatch('pleaserewind')
this.remaining_time = 0
clearInterval(this.countdown)
this.countdown=null
if(this.motionreplaytimer){clearTimeout(this.motionreplaytimer)}
this.motionreplaytimer = setTimeout(() => {
info('end of time to shake to play')
this.motionreplaytimer = null
this.$dispatch('pleaserewind', {time: 50}) // I most likely missed the end
}, 10000)
}
}, 1000)
},
}" class="w-full flex"
@devicemotion.window="let motion = (
$event.acceleration.x * $event.acceleration.x
+ $event.acceleration.y * $event.acceleration.y
+ $event.acceleration.z * $event.acceleration.z
)
if(motion > 400 && motionreplaytimer){
clearTimeout(motionreplaytimer)
motionreplaytimer = null
info('shaked, again!!')
start()
}
"
>
<input type="number" class="w-8" x-model="time"/>
<input type="number" disabled class="w-8" x-model="remaining_time"/>
<input type="range" min="0" disabled :max="time * 60" :value="time * 60 - remaining_time" class="flex-grow"/>
<button @click="
if(countdown){
clearInterval(countdown)
countdown = null
$dispatch('pleasepause')
$dispatch('pleaserewind')
remaining_time = 0
}
else
{
start()
}
"
x-text="countdown ? 'stop' : 'start'"></button>
</div>
<template x-if="current_progress.duration">
<div id="controls" x-show="current_episode && dirs">
<span id="controls" class="flex place-content-evenly text-3xl">
<button @click="$dispatch('gotoprev')">⏮</button>
<button @click="$dispatch('pleaserewind')">⏪</button>
<button @click="$dispatch(current_playing ? 'pleasepause' : 'pleaseplay')" x-text="current_playing ? '⏸' : '▶'"></button>
<button @click="$dispatch('pleaseffwd')">⏩</button>
<button @click="$dispatch('gotonext')">⏭</button>
<input type="checkbox" x-model="show_old"/>
<span class="text-sm"
x-data="{
disabled: true,
disabled_timer: null,
async set_timer () {
if(this.disabled_timer !== null){
clearTimeout(this.disabled_timer)
}
this.disabled_timer = setTimeout(() => {this.disabled = true; this.disabled_timer = null}, 5000)
},
}"
@dblclick="disabled = !disabled ; if(! disabled) {await set_timer()} else if (disabled_timer !== null) {clearTimeout(disabled_timer) ; disabled_timer=null;}"
>
<span class="w-8 text-right inline-block" x-text="current_speed"></span>:
<input class="align-middle"
type="range"
x-effect.follow-current-episode="if(current_episode){
current_speed = localstoragegetter(key('rate')) || 1
}"
:value="current_speed"
:disabled="disabled"
@input="$dispatch('pleasesetspeed', {speed: $el.value}) ; await set_timer()"
min="0.25" max="4" step="0.25"/>
</span>
</span>
<span id="progress" class="text-sm w-full flex flex-row"
x-data="{
disabled: true,
disabled_timer: null,
async set_timer () {
if(this.disabled_timer !== null){
clearTimeout(this.disabled_timer)
}
this.disabled_timer = setTimeout(() => {this.disabled = true; this.disabled_timer = null}, 5000)
},
}"
@dblclick="disabled = !disabled ; if(! disabled) {await set_timer()} else if (disabled_timer !== null) {clearTimeout(disabled_timer) ; disabled_timer=null;}"
>
<span>
<span x-text="formatTime(current_progress.cur)"></span>/<span x-text="formatTime(current_progress.duration)"></span>
</span>
<input
type="range"
class="flex-grow"
min="0" :max="current_progress.duration"
:disabled="disabled"
:value="current_progress.cur"
@input="$dispatch('setcurrenttime', {value: $el.value}) ; await set_timer()"
/>
</span>
</div>
</template>
</div>
<div id="player"
class="flex justify-center bg-black"
x-data="{
init_values() {
let cur = localstoragegetter(key('time'))
if(cur)
{
this.$el.currentTime = cur
}
let episode_rate = localstoragegetter(key('rate'))
if(episode_rate)
{
this.$el.playbackRate = parseFloat(episode_rate)
}
else
{
let podcast_rate = localstoragegetter(`rate_${current_podcast.name}`)
if(podcast_rate)
{
this.$el.playbackRate = parseFloat(podcast_rate)
}
}
window.v = this.$el
},
onended() {
localStorage.setItem(this.key('time'), 0)
sync_state[this.key('time')] = 0
this.$dispatch('gotonext', {playing: true})
},
medium: {
async ['x-init']() {
this.init_values($el)
if('mediaSession' in navigator) {
var artist = ''
if(this.current_episode.index !== null)
{
artist += `${this.current_episode.index}`
}
if(this.current_episode.number !== null)
{
artist += `/${this.current_episode.number}`
}
if(artist !== '')
{
artist += ' '
}
artist += this.current_podcast.name
navigator.mediaSession.metadata = new MediaMetadata({
title: this.current_episode.name,
artist: artist,
album: this.current_episode.album || this.current_podcast.name,
});
navigator.mediaSession.setActionHandler('nexttrack', () => {this.$dispatch('gotonext', {playing: true})});
navigator.mediaSession.setActionHandler('previoustrack', () => {this.$dispatch('gotoprev', {playing: true})});
navigator.mediaSession.playbackState = 'paused'
}
},
['@pleasesetspeed.window']() {
this.$el.playbackRate = this.$event.detail.speed
},
['@pleasescroll.window']() {
this.$el.scrollIntoView()
},
['@canplay']() {
if(this.current_playing) {
this.$el.play()
}
this.current_progress = {cur: this.$el.currentTime, duration: this.$el.duration}
},
['@play']() {
this.$el.volume = 1
this.current_playing = true
},
['@pleaseplay.window'](){
this.$el.play()
},
['@pleasevolume.window'](){
this.$el.volume = this.$event.detail.volume
},
['@pleasepause.window'](){
this.$el.pause()
},
['@pleaseffwd.window'](){
this.$el.currentTime += 10
},
['@pleaserewind.window'](){
this.$el.currentTime -= this.$event.detail.time || 10
},
['@pause.window'](){
this.$el.pause()
},
async ['@loadeddata'](){
await $nextTick()
this.$el.scrollIntoView()
},
['@pause']() {
this.current_playing = false
if(this.$el.currentTime === this.$el.duration)
{
this.onended()
}
},
['@ended']() {
// this is not triggerd for audio in qutebrowser
},
['@timeupdate'](){
localStorage.setItem(this.key('time'), this.$el.currentTime)
sync_state[this.key('time')] = this.$el.currentTime
this.current_progress = {cur: this.$el.currentTime, duration: this.$el.duration}
},
['@setcurrenttime.window']() {
this.$el.currentTime = this.$event.detail.value
},
['@ratechange'](){
localStorage.setItem(this.key('rate'), this.$el.playbackRate)
sync_state[this.key('rate')] = this.$el.playbackRate
localStorage.setItem(`rate_${current_podcast.name}`, this.$el.playbackRate)
sync_state[`rate_${current_podcast.name}`] = this.$el.playbackRate
this.current_speed = this.$el.playbackRate
},
},
}"
>
<template x-if="current_episode && ( current_episode.url.endsWith('mp3') || current_episode.url.endsWith('ogg') || current_episode.url.endsWith('m4a'))">
<audio class="w-full"
x-bind="medium"
>
<source :src="current_episode && current_episode.url" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
</template>
<template x-if="current_episode && ! ( current_episode.url.endsWith('mp3') || current_episode.url.endsWith('ogg') || current_episode.url.endsWith('m4a'))">
<video class="w-screen max-h-screen object-contain"
x-bind="medium"
x-data="{
async click(event) {
xpos = event.offsetX / event.target.clientWidth
ypos = event.offsetY / event.target.clientHeight
if(xpos < 0.25) {
info('rewind')
this.$dispatch('pleaserewind')
}
else if (xpos > 0.75) {
info('ffwd')
this.$dispatch('pleaseffwd')
}
else {
if(ypos > 0.80)
{
info('PIP')
try{
await this.$el.requestPictureInPicture()
} catch(e) {
info(JSON.stringify(e))
}
}
else
{
info('toggling')
if(this.current_playing){
this.$dispatch('pleasepause')
}
else
{
this.$dispatch('pleaseplay')
}
}
}
}
}"
@dblclick="await click($event)"
>
<source :src="current_episode && current_episode.url" type="audio/mpeg">
</video>
</template>
</div>
</body>
</html>
{
"name": "podcast",
"short_name": "podcast",
"description": "podcast",
"start_url": "./podcast.html",
"display": "fullscreen",
"background_color": "#cccccc",
"theme_color": "#4285f4",
"orientation": "any",
"icons": [
{
"src": "./podcast.png",
"type": "image/png",
"sizes": "192x192"
}
],
"scope": "./",
"permissions": [],
"splash_pages": null,
"categories": []
}
Note: Beware that we should not provide an orientation so as to follow the device one.
I put this icon aside with the manifest (generated with the prompt “generate a 192x192 image on the theme podcast” in chatgpt).
// taken from https://googlechrome.github.io/samples/service-worker/basic/
const CACHE_NAME = 'podcastcache';
const DYNAMIC_URLS = [
"./podcast.html",
"./podcasts.json",
"./podcastmanifest.json",
"./swpodcast.js",
"./podcast.png",
]
// A list of local resources we always want to be cached.
const PRECACHE_URLS = [
"https://ipfs.konubinix.eu/bafybeienb3tpzdewluwh5i5mzrc7wq3vibqqsp52gf6z4j26winzlsbqlm/toastify.min.css?orig=https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css",
"https://ipfs.konubinix.eu/bafybeienb3tpzdewluwh5i5mzrc7wq3vibqqsp52gf6z4j26winzlsbqlm/toastify-js?orig=https://cdn.jsdelivr.net/npm/toastify-js",
"https://ipfs.konubinix.eu/bafkreidregenuw7nhqewvimq7p3vwwlrqcxjcrs4tiocrujjzggg26gzcu?orig=https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.x.x/dist/cdn.min.js",
"https://ipfs.konubinix.eu/bafkreighmyou4lhqizpdzvutdeg6xnpvskwhfxgez7tfawason3hkwfspm?orig=https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.x.x/dist/cdn.min.js",
"https://ipfs.konubinix.eu/bafkreic33rowgvvugzgzajagkuwidfnit2dyqyn465iygfs67agsukk24i?orig=https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js",
"https://ipfs.konubinix.eu/bafybeihp5kzlgqt56dmy5l4z7kpymfc4kn3fnehrrtr7cid7cn7ra36yha?orig=https://cdn.tailwindcss.com/3.4.3",
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll([
...PRECACHE_URLS,
...DYNAMIC_URLS
]))
.then(self.skipWaiting())
);
});
// clean up old caches
self.addEventListener('activate', event => {
const currentCaches = [CACHE_NAME];
event.waitUntil(
caches.keys().then(cacheNames => {
return cacheNames.filter(cacheName => !currentCaches.includes(cacheName));
}).then(cachesToDelete => {
return Promise.all(cachesToDelete.map(cacheToDelete => {
return caches.delete(cacheToDelete);
}));
}).then(() => self.clients.claim())
);
});
// taken from https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Caching
async function cacheFirst(request) {
const cachedResponse = await caches.match(request);
if (cachedResponse) {
console.log(`Get ${request.url} from the cache (cacheFirst)`)
return cachedResponse;
}
try {
// for some reason, the intercepted requests uses crendentials: include,
// leading to an error, explicitely tell to omit credentials then. I
// also have to explicitely tell it that I want to use cors.
const networkResponse = await fetch(request, { mode: 'cors', credentials: 'omit' });
console.log(`cacheFirst: ${request.url}: ${request.credentials}, ${networkResponse.ok}, ${networkResponse.status}, ${networkResponse.statusText}`)
if (networkResponse.ok) {
console.log(`cacheFirst: Put ${request.url} into the cache`)
const cache = await caches.open(CACHE_NAME);
cache.put(request, networkResponse.clone());
}
else
{
console.log(`cacheFirst: bad response with ${request.url}`)
}
return networkResponse;
} catch (error) {
console.log(`cacheFirst: error with ${request.url}`)
return Response.error();
}
}
async function networkFirst(request) {
try {
const networkResponse = await fetch(request);
if (networkResponse.ok) {
const cache = await caches.open(CACHE_NAME);
console.log(`Put ${request.url} into the cache (networkfirst), ${request.credentials}`)
cache.put(request, networkResponse.clone());
}
return networkResponse;
} catch (error) {
const cachedResponse = await caches.match(request);
console.log(`Got ${request.url} from the cache (networkfirst)`)
return cachedResponse || Response.error();
}
}
self.addEventListener('fetch', event => {
const dynamicUrlsRegex = new RegExp(`^.*(${DYNAMIC_URLS.join('|')}).*`);
if (event.request.url.match(dynamicUrlsRegex)) {
// this is likely to change from time to time, use a network first approach
console.log(`Dealing with ${event.request.url} (networkfirst)`)
event.respondWith(networkFirst(event.request));
} else if (event.request.url.includes("/ipfs/")) {
// ipfs content never changes, get it from the cache if possible
console.log(`Dealing with ${event.request.url} (cacheFirst)`)
event.respondWith(cacheFirst(event.request));
}
else
{
console.log(`Leaving ${event.request.url} to the browser`)
}
});