mkruisselbrink / navigator-connect

Apache License 2.0
25 stars 8 forks source link

Use ServiceWorkerRegistration and its messaging design as-is option #38

Closed jungkees closed 9 years ago

jungkees commented 9 years ago

I just came up with another idea. I think if we get ServiceWorkerRegistration object from n.c(), things are getting simpler and easier. The registrations are persistent itself. (We don't have to care about any independent lifetime of the whatever the connection object is.) Once connected, the client and the service can use the service worker messaging as-is, which means they can postMessage to any of the available service workers (installing/waiting/active.) For the other types of the clients - window/worker/sharedworker - we may not provide persistent connection when the clients themselves are unloaded. But as long as they are alive they can go on with the messaging with their connected service. WDYT?

// client.js connects to service
navigator.connect('https://myservice/').then(registration => {
  registration.installing.postMessage('To installing worker.'); 
  // or clients can choose other service workers in the registration
});

// service.js accepts it upon connect event
self.onconnect = e => {
  if (e.origin == 'https://myclient' && e.target == 'https://myservice')
    e.accept('myclient'); // tag it with a name so it can search later
  else
    e.reject();
};

// client.js can later get the connection from the collection
// and postMessage to any service worker it wants
// (service.js can also use self.connections in the same way with other options args)
self.connections.matchAll().then(registrations => {
  registrations.forEach(r => {
    if (r.scope == 'https://myservice') {
      r.active.postMessage('want-to-do-this-action');
    }
  });
});
[Exposed=(Window, Worker)]
partial interface Navigator {
  Promise<ServiceWorkerRegistration> connect(USVString url); // get the service's registration from connect
};

[Exposed=ServiceWorker]
interface ConnectEvent {
  readonly attribute USVString target; // target's url
  readonly attribute USVString origin; // client's origin
  void accept(optional DOMString tag); // optionally can tag the connection with a name
  void reject(); // can explicitly reject
};

partial interface ServiceWorkerGlobalScope {
  readonly attribute Connections connections;
  attribute EventHandle onconnect;
}

[Exposed=ServiceWorker]
interface Connections {
  Promise<sequence<ServiceWorkerRegistration>> matchAll(optional MatchOptions options);
};

dictionary MatchOptions {
  USVString scope;
  DOMString tag;
  ConnectorType type = "all";
};

enum ConnectorType {
  "client",
  "service",
  "all"
};
mkruisselbrink commented 9 years ago

It seems a bit weird to expose ServiceWorkerRegistration like this... For one almost every attribute/method on SWR would need to be closed down to not be available for these cross origin connections, similar to how any Window property throws a SecurityException for most properties cross origin. But I'm not sure that worked out that great for Window (the spec even has this comment: "This section describes a security model that is underdefined, imperfect, and does not match implementations. Work is ongoing to attempt to resolve this, but in the meantime, please do not rely on this section for precision.")

And given that many other specs actually extend ServiceWorkerRegistration, reusing that type and speccing it in a way that makes sense just seems really complicated.

Also an unfortunate consequence of using ServiceWorkerRegistration to represent a connection is that ideally we'd like to use navigator.connect (or a very similar API) for connections to services/things that aren't necessarily service workers as well.

Also while forcing a client to pick which service worker version to send messages to solves the problem of how to determine where messages go, I don't think a client really is in any position to make this decision.

Finally I'm not sure how you're representing a connection that was made from a window (that might not even have a service worker) to a service as a ServiceWorkerRegistration? Or is the idea that only service workers can make connections to cross origin service workers?

jungkees commented 9 years ago

For one almost every attribute/method on SWR would need to be closed down to not be available for these cross origin connections, similar to how any Window property throws a SecurityException for most properties cross origin.

I think it'd be clear to devs that getting a SWR object from n.c() would not mean its attributes/methods can be accessed from within cross-origin clients. n.c() would be thought of a way to get a permission to use the messaging capability btw cross-origin SWs. In addition, here's a bit of discussion about exposing an object to an untrusted context: with the CacheStorage object getter in untrusted origin we decided to expose the object itself and make their methods throw/reject SecurityError exception.

Also while forcing a client to pick which service worker version to send messages to solves the problem of how to determine where messages go, I don't think a client really is in any position to make this decision.

A client can check the state of the service workers in a registration to decide when (with which service worker state) to start the communication, and the service can decide what to provide based on its installation state. (e.g. just send back a message to the client to say "Hold on until I'm activated" and match the connection from within onactivate and send a message to the client to start over with the actual service.)

Finally I'm not sure how you're representing a connection that was made from a window

The returned SWR object itself represents a connection but not a persistent connection. We may not provide a persistent connection for them but they can do the messaging with their connected service during their lifetime.

mkruisselbrink commented 9 years ago

The returned SWR object itself represents a connection but not a persistent connection. We may not provide a persistent connection for them but they can do the messaging with their connected service during their lifetime.

What I meant to question is how would a connection from a window to a service worker be represented on the service worker side. In your proposal all connections to a service worker are represented by SWR objects, which doesn't work when the other side of the connection isn't or doesn't have a service worker. So either you're dropping the ability to connect from non-service worker based websites, or something in the API needs to be different.

jungkees commented 9 years ago

It's a straw man proposal. We'd have to discuss and tweak the API for sure.

I think SW's Client interface is something we can think of as a starting point. Currently in SW's case, the .source attribute of a message event fired in SW context returns Client object, and self.clients.matchAll() returns an array of Client objects. Client is basically designed to represent document and other worker clients referenced from a service worker. And client.postMessage() is the very method we'd want to expose in service workers. So Connections.matchAll() should somehow be able to resolve with an array of Client objects or we may add such method there I guess.

jungkees commented 9 years ago

Closing as we determined to work on https://github.com/mkruisselbrink/navigator-connect/issues/39.