Is there a way to have my service worker intercept fetch requests coming from a client-side SvelteKit load function? (+page.ts) - service-worker

I'm trying to have a service worker intercept fetch requests coming from a client-side SvelteKit load function. The network requests are being made, but the fetch event is not being triggered.
The fetch request from the load function is going to /api/allTeams, which is cached as reported by chrome devtools, but like I said, it's not getting intercepted. All the function does it fetch the data, and return it in a prop.
Also, every couple minutes I run invalidateAll(), to reload the data, and even those requests aren't being picked up by the SW.
Thanks!
--reese
src/service-worker.js:
import { build, version } from '$service-worker';
self.addEventListener('fetch', function (event) {
console.log("fetch")
event.respondWith(
fetch(event.request).catch(function () {
return caches.match(event.request);
}),
);
});
self.addEventListener('install', async function (event) {
event.waitUntil(
caches.open("ccs-" + version).then(function (cache) {
cache.add("/api/allTeams")
cache.addAll(build)
return;
}),
);
});
src/app.html:
<script>
const registerServiceWorker = async () => {
if ("serviceWorker" in navigator) {
try {
const registration = await navigator.serviceWorker.register("/service-worker.js", {
scope: "*",
});
if (registration.installing) {
console.log("Service worker installing");
} else if (registration.waiting) {
console.log("Service worker installed");
} else if (registration.active) {
console.log("Service worker active");
}
} catch (error) {
console.error(`Registration failed with ${error}`);
}
}
};
registerServiceWorker()
</script>
src/+page.ts:
export async function load(request: Request) {
const searchQuery = new URL(request.url).searchParams.get("q")
const apiUrl = new URL(request.url)
apiUrl.pathname = "/api/allTeams"
const req = await fetch(apiUrl)
const data = await req.json()
return {data, searchQuery};
}

Related

How to respond with a stream in a Sveltekit server load function

Below I try to respond with a stream when I receive ticker updates.
+page.server.js:
import YahooFinanceTicker from "yahoo-finance-ticker";
const ticker = new YahooFinanceTicker();
const tickerListener = await ticker.subscribe(["BTC-USD"])
const stream = new ReadableStream({
start(controller) {
tickerListener.on("ticker", (ticker) => {
console.log(ticker.price);
controller.enqueue(ticker.price);
});
}
});
export async function load() {
return response????
};
Note: The YahooFinanceTicker can't run in the browser.
How to handle / set the response in the Sveltekit load function.
To my knowledge, the load functions cannot be used for this as their responses are JS/JSON serialized. You can use an endpoint in +server to return a Response object which can be constructed from a ReadableStream.
Solution: H.B. comment showed me the right direction to push unsollicited price ticker updates the client.
api route: yahoo-finance-ticker +server.js
import YahooFinanceTicker from "yahoo-finance-ticker";
const ticker = new YahooFinanceTicker();
const tickerListener = await ticker.subscribe(["BTC-USD"])
/** #type {import('./$types').RequestHandler} */
export function GET({ request }) {
const ac = new AbortController();
console.log("GET api: yahoo-finance-ticker")
const stream = new ReadableStream({
start(controller) {
tickerListener.on("ticker", (ticker) => {
console.log(ticker.price);
controller.enqueue(String(ticker.price));
}, { signal: ac.signal });
},
cancel() {
console.log("cancel and abort");
ac.abort();
},
})
return new Response(stream, {
headers: {
'content-type': 'text/event-stream',
}
});
}
page route: +page.svelte
<script>
let result = "";
async function getStream() {
const response = await fetch("/api/yahoo-finance-ticker");
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
while (true) {
const { value, done } = await reader.read();
console.log("resp", done, value);
if (done) break;
result += `${value}<br>`;
}
}
getStream();
</script>
<section>
<p>{#html result}</p>
</section>

How to send a result to sender via contextBridge / IPCRenderer?

I have a electron that looks like this
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electron', {
electronStore: {
get(val) {
ipcRenderer.send('electron-store-get', val);
},
set(property, val) {
ipcRenderer.send('electron-store-set', property, val);
},
// Other method you want to add like has(), reset(), etc.
},
});
and ipcMain that looks like this
ipcMain.on('electron-store-get', async (event, val) => {
store.get(val);
// console.log(reply);
// return reply;
// event.reply('electron-store-get', reply);
});
ipcMain.on('electron-store-set', async (event, property, val) => {
// console.log(val);
store.set(property, val);
});
When I was trying to call the function via electron.electronStore.get(), it returns undefined
let a = window.electron.electronStore.get('test');
console.log(a);
However, I've tested that on the line of ipcRenderer.send(""), I was able to receive data by setting as below
let result = ipcRenderer.send('electron-store-get',val);
console.log(result);
Which mean, ipcRenderer is not undefined and set has been successfuly, get as-well, just it went missing when i invoke the ipcMain Get functions
Your current preload API isn't actually returning anything:
get(val) {
ipcRenderer.send('electron-store-get', val);
}
You'll want to either use the synchronous API: return ipcRenderer.sendSync('electron-store-get', val) and then have your handler in main do:
ipcMain.on('electron-store-get', (event, val) => {
event.returnValue = store.get(val);
});
Or make the preload API async:
get(val) {
return ipcRenderer.invoke('electron-store-get', val);
}
ipcMain.handle('electron-store-get', (event, val) => {
return store.get(val);
});
And then:
let a = await window.electron.electronStore.get('test');

Using a service worker to get list of files to cache from server

I´m trying to use a service worker in an existing asp mvc app. Generally, it´s working fine: I can cache files and so on. Problem is, that there are many files to be cached and I´m trying to return an array of paths to the service worker, so that the files can be added to cache without adding them manually.
Here´s what I have so far:
Controller:
public ActionResult GetFilesToCache()
{
string[] filePaths = Directory.GetFiles(Server.MapPath(#"~\Content"), "*", SearchOption.AllDirectories);
string[] cuttedFiles = new string[filePaths.Length];
int i = 0;
foreach (var path in filePaths)
{
cuttedFiles[i] = path.Substring(path.IndexOf("Content"));
i++;
}
return Json(new { filesToCache = cuttedFiles }, JsonRequestBehavior.AllowGet);
}
This gives me a string array with entries like "Content\image1.png" etc.
Service worker:
self.addEventListener('install', function(e) {
console.log('[ServiceWorker] Install');
e.waitUntil(
caches.open(cacheName).then(function (cache) {
console.log('[ServiceWorker] Caching app shell');
return fetch('Home/GetFilesToCache').then(function (response) {
return response;
}).then(function (files) {
return cache.addAll(files);
});
})
);
});
The error I get is:
Uncaught (in promise) TypeError: Failed to execute 'addAll' on 'Cache': Iterator getter is not callable.
Calling the action works just fine, data is received by the service worker, but not added to cache.
In the following code:
return fetch('Home/GetFilesToCache').then(function (response) {
return response;
}).then(function (files) {
return cache.addAll(files);
});
the value of the files parameter is going to be a Response object. You want it to be the JSON deserialization of the Response object's body. You can get this by changing return response with return response.json(), leading to:
return fetch('Home/GetFilesToCache').then(function(response) {
return response.json();
}).then(function(files) {
return cache.addAll(files);
});
Got it working with this code:
self.addEventListener('install', function(e) {
console.log('[ServiceWorker] Install');
e.waitUntil(
caches.open(cacheName).then(function (cache) {
console.log('[ServiceWorker] Caching app shell');
return fetch('Home/GetFilesToCache').then(function (response) {
return response.json();
}).then(function (files) {
var array = files.filesToCache;
return cache.addAll(array);
});
})
);
});
Notice:
Chrome only lists a part of files stored in cache, so you just have to click that little arrow to show the next page:
return fetch('Home/GetFilesToCache').then(function(response) {
return response.json();
}).then(function(files) {
return cache.addAll(files);
});
The returned file has "filesToCache" property on "files" and also use "add" instead of "addAll".
So you need to write the following way.
return fetch('Home/GetFilesToCache').then(function(response) {
return response.json();
}).then(function(files) {
return cache.add(files.filesToCache);
});

Service Worker Caches to much

I'mm working on a service worker, who should just cache some specific files.
But after implementation and refresh (yes I enabled "dumb sw after reload") there are much more files "loaded via the sw" then I added in the "files to cache" list.
I'm new with the service worker and have no idea what is wrong. I just followed some tutorials about it.
Here the code. Perhaps I am on the complete wrong way.
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/worker.js').then(function(registration) {
}, function(err) {
console.log('ServiceWorker registration failed: ', err);
});
});
}
var debugMode = false;
//Variables to cache
var CACHE_NAME = 'companyname-cache-1511794947915';
var urlsToCache = [
'/typo3conf/ext/companyname/Resources/Public/css/animate.css',
'/typo3conf/ext/companyname/Resources/Public/css/bootstrap.css',
'/typo3conf/ext/companyname/Resources/Public/css/bootstrap.min.css',
'/typo3conf/ext/companyname/Resources/Public/css/bootstrap-theme.css',
'/typo3conf/ext/companyname/Resources/Public/css/bootstrap-theme.min.css',
'/typo3conf/ext/companyname/Resources/Public/css/main.css',
'/typo3conf/ext/companyname/Resources/Public/css/et/override.css',
'/typo3conf/ext/companyname/Resources/Public/css/et/screen-full.css',
'/typo3conf/ext/companyname/Resources/Public/css/et/screen-full.min.css',
'/typo3conf/ext/companyname/Resources/Public/Icons/favicon.ico',
'/typo3conf/ext/companyname/Resources/Public/Icons/FaviconTouch/android-chrome-512x512.png',
];
self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener("activate", function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if(debugMode) {
console.log('actual cache name: %o', CACHE_NAME);
console.log('name inside the cache: %o', cacheName);
}
if ((cacheName.indexOf('companyname-cache-') !== -1) && (cacheName !== CACHE_NAME)) {
if(debugMode) {
console.log("old cache deleted");
}
return caches.delete(cacheName);
}
})
);
})
);
});
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
if (response) {
if(debugMode) {
console.log("fetch 1");
}
return response;
}
var fetchRequest = event.request.clone();
return fetch(fetchRequest).then(
function(response) {
if(!response || response.status !== 200 || response.type !== 'basic') {
if(debugMode) {
console.log("fetch 2");
}
return response;
}
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
if(debugMode) {
console.log("fetch 3");
}
return response;
}
);
})
);
});
The code in your SW works like this:
On installation, add all items in the URL LIST to the cache
On activation, check that the cache has items only for the CACHE NAME
On a fetch event: 1st check if the requested resource is in the cache and return if if found; 2nd if the requested resource was NOT in the cache, request it from the network AND put it in the cache
The last part of #3 causes the problem in your SW code – it is adding everything requested to the cache so in the future they're available from the cache.
You can see the problem in your code if you look for the last occurrence of this:
caches.open(CACHE_NAME)
That's the part of your code when something WASN'T found from the cache AND was requested from the network. The code is then putting it into the cache – that's not what you wanted.

ServiceWorker 'fetch' event not triggered

fetch event :
self.addEventListener('fetch', function(event) {
console.log("[Service Worker] fetching "+event.request);
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});

Resources