w3c / ServiceWorker

Service Workers
https://w3c.github.io/ServiceWorker/
Other
3.63k stars 313 forks source link

API Generality #445

Closed martinthomson closed 9 years ago

martinthomson commented 10 years ago

Apologies if this was raised previously. I couldn't see anything discussion on the topic, though I'm told that at least some people have been thinking about this already.

One thing I get reading the service worker spec is the sense that the first use case (offline) dominates the form of the API.

The scope attribute is the only real problem in this regard. ServiceWorkerContainer.register takes the RegistrationOptionList, which currently only has a single argument:

  navigator.serviceWorker.register(script, { scope: "/foo" }) 

Limiting the scope (in the more general sense) of a registration seems to be the right idea. However, ad hoc limiting based on a bag of arguments with different purposes doesn't seem like a good way to build the basis of a new generic platform feature. A more modular approach might ensure service workers are suited to use for offline apps, web push, geolocation and other features.

I have a few ideas. @sicking tells me he has some thoughts too. I'll share mine in separate comments.

sicking commented 10 years ago

Thanks for starting this conversation. I've felt the same discomfort about the registration API from the beginning, but hasn't been able to put a finger on what exactly bothered me until recently.

Currently ServiceWorkers suffers from a couple of problems related to the registration API.

Apps with URLs in multiple directories

First of all, a ServiceWorker currently can only service a single URI pattern. This means that if your application lives under "/calendar/" and under "/meetings/", you are left with mainly bad choices.

You could change your URLs, but that means breaking any existing links.

You could register for "/". But that means that if you have two applications on the same server with the same problem, you now have to use the same serviceworker for two separate applications. It also means that a lot of URLs that are unrelated to the application

You could use create two separate registrations. But that looses some of the nice atomic update behavior that ServiceWorkers provide. It would also mean that we're now running two worker threads rather than one, which costs resources, especially on mobile. Additionally, this would require that manifests support multiple service worker registrations in a single manifest.

You could use two separate registrations, but use the same service worker URL. This could in theory enable the implementation to do a single download for both service workers, so that part would still be atomic. However the install steps might still fail for one but not the other. It also adds additional complexity to avoid having the two service worker instances stomp on each other's caches or download the same resources twice. And it has the same two-threads and manifest-complexity problems as the two-separate-registrations solution.

You could simply choose not to cache one of the two paths. This is the option I'm most worried developers will choose since it means that the app won't be as fast, and might not work offline. But it's also the simplest solution which means that there's a big risk that developers will choose it.

Http-intercept registration differs from other features

The second problem with the registration API is that the "intercept http requests" feature uses a different registration mechanism than things like "register push notifications" or "register geofencing notification". This isn't a huge problem, but is an unfortunate inconsistency.

A slightly bigger problem is that it means that in order to use other service worker features, you have to register for a scope at which to intercept http requests, even if you're not interested in that ability. To handle that we have added the "look for event handlers in the global scope" thing, and use that to effectively unregister for the "intercept http requests" feature.

Proposed solution

A solution to both of these problems would be to separate installing a service worker from registering for http intercepts. This way we could enable websites to make multiple calls to "register for http intercepts" in order to make a service worker cover multiple http scopes.

This could look something like this:

[Exposed=Window]
interface ServiceWorkerRegistration : EventTarget {
  [Unforgeable] readonly attribute ServiceWorker? installing;
  [Unforgeable] readonly attribute ServiceWorker? waiting;
  [Unforgeable] readonly attribute ServiceWorker? active;

  Promise<boolean> unregister();

  Promise<void> addScope(ScalarValueString scope);
  // resolves to false if the scope wasn't registered.
  Promise<boolean> removeScope(ScalarValueString scope);

  // does this need to be async?
  readonly attribute sequence<ScalarValueString> scopes;

  attribute EventHandler onupdatefound;
};

However a question is what will now be the identifier for a SW given that we currently use the http intercept scope as the identifier. The simplest answer seems to be to simply enable service workers to have a name. So registration would look something like:

interface ServiceWorkerContainer {
  ...
  Promise<ServiceWorkerRegistration> register(ScalarValueString scriptURL, optional ScalarValueString name="");

  Promise<ServiceWorkerRegistration> getRegistration(optional ScalarValueString name = "");
  ...
};

This has the following benefits over the current API:

The main disadvantage that I can see is that registering for http interception now requires slightly more code. This seems ok to me, but if we want to address it, we could as sugar enable passing in a scope during registration:

interface ServiceWorkerContainer {
  ...
  Promise<ServiceWorkerRegistration> register(ScalarValueString scriptURL, optional RegistrationOptionList options);

  Promise<ServiceWorkerRegistration> getRegistration(optional ScalarValueString name = "");
  ...
};

dictionary RegistrationOptionList {
  ScalarValueString name = "";
  (ScalarValueString or sequence<ScalarValueString>) scope;
};

This is almost exactly what the API looks like today, the main difference is that if you don't pass in a scope at registration, that no scope gets registered, rather than that / is used as scope. Technically we could even change that, but that would bring back some of the downsides mentioned above.

sicking commented 10 years ago

I should note that this still gives the http-intercept feature somewhat of a special status.

Scopes still get a predominant position in the API, and so far scopes are specific to http intercepts. This affects things like navigator.serviceWorker.controller and navigator.serviceWorker.ready().

However I think this is ok. First of all http-intercepts is one of the more important features of ServiceWorkers, which is why I also think it's ok to have syntax for it in the registration API.

But second, the concept of a scope could very well come in handy for other features.

martinthomson commented 10 years ago

I should note that this still gives the http-intercept feature somewhat of a special status.

Despite this being a initial motivation for the feature as a whole, I think that greater generality is still valuable here. First use not conferring any sort of special status.

To that end, here's my alternative approach. It's more disruptive, but it has some encapsulation benefits over both the current and proposed alternatives. It retains the general concept of "scope", but generalizes that concept more. URL-space scope, as used in the fetch/caching cases is largely the same, but it has a different entry point.

The outer ServiceWorkerContainer and ServiceWorkerRegistration no longer require knowledge of the functions of the worker itself. Registration only requires knowledge of the script URL and a name (if multiple instances of the same worker are needed. Note that we probably need to ensure that the name of the worker is exposed to the worker if we're using names.

interface ServiceWorkerContainer {
  Promise<ServiceWorkerRegistration> register(ScalarValueString script, DOMString name = "");
  Promise<ServiceWorkerRegistration> getRegistration(optional DOMString name = "");
  ...
}

This is no different to using names for identification as @sicking proposes.

What changes is the internals and how events are surfaced to workers. During the install event the worker itself registers interest in different scopes.

this.addEventListener("install", e => {
  e.waitUntil(Promise.all(
    this.webPush.register().then(push => {
      push.addEventListener("push", handlePush);
    });
    this.geolocation.register(geolocationOptions).then(geo => {
      geo.addEventListener("enter", handleGeoEnter);
      geo.addEventListener("leave", handleGeoLeave);
    });
    this.fetch.register(scope).then(...);
    this.cache.register(scope).then(...);
  ));
}); 

The trick here being that each different module (or whatever you want to call a logical function that we might use SW for) has particular needs for determining how to pre-filter the events it might generate. Relying on a single string value (or set thereof) isn't sufficient for the new geolocation features (see proposals here and here) and we can't possibly anticipate the needs that a future API might generate.

On the other hand web push has no need for any such affordance, at least as far as current plans indicate, though that could change (and likely will over time). The idea that apps might need the same fetch access for multiple path prefixes is clearly another example of how different use cases evolve to encompass new needs.

interface ServiceWorkerGlobalScope {
  GeolocationWorkerContainer geolocation;
  WebpushWorker push;
  FetchWorker fetch;
  ...
}

interface GeolocationWorkerContainer {
  Promise<GeolocationWorker> register(optional GeolocationWorkerOptions options, optional DOMString name = "");
  Promise<GeolocationWorker> getRegistration(optional DOMString name = "");
}
interface GeolocationWorker : EventTarget {
  readonly attribute DOMString name;
  attribute EventHandler? onleave;
  attribute EventHandler? onleave;
}

Arguably, the register and getRegistration functions here are free to be renamed to anything that suits the less general pattern this fits, but establishing a convention seems to make sense (the use of generics in WebIDL again suggests itself here, which would obviate the need for informal hacks like "convention").

The advantage of an approach like this is that the worker itself determines what it needs. That provides content authors better encapsulation. I think that this also provides the browser with more determinism regarding what events need to hit the service worker. If the set of event registrations has to be complete by the time that install completes, then that ensures that the worker can be suspended more efficiently.

I can imagine surfacing information on ServiceWorkerRegistration that would allow clients of the SW to learn status about what APIs are being used by that SW.

sicking commented 10 years ago

My vision has been to not have the SW "filter" events. Instead, at the time when you register for some particular feature, you indicate which events you want fired, and which SW to fire the events at.

So myRegistration.geolocation.registerRegion(...) means fire a geofenceenter event at the myRegistration service worker when the user enters the given area.

Likewise myRegistration.push.register() means fire a push event at the myRegistration service worker when a message arrives at the given endpoint.

And myRegistration.addScope("/") means fire a fetch event for any documents whose URL starts with "/", and fire the event at the myRegistration service worker.

So no filtering is happening. And each API can have entirely different patterns of events that are fired. "scope" is really just a "set of URLs in which to invoke the http-intercept feature". Technically we could call addScope something like addHttpInterceptScope, but I'd rather use a shorter name.

I think the main difference between our proposals is that I've stuck the registration APIs on the ServiceWorkerRegistration object, rather than inside the service worker's global scope. The advantage of that is that it makes it more clear that once a http-intercept-scope or a push-registration has been registered, it stays with the service worker even if the worker is upgraded. I.e. these things are tied to a service worker registration, not a particular version of the service worker. When the service worker is updated and we fire the install event again, there is no need to reregister.

annevk commented 10 years ago

I like it!

jakearchibald commented 10 years ago

@sicking

First of all, a ServiceWorker currently can only service a single URI pattern. This means that if your application lives under "/calendar/" and under "/meetings/", you are left with mainly bad choices.

Do you have any examples of this?

Promise<void> addScope(ScalarValueString scope);

So now I can have multiple registrations claiming the same scopes? What happens then?

@martinthomson

this.addEventListener("install", e => {

Nah, you can't register for these things within the serviceworker, they need to be done from the window so we have somewhere relevant to show permission dialogs. Also, you're adding listeners within the install event, those will be lost once the serviceworker terminates, you'll most likely never get any geo events.

Scope isn't just about fetch interception, it's part of the lifecycle, it's how we control upgrades. If we see sites wanting '/blah/' and '/whatever/' to be controlled by the same worker, but a scope of '/' doesn't work for them, we can look at ways of expanding 'scope' to an array while retaining it's primary key nature.

The proposals here feel like huge changes & complication for very little benefit.

sicking commented 10 years ago

First of all, a ServiceWorker currently can only service a single URI pattern. This means that if your application lives under "/calendar/" and under "/meetings/", you are left with mainly bad choices.

Do you have any examples of this?

Not off the top of my head, but I'd be shocked if this isn't common among sites that host multiple "apps" on the same domain. I.e. among sites that need scopes at all.

But I'll look for examples.

Scope isn't just about fetch interception, it's part of the lifecycle, it's how we control upgrades.

How so? I.e. how does the scope affect the lifecycle and upgrades? Not doubting you, I might very well be missing pieces.

I agree that it's a big change. Though with the proposed sugar it comes out as very small code changes for the website.

But I think the spec right now is making a very big assumption that websites are generally structured such that each "app" has its own directory. If we're betting wrong on that the result will likely be that websites won't work as well offline, and will be slower online, which is our main goal to avoid.

jakearchibald commented 10 years ago

how does the scope affect the lifecycle and upgrades?

We don't promote a "waiting" worker to "active" until all clients have disconnected. Clients are windows that have opted to use a registration as their controller, which is done based on scope.

Also installEvent.replace() makes all within-scope clients use the registration as their controller.

I guess the clients API itself is also very scope-driven.

martinthomson commented 10 years ago

@sicking

My vision has been to not have the SW "filter" events.

Yes, browser filters, not the SW. I apologize for accidentally implying otherwise.

@jakearchibald

[...] they need to be done from the window so we have somewhere relevant to show permission dialogs.

Is the intent here to ask a series of questions? "Do you want this site to be available offline?" "Do you want this site to be able to track your location when you aren't visiting it?" "Do you want to receive notifications from this site?"

Having had more time to think about it, I think that my own lack of understanding about the lifecycle made my alternative seem plausible, but it really isn't. The fact that scopes are still special bothers me some, but not enough to get excited about.

jakearchibald commented 10 years ago

Is the intent here to ask a series of questions?

We're not changing the permission model of the web here, the idea is to request permission at a moment that makes sense to the user, either after an interaction "enable push messaging", or without interaction if the intent is clear to the user, eg gmaps asking for location permission.

ehsan commented 10 years ago

First of all, a ServiceWorker currently can only service a single URI pattern. This means that if your application lives under "/calendar/" and under "/meetings/", you are left with mainly bad choices.

Do you have any examples of this?

https://www.google.com/calendar. :) It accesses www.google.com/csi among other things.

ehsan commented 10 years ago

FWIW I like @sicking's proposal here too.

jakearchibald commented 10 years ago

What is http://www.google.com/csi? It doesn't appear to be another place /calendar/ lives (it's an empty page for me).

FWIW I like @sicking's proposal here too.

But liking it isn't enough. The scope is more than just for determining which fetches are captured (see my comment above).

Closing this. Feel free to reopen if there's a desire (and a solution) to separating fetch without breaking the other things that depend on scope.

mvano commented 10 years ago

Client Side Instrumentation - it's not serving anything, just a place to report to about e.g. how long it took to load the page, how long to render some component, etc.

jakearchibald commented 10 years ago

Ah ok, not a relevant example then.

KenjiBaheux commented 10 years ago

Assuming that nothing has changed as the result of the discussion => will be adding no impact label.

ehsan commented 10 years ago

Ah ok, not a relevant example then.

Here is another one: http://www.bbc.co.uk/persian/. It loads content from /persian, /worldservice, etc. I am really surprised that it's hard for you to believe that there are web properties that live on multiple URL paths.

Can you please reopen the issue? I don't think that we have addressed it at all, and I don't seem to have access to reopen it myself.

jakearchibald commented 10 years ago

@ehsan

Here is another one: http://www.bbc.co.uk/persian/. It loads content from /persian, /worldservice, etc

You're misunderstanding what @sicking was referring to. He was talking about a web property that exists across multiple paths where a origin-scoped serviceworker would be harmful.

http://www.bbc.co.uk/persian/ loads content from /worldservice, yes, but it's static assets. That's not the point. https://jakearchibald.github.io/trained-to-thrill/ loads content from flickr, doesn't mean they should share a serviceworker.

I am really surprised that it's hard for you to believe that there are web properties that live on

Yet everyone here has been unable to come up with an example.

I'm not saying it doesn't happen, but I'd like to see a site that would have this problem before we consider solving it.

Also, this can be solved with less severe changes such as allowing scope to be an array.

Have reopened, but will close again soon if there isn't a good example.

martinthomson commented 10 years ago

My problem with the API remains that selection is based on concepts that aren't universally applicable. Web Push doesn't need to concern itself with scope of control in the same way that fetch might. Nor does geolocation.

The geolocation case at least benefits from access to a parallel means of determining what events a SW is configured to handle, but using that same mechanism as a basis for SW selection would not work. (I do ultimately think a similar event determination process applies to push as well, but that group has decided to simplify so much that that isn't really an option.)

It's that conflation that is the issue here, not the multiple scopes thing, which is a separable issue. I'm happy to defer to others on this. Though I'll point that you are creating a forcing function by limiting scopes this way, which I don't think is wise. If the part of the web you look at happens to form into neat compartments, that's great, but I've learned not to make assumptions along those lines.

ehsan commented 10 years ago

@jakearchibald I pinged you on IRC to get a better understanding of what examples you find acceptable. My basic assumption is that a good example would be an application living under the same origin with other independent applications across multiple URL scopes. If you're looking for something else, please be more specific.

Here is another example: https://www.facebook.com/events. The UI to create a new event loads from https://www.facebook.com/ajax/plans/create/dialog.php. Browsing Facebook under the network panel in the Firefox devtools, it seems like /ajax is the URL scope for several helper scripts, such as this one.

jakearchibald commented 10 years ago

I don't think that example is a problem either.

/events/ requests data from /ajax/ - that's fine. You can think of /ajax/ as a sub app. Either the /events/ SW script will handle caching/routing the way that makes sense for /events/, or if there's enough commonality between other apps on the origin, it'll importScripts the code, or (and most likely in Facebook's case) there'll be one SW for the origin, it is a single native app after all. (yes, ok messaging is separate, but not on the web, and not spread across URLs in a way that breaks the current model)

Compare to my trains demo again. I request data from Flickr but my app should not share a SW with flickr. I'm using flickr data, but the SW logic is unique to my app. Maybe flickr will provide a script to help make flickr stuff work offline for me, if that works for usecases I can importScripts it.

What Jonas is talking about is an app that doesn't have a root path. The app has roots at /a/ and /b/, so they would want the same SW, but /c/ is something different, and having a SW for the whole origin and a separate one for /c/ wouldn't work for some reason.

Maybe that's something we should be worrying about, but I'd like to see a real-world example of that before we work on solutions, so we better understand the problem. Once we can see it, maybe we can fix it in a way that isn't removing the bottom block of the jenga tower. On 12 Sep 2014 21:37, "Ehsan Akhgari" notifications@github.com wrote:

@jakearchibald https://github.com/jakearchibald I pinged you on IRC to get a better understanding of what examples you find acceptable. My basic assumption is that a good example would be an application living under the same origin with other independent applications across multiple URL scopes. If you're looking for something else, please be more specific.

Here is another example: https://www.facebook.com/events. The UI to create a new event loads from https://www.facebook.com/ajax/plans/create/dialog.php. Browsing Facebook under the network panel in the Firefox devtools, it seems like /ajax is the URL scope for several helper scripts, such as this one.

— Reply to this email directly or view it on GitHub https://github.com/slightlyoff/ServiceWorker/issues/445#issuecomment-55450824 .

slightlyoff commented 10 years ago

The problem statement as such is tortured. Many plugin features benefit from scoping in the same way that interception does.

Won'tfixing this unless we get a better argument.

annevk commented 10 years ago

What plugin features benefit from scoping in this manner?

These do not:

It does seem like a better design to install a service worker for an origin and then associate scope(s) with it. That way we also solve the issue with whether or not we dispatch events inside the service worker. Adding scopes is opting into the fetch event. Registering for plugin features opts into their events.

These seems like a vastly better design and the changes for everyone involved are minimal.

domenic commented 10 years ago

Especially if onfetch is not part of the MVP anyway.

KenjiBaheux commented 10 years ago

Removed no impact (blink): I want to make sure we get down to the bottom of this and do the right thing for the web developers.

slightlyoff commented 10 years ago

Background sync absolutely benefits from scoping. Push is tied to registration and scoping is logically about a "part" of the origin space, so by allowing multiple scopes (and implicitly, multiple registrations), origins can logically distinguish their push handlers.

The best argument I can see for the non-scoping side is that it ties navigation handling to a bit of the namespace and there might be value is distinguishing a namespace for mulitple SW registrations (logically, separate apps or separate parts of an app) from this handling, but I honestly can't imagine wanting to do that and not also scope things to a bit of URL space.

Closing.

dominiccooney commented 10 years ago

To summarize, there are three issues here. Did I miss any?

  1. An application hosted at multiple paths can't be controlled by a single Service Worker without taking over the prefix of the paths, for example /, which is really broad. By "hosted at", we mean the user loads that URL in a tab or frame; we don't mean "loads a subresource from" because Service Worker interception handles that case flexibly--it's the main resource URLs that are important.
  2. Using a Service Worker at /foo/bar for push events implicitly means not using a Service Worker at /foo for, for example, onfetch events. (Pick any pair of features.) The features are orthogonal, so the uses should also be orthogonal.
  3. How are Service Workers identified? onfetch must map a main resource URL to a Service Worker, but other features, such as push messages, could identify a specific worker a different way (some people proposed by registration.)

As as implementer, I'm concerned that proposals on this thread don't explain three things:

A. How do Service Workers' update and atomic "controlling" mechanism work with the proposed changes?

B. How does the browser identify the Service Worker(s) for onfetch? Scopes used to be single values fixed for a given registration; in some proposals they're dynamic sets.

C. Can the author determine how the system will act? For example in the existing system it is easy to identify with JavaScript which Service Worker, if any, will intercept an XHR from the page. Preserving this property would help us implement this in a layered way and write tests for it in JavaScript.

annevk commented 10 years ago

@slightlyoff could you explain why it would benefit? All storage areas are not scoped, but per-origin. The push part is also realized by names, even better so.

@coonsta I think @sicking gave answers to those questions in his first post, no?

A. By using a name as identifier. B. By going through the scopes associated with a given origin and then getting the associated service worker. C. I don't see why that's any harder.

Reopening as this is important to Mozilla and I don't think it's been fairly considered thus far.

slightlyoff commented 10 years ago

Apologies for the slow reply.

Logically the scope is about the "app". Using a name as an identifier creates a new problem: what if multiple SWs register for the same scope? Further, as the SW is a natural proxy for the "app", arguing that it's not useful (e.g., for rationalizing who is registering for a Push) doesn't feel true. The SW registration object becomes the natural locus for determining how to interact with background services for the current "app".

@sicking's example doesn't make sense to me, and in (long) conversations with @jakearchibald about this, we couldn't figure out a way to rationalize it. In apps I use which present calendars, something like /meetings either comes from calendar.example.com or is subsumed by the viewer in some other way (e.g.: example.com/calendar/meetings). To take another example, both Flickr and Plus show detailed photos or groups of photos (sets) as URLs which occur BELOW the viewer (e.g. https://www.flickr.com/photos/slightlyoff/sets or https://plus.google.com/photos/113757927151929258451/albums). A SW for the /photos/ apps would suffice in both cases.

Thinking through the URL design of a service, I COULD imagine having API endpoints at the same URL level as a rendering system, but I think that argues for using composition via importScripts() or, eventually if we hear pain from users, a list of potentially non-overlapping scopes. But I don't see how this will happen frequently in the wild. Our general advice with SW is to put multiple things on their own origins. Scopes are to solve a fiddly problem for power users. If Mozilla thinks there's something more to them or believes there's a large group of apps that will be caught out, I'd like to see examples and data. It's always the fastest way to convince me of anything.

What's causing me to want to close this again is that we haven't heard from any early adopters that this is an issue for them. Nobody we're talking to has this problem and we don't observe it in the wild.

What am I missing?

annevk commented 10 years ago

I think one thing you are missing (or at least not responding to) is that this solves the "addEventListener problem" as well. Through the proposed mechanism it is clear what services the developer wants to have notifications for and "is there an event listener" primitives are no longer required.

(It also seems a bit soon to talk about early adopters.)

jakearchibald commented 10 years ago

We currently use scope to determine:

  1. which ServiceWorker to dispatch a fetch event to
  2. when an installed ServiceWorker can be promoted to active (when all tabs within a scope are gone)
  3. when a ServiceWorker can be checked for updates

The proposed solution handles 1, perhaps 2 kinda, but not 3. My problems with the proposal:

  1. Easy to create a worker that never updates (we've gone to lengths to avoid this kind of lock-in)
  2. We still don't have an example of a site that needs two scopes
  3. Increased complexity by adding name on top of scope
  4. Need to handle two registrations with the same scope

I can kinda see we have these issues with the current API:

  1. No way to have one registration for /calendar/ and /meetings/
  2. No way to change a scope from /calendar/ to /meetings/ without losing things like push registrations
  3. Model assumes you want fetch events

I don't think we should entertain 1 until we have an example, but allowing an array of scopes sounds like a simple solution. 2 can be handled by registration.changeScope(newScope) if we think it's important enough. I'm not really bothered by 3, push messages that fail on tap when you're offline are a crime native frequently commits, background sync is pretty pointless without some kind of offline handling.

LarsKemmann commented 10 years ago

My apologies, I sent this as a possible example last week and it seems to not have made it onto the thread.

What Jonas is talking about is an app that doesn't have a root path. The app has roots at /a/ and /b/, so they would want the same SW, but /c/ is something different, and having a SW for the whole origin and a separate one for /c/ wouldn't work for some reason. Maybe that's something we should be worrying about, but I'd like to see a real-world example of that before we work on solutions, so we better understand the problem. Once we can see it, maybe we can fix it in a way that isn't removing the bottom block of the jenga tower.

This discussion is very interesting for me as I’m working on a set of offline applications right now that seem to fit somewhat into the scenario being described. (I’m making do with AppCache at the moment, based on Matt Andrews’s excellent guidance from FT Labs, which I understand helped catalyze SW). I should state up front that I haven’t read through the full SW spec yet (I’m kind of waiting for it to stabilize a bit first :)), so my understanding is at the novice level. Unfortunately my service won’t be publicly available for another few months, but hopefully I can provide enough detail to be valuable to the discussion. If what I’m working on can reasonably fit into the existing design, great! If not, perhaps what Jonas has suggested needs more consideration.

The system I’m developing is a factory management service. It currently has the following endpoints, and more will be added:

<host> redirects to <host>/manage by default for ease of use, although this may change in the future. That redirect ought to work offline as well (this is not currently possible AFAIK with AppCache since a manifest for / would conflict with a separate manifest for /manage). Intercepting requests to / is therefore necessary but different from intercepting /hmi (definitely) or /manage (potentially).

While /hmi should pretty much live in its own world, /manage and /admin would benefit quite a bit from sharing some common offline data (e.g., subscription information) as well as accessing some of the same notifications streams. Interception from a single SW for both /manage and /admin seems like it would then be a beneficial feature that I can’t currently take advantage of.

Hope that helps!

jakearchibald commented 10 years ago

This is great feedback.

What's the difference between /manage and /hmi, do they do different things or is it just a different interface?

Is a given device likely to hit all those URLs, or will "device class A" go to /manage whereas "device class B" would go to /hmi? What kinds of devices go to /admin?

You've going to need a ServiceWorker scoped to / to handle that redirect offline. Assuming /admin is for a subset of users, I guess that would be a separate SW. Not sure about the others yet.

LarsKemmann commented 10 years ago

/manage is a mobile+desktop app, whereas /hmi is intended for fixed touchscreen panels at the assembly lines. So /manage and /hmi are very different and will almost never be used from the same device class, though it should be possible e.g. when testing an HMI configuration (configurations are defined by IT staff in /manage). /manage and /admin, on the other hand, are generally visited by the same mobile+desktop devices.

LarsKemmann commented 10 years ago

In the not-too-distant future I do see the need to allow additional apps to be installed (in the mobile+desktop case) that will, as far as I can tell, always be presented under a single URL segment (like /manage or /admin, so the /calendar+/meetings issue doesn't apply to me). But they do need to communicate with each other. That actually becomes more important as the number of available apps grows - in terms of bandwidth, for example, it doesn't make sense that four or five different apps on a user's phone would all need to subscribe to notifications from the server. But also in terms of a user experience.

Maybe I can communicate my concern better by describing a similar issue that I actually experienced recently in a native context. My Windows Phone has a built-in Facebook integration, and I have the official Facebook and Facebook Messenger apps installed. Until recently, the Facebook app wasn't aware of the Facebook Messenger app's presence, and so I would get toast notifications from three different sources whenever someone messaged me. (That was fun. :)) The FB app has been enhanced to be able to pick up on Messenger's presence and now suppresses its own notifications.

Extend the same scenario to a mobile web app environment. Say the user has a variable subset of three available apps installed on a device (let's call them /dashboard, /sales, and /support). And let's assume each app integrates with the mobile OS to be able to show toast notifications, and is able to alert the user if a critical-priority customer incident occurs. (This seems to be a reasonable scenario.) We don't want to show a separate notification for each of the installed apps. Assuming that they know about each other, we can come up with a prioritization (based on likely use cases, and possibly user-configurable) for which app alerts the user. Let's say the order is /dashboard if installed, followed by /sales, followed by /support.

As I said I'm a novice in terms of SW, so I don't know for sure if this is relevant to the SW API generality issue (hence it's a separate comment from my previous one). As an engineer I just want to know that I can achieve this scenario without resorting to ugly hacks. :)

jakearchibald commented 10 years ago

Sounds to me you'd have 4 registrations, scoped to:

This is because each is a separate app that would manage separate caches. If I visit /manage/ I wouldn't expect it to cache resources only used by /admin/, which I may never visit.

The odd one out is /, which is only there to handle the redirect. A root URL that redirects to another path is usually a bad smell, but if more apps are added to the origin I guess / would become some kind of independant landing page.

annevk commented 10 years ago

Easy to create a worker that never updates (we've gone to lengths to avoid this kind of lock-in)

I don't understand. The client is required to check for updates after some period of time. That will remain to be the case.

We still don't have an example of a site that needs two scopes

It seems fairly reasonable that sites require such a thing, or that the scope it handles changes over time. Or you might have a couple of small applications that can easily share a service worker and push service. I don't see why we should be limiting here when we can avoid it.

Increased complexity by adding name on top of scope

It's not added on top. It's a key so the URL can change. Nothing to do with scopes.

Need to handle two registrations with the same scope

We already need to handle that.

jakearchibald commented 10 years ago

I don't understand. The client is required to check for updates after some period of time. That will remain to be the case.

No, we check for updates after navigation to a url in scope.

It seems fairly reasonable that sites require such a thing

I'd still really like an example. One example from the whole world wide web. If we can't find one, I think we're making a big deal over very little.

It's not added on top. It's a key so the URL can change. Nothing to do with scopes.

We're going from "scope & script url" to "name & script url & scopes". It's an increase in complexity.

Need to handle two registrations with the same scope

We already need to handle that.

Not really, we don't need to handle clashes right now. Registrations to the same scope are not new registrations.

LarsKemmann commented 10 years ago

Thanks for the reply @jakearchibald; the way you laid out the scopes makes sense. And yes, / could eventually become a landing page.

I'd still really like an example.

I think @annevk pointed to what I was trying to describe with my (admittedly still fictitious) "CRM apps suite" example of /dashboard, /sales, and /support:

Or you might have a couple of small applications that can easily share a service worker and push service.

It's likely impossible to find a non-fictitious example on the web at this point, simply because the necessary technology doesn't exist yet. ;) So no one has really put much effort into evaluating those kinds of designs yet. But aside from such a CRM apps suite, here's another scenario that would benefit from a shared push service: game distribution.

Imagine you're a distributor of HTML5 games (something like AddictingGames.com) and you want to implement a global ranking system and social interaction, something like Steam or Xbox Live, which suggests a shared SW. So now you've got a bunch of smaller apps that the user can install, and many of them are probably built on the same frameworks, or some home-grown engine that you provide to developers yourself as a perk for working with you and so you'd only want those things cached once. Of course each game will have its own local storage for assets.

I would say "name & script url & scopes" is needed here, or at least "script url & scopes". name for debugging purposes, script url as a global service offered by the distributor, and scopes is modified dynamically as the user installs or removes games (which can happen many times per session, given that they're smaller games).

If I've understood correctly, you've argued that then the scope for the SW should just be /. But that makes it more difficult for the distributor to do anything else in that URL space (like GOG.com, which recently added movie distribution to its lineup). And it's likely that some of the games will need their own service worker registrations to handle multiplayer scenarios.

Again, if that's actually possible to do with a less general design without imposing any very restrictive burdens on the developers of such a scenario, wonderful and please proceed with implementation. :)

KenjiBaheux commented 10 years ago

This is still a wontfix, removing impact MVP.

annevk commented 10 years ago

@KenjiBaheux it's still very much an issue from Mozilla's perspective, as far as I know.

jakearchibald commented 10 years ago

@annevk would be good to get some response to https://github.com/slightlyoff/ServiceWorker/issues/445#issuecomment-56801213, especially how we use scope for updates. Would still love a real-world example of a site that would be hit by this issue too.

annevk commented 10 years ago

I don't see why we'd lose the invocation of Soft Update in Handle Fetch. Apart from whether or not a site might need to update its scopes over time or has multiple non-overlapping scopes, there are other advantages outlined in https://github.com/slightlyoff/ServiceWorker/issues/445#issuecomment-54394069 that make this worth it.

ehsan commented 10 years ago

@jakearchibald, does this count as an example of an app with two entry points under two separate scopes?

https://docs.google.com/spreadsheets/d/docid https://docs.google.com/a/mozilla.com/spreadsheets/d/docid

jakearchibald commented 10 years ago

@annevk if you've specified scopes, we can still do updates when those scopes are visited. But if there are no scopes by default, you get lock-in by default, and that's baaaaad.

@ehsan the latter is redirecting to the former for me

ehsan commented 10 years ago

@ehsan the latter is redirecting to the former for me

The redirect only happens if you don't have a real document URL, please try with a real one (I'm sorry that I can't share a real URL here, for obvious reasons!). You can use any apps URL /a/foo.com/....

annevk commented 10 years ago

@jakearchibald no you don't. The service worker still needs to be updated daily and there's no reason that won't happen. Or did we somehow change the design and lift that requirement?

jakearchibald commented 10 years ago

@annevk this was never the design as far as I'm aware, it's a misunderstanding of the 24 hour limit on max-age when it comes to updates. Let's move that part of the discussion to #514

ehsan commented 10 years ago

@jakearchibald now that we have a real website that demonstrates that this is an actual issue, can we please address this?

jakearchibald commented 10 years ago

Recognise the scoping issue, that could be fixed in the current api.

Let's say there's a navigation to https://docs.google.com/a/mozilla.com/spreadsheets/d/docid:

navigator.serviceWorker.register('/spreadsheets/sw.js', {
  scope: '/spreadsheets/'
}).then(function(reg) {
  var newScope = window.location.pathname.replace(/\/spreadsheets\/.*/, '/spreadsheets/');
  reg.subScopes.add(newScope);
});

Something like subScopes (bikeshedding welcome) would allow these to be dynamically added.

If we take out the bottom brick of the jenga tower, it all falls down, and we may be able to rebuild it with fetch as an optional component. I really don't think it's worth it. As we've seen discussed on the "Push without ServiceWorker" thread, this stuff is deeply tired to offline & caching. If you don't want to handle offline, don't listen for 'fetch' events.