How do I get page URL with parameters from a service worker?
I have tried self.registration.scope but that doesn't include the parameters.
I'm not clear as to whether you're asking about getting the service worker script's URL, or the URLs of all of the client pages that are open under the service worker's scope. So... here's how to do both:
// Get a URL object for the service worker script's location.
const swScriptUrl = new URL(self.location);
// Get URL objects for each client's location.
self.clients.matchAll({includeUncontrolled: true}).then(clients => {
for (const client of clients) {
const clientUrl = new URL(client.url);
}
});
In either of those cases, once you have a URL object, you can use its searchParams property if you're interested in the query parameters:
if (url.searchParams.get('key') === 'value') {
// Do something if the URL contains key=value as a query parameter.
}
You can get waiting.scriptURL or active.scriptURL, pass result to URL() constructor, get .search property of object
navigator.serviceWorker.register("sw.js?abc=123")
.then(function(reg) {
const scriptURL = reg.waiting && reg.waiting.scriptURL || reg.active.scriptURL;
const url = new URL(scriptURL);
const queryString = url.search;
console.log(queryString);
}).catch(function(err) {
console.log("err", err);
});
ServiceWorker.scriptURL can give you the URL and its parameters as well.
Then, the following step is to get a ServiceWorker object, and it depends on where you would like to use the URL parameters.
In the worker script, use self.serviceWorker.scriptURL. e.g.
const searchParams = new URLSearchParams(self.serviceWorker.scriptURL);
In the page script, use scriptURL with navigator.serviceWorker.ready. e.g.
const serviceWorkerRegistration = await navigator.serviceWorker.ready;
const activeServiceWorker = serviceWorkerRegistration.active;
const searchParams = new URLSearchParams(activeServiceWorker.scriptURL);
However, you might want to get a registration object from register API, but the above code snippet should work as well.
Related
I encountered this weird problem when experimenting with JavaScript's URL object
here is the demo : https://codesandbox.io/s/fervent-wilbur-15kyt?file=/src/index.js
so the endpoint is https://jsonplaceholsdsdsdder.typicode.com/todos/,
The key id can be integer as its value. So https://jsonplaceholder.typicode.com/todos/?id=4 is valid. and https://jsonplaceholder.typicode.com/todos/?id=dsdsd is not valid.
I found that using Fetch to make the request https://jsonplaceholder.typicode.com/todos/?id=4 will still return a response with a status code 200.
const inputEl = document.querySelector("#input");
const endpoint = new URL("https://jsonplaceholder.typicode.com/todos/");
inputEl.addEventListener("input", async e => {
const { value: text } = e.target;
endpoint.searchParams.set("id", text);
const repsonse = await fetch(endpoint).catch(console.error);
const data = await repsonse.json();
console.log(repsonse.status); // 200
console.log(data); // []
});
However if we construct the URL directly like this
https://jsonplaceholsdsdsdder.typicode.com/todos/dd. this will actually return a response with 404 status code.
search params are not a part of the resource location. They are optional and have to be manually accessed by the server processing the request.
https://jsonplaceholder.typicode.com/todos/ is a valid resource location that has been set up by jsonplaceholder.com.
https://jsonplaceholder.typicode.com/todos/?id=4 is a valid resource location because anything after the question mark (?) is a parameter and parameters are not considered to be apart of the resource location so it is still valid.
https://jsonplaceholsdsdsdder.typicode.com/todos/dd is not a valid resource location because jsonplaceholder.com has not exposed a resource with that path.
This is because of the way the API works server side.
If you try and use that route, you'll get the 404 error because that specific route wasn't found.
If you use the searchParams.set method, it's just a parameter that the backend will use to filter the full list of todos, in which case the call was made successfully, hence the 200 response code. but the response results were empty, hence the empty array [].
I am having troubles running queries from Cloud Functions using the request parameters to build the query form HTTP calls. In the past, I have ran queries from cloud functions fine with no error. My problem arises when I try to run the query using parameters gotten from the request.
When I hardcode the location of the document in the function, it works fine but when I try to build a query, it returns status code of 200. I have also logged the the built query and it is logging out the right thing but no data is being returned. It only returns data when the document path is hardcoded. See code below.
Query looks like this
https://us-central1-<project-id>.cloudfunctions.net/getData/CollectionName/DocumentName
export const getData = functions.https.onRequest((request, response) => {
const params = request.url.split("/");
console.log("the params 0 "+params[0]);
console.log("the params 1 "+params[1]);
console.log("the params 2 "+params[2]);
//Build up the document path
const theQuery = "\'"+params[1]+"\/"+params[2]+"\'";
console.log("the query "+theQuery); <-- logs out right result in the form 'Collection/Document'
//Fetch the document
const promise = admin.firestore().doc("\'"+params[1]+"\/"+params[2]+"\'").get() <---- This doesnt work, building the query
//const promise = admin.firestore().doc('collectionName/DocID').get() <---- This hard coded and it works
promise.then(snapshot => {
const data = snapshot.data()
response.send(data)
}).catch(error => {
console.log(error)
response.status(500).send(error);
})
});
I tried using a different approach and giving the datafields a names as seen below
Query looks like this
https://us-central1-<project-id>.cloudfunctions.net/getData?CollectionName=CName&DocumentID=Dname
export const getData = functions.https.onRequest((request, response) => {
const collectName = request.query.CollectionName;
const DocId = request.query.DocumentName;
//Build up the document path
const theQuery = "'"+collectName+"\/"+collectName+"'";
console.log("the query "+theQuery); <---Logs out correct result
//Fetch the document
const promise = admin.firestore().doc(theQuery).get() <-- Building the query does not work
//const promise = admin.firestore().doc('collectionName/DocID').get() <---- This hard coded and it works
promise.then(snapshot => {
const data = snapshot.data()
response.send(data)
}).catch(error => {
console.log(error)
response.status(500).send(error);
})
});
In both cases, when the request is build from the URL, it does not return any data and it does not return any errors. And I am sure the documents I am trying to fetch exsist in the database. Am I missing anything ?
Try request.path. Then you can obtain the path components, e.g. request.path.split("/")[1]
The syntax for request.query is valid when using Express. This is referenced in some of the docs, but not made explicit that Express is required. It's confusing.
To properly handle the dynamic inputs, you may have more luck working with Express and creating routes and handlers. This Firebase page has links to some projects using it.
Walkthough set-up using Express on Firebase.
While in other questions people claimt EventSource is fairly well documented I have found it to be more implied then explicit in some cases.
My understanding is that when you initialise an EventSource object in JS it opens a connection to your server using the specified URI.
Is this connection initiated using GET?
(Not sure if this constitutes a second question) Is it possible to use/force another HTTP Method (POST)?
The request method when using the EventSource interface is a GET request. You can include a query string in the URL passed to the constructor and parse the query string at the server.
const stream = "data: event stream\n\n";
const blob = new Blob([stream], {type:"text/event-stream"});
const blobURL = URL.createObjectURL(blob);
const es = new EventSource(blobURL);
es.onmessage = e => {
console.log(e.data);
}
es.onerror = e => {
es.close();
}
How to use below code in WorkboxSW to register routes for all per-caching urls. This per-cached urls includes ajax that will go to server also!
$.ajax({
url : '/MyAPI/Records',
type : 'GET',
dataType:'json',
success : function(data) {
alert('Records: '+data);
//build urls array to get all records details
var urls = [];
urls.push('/MyAPI/Records')
$(data).each(function (index) {
urls.push('/MyAPI/Records/' + data[index].id + '/data')
});
//add all urls to cache
var requests = urls.map(url => new Request(url));
caches.open('my-cache').then(cache => {
return cache.addAll(requests).then(() => {
// At this point, `cache` will be populated with `Response` objects,
// and `requests` contains the `Request` objects that were used.
}).catch(error => console.error(`Oops! ${error}`));
});
},
error : function(request,error)
{
alert("Request: "+JSON.stringify(request));
}
});
Workbox's precaching relies on having access to a local file representing the resource at build time. This allows it to generate a hash of each resource its managing (based on the local file's contents) and then keep that cached resource up to date whenever the local file changes.
What you're suggestion sounds more like Workbox's support for handling certain routes via runtime caching. You can configure it via something like:
// Replace with your actual API endpoint.
const API_URL = 'https://my-api-server.com/api/restEndpoint';
// Use whichever strategy you'd prefer: networkFirst, staleWhileRevalidate, etc.
const apiCallHandler = workboxSW.strategies.networkFirst({
cacheName: 'my-api'
});
workboxSW.registerRoute(
API_URL,
apiCallHandler
);
This will result in responses from https://my-api-server.com being added to the cache named my-api at runtime, after you make your first request. (In this particular case, using the networkFirst strategy, those cached responses will only be used if the network is unavailable.)
If you're not okay with the runtime cache starting out "cold" and you feel like it needs to be primed, then you could do that by writing your own install event handler alongside your Workbox code:
// Your existing WorkboxSW code goes here.
self.addEventListener('install', event => {
event.waitUntil(
caches.open('my-api')
.then(cache => cache.add(API_URL))
);
});
I have a function that returns a new Request object;
import * as _url from 'url';
// pathname starts with '/content/'
const isContentUrl = (path) => /^\/content\//.test(path);
export default function(url) {
let urlObj = _url.parse(url);
if (isContentUrl(urlObj.pathname)) {
urlObj.pathname = `/offline${urlObj.pathname}`;
}
return new Request(_url.format(urlObj), {
credentials: 'same-origin',
headers: {
'x-requested-with': 'sw'
}
});
}
Now I'm writing unit tests for this function and although I know there isn't actually much that changes here but say for example the headers could change for whatever reason, how can I assert parts of the request object like the headers, credentials or the URL?
Is there a nice way to be able to parse it for testing?
Ideally I'd like to do something like
it('should return a Request object with the correct headers', () => {
const url = '/content/2c509c50-e4ba-11e6-9645-c9357a75844a';
const request = offlineContent(url);
const result = request.headers;
const expected = {'x-requested-with': 'sw'};
expect(result).to.eql(expected);
});
in my test
Assuming that request here the request HTTP client, the returned object is an instance of the Request constructor defined here.
If you follow through the code, you will see that headers provided are available simply through an object member and thus headers, and other members being attached to self can be easily introspected through javascript.
In addition, the http module through which requests are dispatched is available as self.httpModule and can be mocked by an implementation that is compliant with node http module and requests dispatched through the library can be intercepted through spies.
MDN details the methods on the Request and Headers object. Unfortunately I haven't found a way to convert this to an object that I could assert with a deep equal. But I can use these to assert against.
request.url;
request.credentials;
request.headers.get('x-requested-with');
Unfortunately the Headers.getAll() method has been deprecated so in order to fail tests if someone added a new header I have asserted against;
Array.from(request.headers.keys()).length