-
Notifications
You must be signed in to change notification settings - Fork 67
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
Consider removing .nfc namespace #67
Comments
Same as #66 |
@kenchris Someting like this? partial interface Navigator {
Promise<NFC> requestNFC();
};
interface NFC {
push
cancel
watch
unwatch
}; How would you check if UA has WebNFC implemented? if (!navigator.requestNFC) {
...
} |
Works for me. If Web NFC is not implemented, |
Note that in the example above NFC is the same thing as NFCAdapter used to be. If there are multiple NFC adapters, implementations MAY choose one of them based on settings/heuristics, or pop up a dialog for choosing one of the adapters. Implementations will need to do things with this multiplicity in mind. I think the spec should include a note saying that the NFC object should be a singleton per physical NFC adapter and script execution context. Subsequent calls on requestNFC() would return the same object if it was requested before, or otherwise construct it again. |
I would really like to discuss this at the F2F... requestNFC sounds weird to me. Maybe adapter is not the best word, but maybe we can find something better than "NFC" for describing it. |
Replacing navigator.nfc with navigator.requestNFC() does simplify things. If you don't like the NFC name, it could be requestNFCAdapter, and stick with NFCAdapter instead of NFC. I don't care about the names that much :). But this really needs to be discussed and synchronized with the Bluetooth guys, since it's the same problem for them. @jyasskin |
@zolkis To my knowledge, there are no known platforms that have such HW configuration (>1 nfc adapter). Here is an example: var nfcAdapter;
var watchId;
// UA shows dialog (select adapter => adapter 1 is selected)
navigatior.nfc.requestAdapter().then( (adapter) => {nfcAdapter = adapter; } );
nfcAdapter.watch(...).then((id) => {watchId = id;})
nfcAdapter.pushMesage(...);
// UA shows dialog (select adapter => adapter 2 is selected)
navigatior.nfc.requestAdapter().then( (adapter) => {nfcAdapter = adapter; } );
nfcAdapter.pushMesage(...);
nfcAdapter.unwatch(watchId); // Side effects?
Proposal: |
We should avoid enumeration... Supporting multiplicity does not change the API with the pattern above. As for the example, we have 2 different adapters that can work in parallel.
However: if we decide to not support multiple adapters, no need to change the API. Even in that case, it should be noted in the spec that an NFC adapter is a singleton per physical adapter+script execution context. |
You can just say, that first invocation of requestAdapter per browsing context MAY show a selector dialog allowing the user to pick an adapter, but after that it will never change. I think that is a fine compromise. |
@kenchris Fine with me. After first requestAdapter, NFC adapter should not be changed during lifetime of browsing context. |
Basically it is implementation choice whether:
In all of these cases the implementation should still do the same thing, expose a singleton for NFC adapter, or return an error. Disposal of the adapter is described in the spec. |
If adapter is changed to a second one, we have the following options:
I see more problems/inconsistencies with policy 3 than either with policy 1 or 2. In addition, we discussed with @alexshalamov that a non-fingerprintable id (like "nfc1", "nfc2" etc) could be included to an NFCAdapter, in order that clients could tell them apart. |
Well, the id etc could always be added later in the spec if needed. Let's just keep it open and make sure it is always a singleton |
I would suggest policy 2 from above. I think it has least amount of problems. Also, an id is not needed in that case. If you agree with that, I can include this in the |
2 is fine |
In the F2F, I proposed moving the contents of the That is,
If we eventually find a need for the developer to distinguish between multiple adapters, we can add that notion into the API then, but leaving it out could make the initial API simpler. |
When pushing, it is non-deterministic on which adapter the push/write happened, and the web page would not know either. Basically that is not such a big problem. If it was the wrong one, and timeout happens, the page can ask again, and again,... until the user realizes the wrong adapter was used. Which is not much different from the case when we would have had 2 adapter objects. When reading from 2 adapters far enough to not have interference/race, we don't have a problem merging them. When they are too close, it gets non-deterministic, but similarly as above, in the end the use case is handled the same way. In summary, we can try to implement this, and modify the spec accordingly. This would also have the advantage of attaching permissions to the leaves, i.e. navigator.nfc.watch() and navigator.nfc.push(). |
However, it is tricky to handle errors with the merged model. If you have multiple adapters, and a push, or setting a watch succeeds on some of them and fails on others, how do we handle the situation? If we say succeeding on any adapter is success, from where would the user know which adapter is usable? Without explicit adapter enumeration and management (which I would like to avoid), what is better:
Note that 1. is possible with the navigator.nfc model as well, e.g. 1.a.) by using a Web NFC specific policy, like:
or 1.b.) by using an explicit (new) Please choose between 1.a., 1.b., 2, 3, or propose another policy. Personally I would be most comfortable with 1.b. (or 1.a. in the first version), and I would like to avoid using all the adapters in parallel (2 and 3). In this case the API would look like: partial interface Navigator {
readonly attribute NFC nfc;
};
interface NFC {
Promise<void> push(NFCMessage message, optional NFCPushOptions options);
void cancelPush(NFCPushTarget target);
Promise<long> watch(NFCWatchOptions options, MessageCallback callback);
Promise<void> unwatch(long id);
// Promise<void> selectAdapter(); // triggers a dialog
}; (NB. The other proposed changes, e.g. content handling and removing push target etc. will be discussed separately) |
I think it makes sense to take the first result from any adapter and resolve the promise with it. In most cases, that would mean option (3), that if any adapter fails to set a watch or publish a message, the overall promise would reject with that adapter's result, but we wouldn't want to wait for a failure past any adapter receiving or successfully pushing a message. With this model, a user with multiple adapters, one of which is broken, may have some trouble figuring out which one to disconnect (although we might be able to help with the rejection message), but I expect that to be less common than a user with multiple working adapters who's confused by which one was picked as the default or by the selection dialog. (And both of these will be much less common than the user with just one adapter, who's the only person developers will actually write code for.) |
I agree that in 95+% of the cases there will be one adapter. However, the policy you suggest is too obscure. Probably hard to formulate as exact algorithmic steps, but beyond that I am more scared of letting developers and users deal with all possible combination of errors that may occur with various scenarios of I/O and radio races, without any slight hint where it went wrong. IMO that would bring a really bad user, and developer experience. A priority list like external, internal, and then (if ever needed) a selection dialog for any other case would make it very clear to the user which adapter to use. Also, when something goes wrong, both users and developers know where and what happened. If there is one adapter, or even when there is one internal and one external adapter, this works as well as your proposal, and covers a tiny bit more use cases, say 99+% of them :). Indeed we'd lose the ability to handle concurrent writes and reads across all connected adapters, but I doubt we have a use case for that. Quite the contrary, with NFC use cases (and not only payment, though it's in future versions) you want to be certain which adapter you are using. Also, it leaves an open path forward to support adapter selection on user demand, while keeping the simple interface which was the primary goal after all. |
I'm in favor of option (3). Promise selectAdapter(); function also degrades API usability.
I'm for simple API that doesn't have quirks due to unrealistic scenarios. |
I did not propose selectAdapter() at this step, only a policy to select a default adapter, which sometimes would pop up a selection dialog if there are multiple external adapters. Not very likely to occur, but this would make a clear situation: we use one adapter, and we know which one. But if we speak about selectAdapter():
The API is the same simple in all cases, and all options will have quirks. I am for the quirks which can be resolved, not the ones for which we hope for the best. |
We can also have a 4th option: forget the user dialog, and just have a policy with a priority list. Select the internal adapter, unless there is an external, and in case there are more, select the last which was plugged in. The essence here is to always have one adapter active with Web NFC, and to know which, by policy in this case. The concurrent use of adapters has the problems described above. I wonder how should we write the steps vs error handling. If you are adamant on this is what you want, try writing the push algorithm with this in mind and I will be happy to review it. I can also give it a try. Watches and push buffers can be made independent from the adapter, reflecting developer choice. It ultimately boils down to whether is having a user selection dialog a problem, and whether we explicitly want concurrent handling of multiple adapters. |
Let's not add a selectAdapter or similar method. Developers would rarely use it, which means that it is as good as not having it in the first place and as good as not supporting multiple adapters |
We can do without selectAdapter, and we can support multiple adapters as well as one with a priority list. No change to the API is needed. But an algorithm on how to handle multiple adapters when they are present should be in the spec, even if we restrict functionality to one and the same adapter per browsing session. |
So I have got answer to one of my key questions: do we want/tolerate selection dialog. The quite unanimous answer has been no. We can take that as a group decision. If ever the need arises, it is quite easy to add. The other key question on which we need group decision: in the rare case we have multiple adapters, should the NFC object be bound to only one of them, or allow working with all of them in parallel? In native world, adapters work in parallel. Any of the adapters which get in proximity with an NFC device (peer or tag) will work both on pushing and reading. However, they will have different buffers and will not present problems that we would have with Web NFC if we allowed parallelism. I do not like the narrative that "let's disregard the simultaneous push error mess since it's not likely to happen". That may be fine in an implementation, but not in a spec. We could say in the spec that implementations are free to choose the policy of handling multiple adapters, either by exposing only one of them and make the selection, or by handling in parallel. However, even then the spec should have an algorithm either for selecting an adapter (standardize the priority list), or on how to handle errors. I do not have a good solution for the latter. I have tried to write the relevant push steps and I am stuck. So if you would like to see multiple adapters in work, please specify the push steps. Unless we can do that with reasonable correctness, IMO we have no other choice than selecting an adapter. We could do that the first time NFC functionality is accessed, either through push or watch calls. Then, we could support reacting to plugged in new adapters by applying the priority list based policy, or just say in the spec the page should be reloaded for applying that policy. Make your choices. |
One correction to the above: in native, multiple adapters likely won't actually work in the same time and we will get errors from all of them, which is a clean situation: we reject the push Promise. The question is the following: can some of them succeed and some of them fail, because that is the main problem in resolving or rejecting the push Promise. If we choose option 3 and reject on any error, it can be misleading, since some of the writes might have happened, and while we report error, we have already changed the data on some of the tags, so we lied. Option 2 is the reverse: we report success, whereas some of the writes didn't happen. While you can say we don't care about that case because it is unlikely, that is to say we don't care if the algorithm is correct, I think it would be better then to expose only one adapter at any given time. It is true this would limit read functionality, and add more things to implement (priority list policy). If someone can formulate reasonably correct push steps, I would be fine with the parallel adapters idea as well. But we need to make a choice, and soon. |
For multiple adapters (should be applicable for N adapters): One pending push operation: push(tag) Case 1: One physical NFC tag OR one NFC tag + one peer
Case 2: Two physical NFC tags are activated simultaneously OR one tag is being written while another enters RF field generated by second adapter
Two pending push operations: push(tag) + push(peer) Case 3: One physical NFC tag
Case 4: Two tags (similar to Case 2)
Case 5: One peer
Case 6: Two peers (similar to Case 2, but with NFC peers)
At the moment I'm rewriting POC with following interface: partial interface Navigator {
readonly attribute NFC nfc;
};
interface NFC {
Promise<void> push (sequence<NFCRecord> records, optional NFCPushOptions options);
Promise<void> cancelPush (NFCPushTarget target);
Promise<long> watch (MessageCallback callback, optional NFCWatchOptions options);
Promise<void> unwatch (long id);
} Note that cancelPush returns promise, since operation is asynchronous and I need to check from platform whether it is possible to cancel push. // Watch options are defaulted
navigator.nfc.watch(callback); |
The NFC interface looks good to me. It is also in line with Travis' comments. Actually I have already updated the spec with that in my fork, except the watch parameter reversal. I have a question regarding your examples. In Case 2 for instance, we have one Promise returned. Now say one push (write) succeeded and one failed. We reject the promise. The developer may think the tags were not modified, but one of them was. Therefore unless we can unroll partial success as it were a transaction we cannot say the push failed. Nor can we say the push succeeded. One of them succeeded and the other one failed. We would need 2 Promise objects to convey that. If we would return a sequence of Promise objects, one per adapter, we would need to identify them. That is not trivial to do without fingerprintable information that are visible to the page (or if I am wrong here, suggest a solution). Even then I'd not go there. If we choose to not identify the adapters, that may also work, at least the developer knows what is the state, even though it cannot be mapped exactly to adapters. Anyway, it kind of sucks returning an array of Promises. I see only 2 acceptable ways out: 1. undo the successful write (transaction model) and reject, 2. work with only one adapter. If we choose 1, we need to specify the transaction. I would like to understand, is it more complex to work with one adapter than with multiple? Or we don't want to lose the parallel reading functionality? |
I don't think that anyone cares about unroll. Why not just create an exception/error like PartialWriteException or similar |
Yeah, transactions and rollback look like overkill, considering the very small likelyhood we run into this corner case: they are not worth implementing. Then, Jeffrey's suggestion of handling adapters in parallel has more appeal than selecting a single adapter and deal with multiplicity by applying a policy (or selection dialog), and definitely better than not dealing with multiplicity at all. I think that was a nice cut through, kudos. I just have a problem with writing the push steps for it. Since I cannot imagine a use case which would require simultaneous tapping of multiple tags, could we say in the spec that multiple adapters are supported as long as only one of them is used by the user at any given time, and concurrent use will likely work with reads, but may result in push errors in some of the adapters which means partial success? And in the case of an error, we reject, and the user should try again? Then we could take option 3, as it got most votes so far. |
I am fine with that. I also like this option the most |
Good. Then we arrived to a consensus and we can use the simple API that @alexshalamov suggested. I will prepare the spec change with these. Thanks @jyasskin for the "merged" adapter suggestion; once its details were sorted out, it settled a long debate. |
In case 2, I'd probably have the UA take the following steps, if the underlying APIs make it possible.
I'm suggesting to fulfil if any succeed because that tells the developer whether the state of the external world changed. |
Now that is option 2 (resolve if any pushes succeed), whereas previously you supported option 3, i.e. reject if any of the writes failed. If we resolve when any success happen, we have no way telling the user something went wrong and please repeat the operation (and if possible, not concurrently). So at the moment I find option 3 a bit more useful because that feedback, though it is inconsistent on errors as well as option 2. |
In #67 (comment), I missed the fact that, even if adapter A starts writing before adapter B starts writing, it might be impossible to cancel the publication on adapter B between when adapter A starts writing and when adapter B finishes writing. My guideline of "take the first result" stands, but since there may be many results, we have to fall back to some other guideline. It's not really important which option you pick for this race condition: it's not going to happen a significant amount out in the wild. I'd still lean toward reporting that a side-effect happened, but going the other way isn't going to hurt anyone. |
Then we could use the suggestion from @kenchris : reject the Promise with a new error type like PartialWriteException (find a good name) or DataCloneError, and then we know it is a partial failure (or success). When all operations fail, we reject with NetworkError (which is the error we use for regular push failure). |
Another principle is to avoid making developers write code to handle cases that almost never happen. They mostly won't do it. Try to make the code they will write Just Work. |
Fixed by #88. |
https://github.com/w3ctag/spec-reviews/blob/master/2015/10/nfc-feedback.md
The text was updated successfully, but these errors were encountered: