Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use addEventListener instead of watch #152

Closed
kenchris opened this issue Sep 27, 2018 · 23 comments
Closed

Use addEventListener instead of watch #152

kenchris opened this issue Sep 27, 2018 · 23 comments
Labels
Origin Trial Issues that would be good to solve before OT

Comments

@kenchris
Copy link
Contributor

kenchris commented Sep 27, 2018

I don't recall why we invented the watch, but it feels a bit like its own thing, instead of being aligned with the platform. We could go with a startNotifications() approach like WebBluetooth.

navigator.nfc.addEventListener('exchange', message => {
  for (let record of message.records) {
    let article =/[aeio]/.test(record.data.title) ? "an" : "a";
    console.log(`${record.data.name} is ${article} ${record.data.title}`);
  }
},{
  url: document.baseURI,
  recordType: "json"
});

navigator.nfc.startNotifications();

This means that all of our values in the options dict needs to qualify as a separate handler (ie if you want to remove you need the same options dict and same function

@zolkis
Copy link
Contributor

zolkis commented Sep 27, 2018

We could add a filter to watch(), i.e. options.
It would be nice if addEventListener() supported options.
Next possibility is Observables (not that it would be aligned with the platform, either).
Or just do away with options.

@kenchris
Copy link
Contributor Author

kenchris commented Sep 27, 2018

addEventListener() does support options today :-)

https://dom.spec.whatwg.org/#dictdef-addeventlisteneroptions

@zolkis
Copy link
Contributor

zolkis commented Sep 27, 2018

But not those NFC options. Is it better to extend a standard dictionary with NFC-specific properties (making it apparently aligned), or have a separate call with its own, admittedly different semantics?

@kenchris
Copy link
Contributor Author

kenchris commented Sep 27, 2018

I think it is better extending. Creating something that looks similar but has edge cases that works differently isn't really good.

Other option would be (feel free to bikeshed names)

const filter = navigator.nfc.createReadFilter({
  url: document.baseURI,
  recordType: "json"
});

const onMessage = message => {
  for (let record of message.records) {
    let article =/[aeio]/.test(record.data.title) ? "an" : "a";
    console.log(`${record.data.name} is ${article} ${record.data.title}`);
  }
};

filter.addEventListener('exchange', onMessage, {capture: false});
filter.startNotifications();

@zolkis
Copy link
Contributor

zolkis commented Sep 27, 2018

I think it is better extending.

Maybe so, but what is the general consensus or mechanism on extending standard dictionaries in the platform? Do we need to register those somewhere? (imagine multiple specs adding properties which may clash)

Creating something that looks similar but has edge cases that works differently isn't really good.

Extending a standard dictionary looks exactly like this. So it's a counter-argument.

const filter = navigator.nfc.createReadFilter({

So we'd need to specify a new NFCFilter interface that contains the startNotifications() method?
Isn't that more complex than just using a watch() method?

One way we could do it is define NfcWatchOptions and NfcPushOptions as interfaces (strip the Options from the name), add the startNotifications() method, that would return an object that has the cancel() method.

@kenchris
Copy link
Contributor Author

kenchris commented Sep 27, 2018

Extending a standard dictionary looks exactly like this. So it's a counter-argument.

It acts exactly the same. It should honor all the options from AddEventListenerOptions

So we'd need to specify a new NFCFilter interface

Sure, and it's more similar to WebBluetooth which has getCharacteristic() which returns an object with addEventListener and startNotifications()

watch feels really alien to me

Would be really interesting hearing opinions from @domenic @reillyeon

@zolkis
Copy link
Contributor

zolkis commented Sep 27, 2018

On the other hand we can't really expect to handle NFC interactions with Bluetooth interactions.

@reillyeon
Copy link
Member

I think in general APIs are migrating away from explicitly passing callbacks in favor of either Promises for one-shot events or event listeners for repeating events. I like the idea of creating an NFCWatch (or NFCReadFilter) and adding an event listener to that. Unless it is absolutely necessary a startNotifications() method could be overkill. It is useful for Web Bluetooth because turning on notifications requires communicating with the device and so can fail in more interesting ways. addEventListener()/removeEventListener() could be sufficient for turning on and off notifications.

@zolkis
Copy link
Contributor

zolkis commented Sep 27, 2018

Yes, something like this would do:

[Constructor(NFCWatchOptions options), SecureContext]
interface NFCReadFilter: EventTarget {
  readonly attribute USVString url;
  readonly attribute NFCRecordType recordType;
  readonly attribute USVString mediaType;
  readonly attribute NFCWatchMode mode;
  attribute EventHandler onread;
};

interface NFCReadEvent: Event {
  readonly attribute NFCMessage message;
};

or if we want to bind it to the navigator.nfc object, we could have a factory method instead of constructor (like @kenchris suggested).

@zolkis
Copy link
Contributor

zolkis commented Sep 28, 2018

I still have the question from above:

what is the general consensus or mechanism on extending standard dictionaries in the platform? Do we need to register/link those somewhere? (imagine multiple specs adding properties which may clash)

@kenchris
Copy link
Contributor Author

Yes, that is a fear, so we should be careful when doing so, and work with the people who specced the upstream feature.

Luckily, we also pass these specs through the TAG review which should catch most of these.

With the NFCReadFilter we are not really doing that though.

For factory function, I think that would make more sense for the NFC object if we expected to have multiple of those, and would be in line with Generic Sensors. But it is not really a sensor and we don't expect to have multiple (if so a default could be chosen by the browser, I guess, like camera)

@zolkis
Copy link
Contributor

zolkis commented Sep 28, 2018

I am fine with factory methods, but if we wanted constructors it's well possible.

Filters are independent of NFC adapters, so no real need to bind to the nfc object syntactically (only in runtime: when when filters are constructed, implementations/algorithms should do the standard checks for support, secure context/permission etc and throw if there is an issue).

Even if there are multiple NFC readers, we have agreed to have an additional API in the future for enumerating/selecting them for write (currently all of them can read, and any of them is allowed to write, so if they are too close, it might happen to attempt writing a tag from two adapters, which is anyway a deployment problem and should be avoided). So at any point implementations will know how to handle the NFC adapters.

However, the API would be perhaps cleaner with the filter creation under the nfc object, since after this change it would only contain the push() (to become write()?) method. I suggest keeping the watch() name for a factory method of creating read filters, but I am fine with createReadFilter() as well.

[SecureContext]
interface NFC {
  Promise<void> push(NFCPushMessage message, optional NFCPushOptions options);
  Promise<NFCReadFilter> createReadFilter(optional NFCFilterOptions options);
};

Note that NFCPushOptions may now include an AbortSignal bound to an AbortController. The limitatation is to be able to cancel a push only in the way it was created (not like create with any and cancel only for peer push).

@domenic
Copy link
Contributor

domenic commented Sep 28, 2018

You shouldn't modify a foundational method like that of the platform for NFC-specific use cases. So the "Other option" from #152 (comment) makes more sense to me. (Although I'm unsure why you'd need to do { capture: false }, since it's not in a DOM tree anyway.)

Otherwise, I strongly agree that inventing your own event listener-like infrastructure is not good, and following the precedent set by other specs makes more sense. Especially if we already have such a strong parallel with Web Bluetooth.

or if we want to bind it to the navigator.nfc object, we could have a factory method instead of constructor (like @kenchris suggested).

Constructor is preferable if possible! I wonder why Web Bluetooth went with something else. Does creating it have to be async? Your last IDL block has it as promise-returning, and it's not clear to me why.

@reillyeon
Copy link
Member

Constructor is preferable if possible! I wonder why Web Bluetooth went with something else. Does creating it have to be async? Your last IDL block has it as promise-returning, and it's not clear to me why.

BluetoothRemoteGATTCharacteristic does not have a constructor because you have to interrogate the device in order to determine what characteristics are available. I generally support using constructors however it becomes awkward to use when you can construct an object representing a capability that doesn't exist. Then you need a mechanism to report that the object is invalid rather than refusing to create it in the first place.

@zolkis
Copy link
Contributor

zolkis commented Sep 29, 2018

Does creating it have to be async? Your last IDL block has it as promise-returning, and it's not clear to me why.

The watch() algorithm was asynchronous, but indeed the user intent to create a read filter does not need to wait until NFC is configured, so we could create the object right away. We could handle security/permissions/support errors with exceptions. We could eventually include an onerror event on the NFCReadFilter object to report NFC/read errors.

@kenchris kenchris added the Origin Trial Issues that would be good to solve before OT label Nov 30, 2018
@kenchris
Copy link
Contributor Author

kenchris commented Dec 4, 2018

I agree with @reillyeon we should just go for a version with a constructor. Maybe it is fine just calling it NFCReader

[Constructor(NFCReaderOptions options), SecureContext]
interface NFCReader : EventTarget {
  readonly attribute USVString scope;
  readonly attribute NFCRecordType recordType;
  readonly attribute USVString mediaType;
  readonly attribute NFCReaderMode mode;

  attribute EventHandler onread;
};

interface NFCReadEvent: Event {
  readonly attribute NFCMessage message;
};

interface NFCWriter {
  Promise<void> push(NFCMessageSource message, optional NFCWriterOptions options);
}

@zolkis
Copy link
Contributor

zolkis commented Dec 4, 2018

The name NFCReader confused me a bit, since it's meant to be only a read filter originally.

@kenchris
Copy link
Contributor Author

kenchris commented Dec 4, 2018

It is a reader, but it only reads a subset... If we get rid of the namespace and make these constructable (current recommendation) then we need to find good names for both.

It might also name sense to name these more specialized like NFCTagReader - that is another thing to think about

@zolkis
Copy link
Contributor

zolkis commented Dec 4, 2018

We have one reader and multiple filters.

@kenchris
Copy link
Contributor Author

kenchris commented Dec 4, 2018

Sure, in hardware, but you can create as many instances as you want, same with the writer. I don't have better suggestions. A file reader is also only reading one file :-) We could of course call it an Observer. NFCTagObserver

@reillyeon
Copy link
Member

My interpretation of the name is that when the author wants to read from NFC tags they create an NFCReader.

There's a similar pattern in other APIs, for example, you can create multiple Accelerometer objects even though the hardware only has one accelerometer. The browser is responsible for mapping the web exposed object to the right platform object.

@littledan
Copy link
Contributor

From the outside, NFCReader and NFCWriter sound like good names to me, and the EventTarget "filter" pattern makes sense. I agree with @reillyeon that instances of a class don't need to correspond 1:1 with pieces of hardware.

Thinking of a filter that outputs an EventTarget reminds me of patterns with Observables, which might be nice here from a perspective making an interface which works in Node.js as well, though WebNFC is a bit too tied to origins to make sense unchanged there. (I'm happy to see that the origin model is working out here!)

@zolkis
Copy link
Contributor

zolkis commented Dec 5, 2018

I like the Observables pattern and NFCTagObserver name as it's clear and the semantics is conveyed more precisely, but if there is no confusion about the NFCReader name (meaning actually read-filters), NFCReader seems to be more aligned with the Web Platform.

littledan added a commit to littledan/web-nfc that referenced this issue Dec 5, 2018
This refactoring should make it easier to change the interface to
NFCReader, to address w3c#152

Along the way, closes w3c#156.
littledan added a commit to littledan/web-nfc that referenced this issue Dec 6, 2018
This refactoring should make it easier to change the interface to
NFCReader, to address w3c#152

Along the way, closes w3c#156.
kenchris pushed a commit that referenced this issue Dec 6, 2018
This refactoring should make it easier to change the interface to
NFCReader, to address #152

Along the way, closes #156.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Origin Trial Issues that would be good to solve before OT
Projects
None yet
Development

No branches or pull requests

5 participants