nips icon indicating copy to clipboard operation
nips copied to clipboard

NIP-07 command queue

Open Egge21M opened this issue 10 months ago • 10 comments

This addition to NIP-07 introduces a command queue. This is a common technique in Javascript libraries that are loaded asynchronously from a CDN. It allows API consumers to queue up calls even before the library (or in this case) the provider is present on the page and avoids all race conditions.

window.nostr = window.nostr || { cmd: [] };

window.nostr.cmd.push(() => {
  const pk = window.nostr.getPublicKey();
  // do something with pk
});

Once the provider has been loaded and initialized it invokes all queued callbacks and replaces the pushmethod to instantly invoke future calls to the queue:

// Setup code here
// ...

for (let i = 0; i < window.nostr.cmd.length; i++) {
  window.nostr.cmd[i]();
}

window.nostr.cmd.push = cb => cb();

Egge21M avatar Apr 14 '24 10:04 Egge21M

this seems like a good idea and doesn't add a lot of complexity but could be very useful.

pablof7z avatar Apr 16 '24 11:04 pablof7z

Isn't this better done on the app side instead of on the extension side?

I think maybe we should specify the place of a callback NIP-07 extensions should call when they are loaded, so apps can do stuff like:

window.onNip07Loaded = () => {
  // run the queue
}

let queue = []
queue.push(() => {
  window.nostr.getPublicKey()
})
queue.push(() => {
  whatever()
})

Then when the extension is loaded it simply calls

window.onNip07Loaded()

fiatjaf avatar Apr 16 '24 19:04 fiatjaf

Isn't this better done on the app side instead of on the extension side?

Why? Either way there is some code change necessary on the extension side. The command queue is a very common and simple approach and doesn't require much boilerplate on the consumer side. In the end what you propose is the same thing, but written in different style.

Edit: Actually the biggest difference is, that I proposed a standardised queue as part of the nostr object that can be used by website code but also third party libraries alike. It just works

Egge21M avatar Apr 17 '24 03:04 Egge21M

Thinking more about this I don't think nostr.onNip07Loaded is a good idea, as it could be overwritten easily. If two different parts of an app (for example the website and a third-party widget) used it, they could run into race conditions again. The solution to this is usually to add an event and an event listener, but that is much more complexity than my initial proposal.

I still think the command queue is the best way to go.

Egge21M avatar Apr 17 '24 06:04 Egge21M

I don't think more logic should be put on the NIP07. This makes it just harder to implement nip07 and will lead to less options.

If you want such syntactic sugar then we should put this in a more high-level library. Which imo is also quite easy. here is a 2minutes idea for this for example: https://gist.github.com/bumi/050a29cef2f6f9f9d7960d834104890a

bumi avatar Apr 20 '24 09:04 bumi

I don't think more logic should be put on the NIP07. This makes it just harder to implement nip07 and will lead to less options.

How does this make it harder to implement vs. the struggle of checking if the provider is fully loaded? If anything it becomes easier to implement as people no longer have to use intervals or other workarounds to avoid TypeErrors because their code races the provider injecting the global.


Edit: I see what you mean now, you are talking about the provider implementation. Is at really that much added complexity though.

Working on a provider currently, so I'll see if I shot myself in the foot here haha

Egge21M avatar Apr 20 '24 11:04 Egge21M

. Is at really that much added complexity though.

yes it is. as always with technical debt it starts with this; with something that is seemingly simple to implement.

specs must be simple and stupid, add complexity like this on a higher level.

I really don't see a reason why we want to force this on the nip07 spec. This is a browser and app detail. Make a nice npm package with all the syntactic sugar that wraps around the basic functions (like to sign and event or get a pubkey). What's the problem with that?

and besides all this I also don't think it's worth to make provider implementations incompatible for at least a while.

bumi avatar Apr 20 '24 18:04 bumi

specs must be simple and stupid, add complexity like this on a higher level.

The race conditions are not inherent to the spec, so maybe you are right. However they are present in every single environment that is relevant today.

I really don't see a reason why we want to force this on the nip07 spec.

Because it's a net benefit for all consumers. Feedback from that side of nip07 has been nothing but positive so far.

This is a browser and app detail. Make a nice npm package with all the syntactic sugar that wraps around the basic functions (like to sign and event or get a pubkey). What's the problem with that?

No problem. However as this seems to affect many users I thought a general solution might be needed.

Egge21M avatar Apr 20 '24 20:04 Egge21M

This looks like a complication for both clients and providers - we split the problem and require both sides to make changes. And now we need to detect if provider supports this and fall back to something if they don't, which means I have to code a client-side solution anyway.

Instead, if we're about to recommend a particular way clients should be written ("this is how you init cmd, this is how you enqueue, etc"), then why not just recommend in the nip07 a purely client-side reliable and simple solution like one @bumi is proposing? "Here is how you wrap your calls to make sure you avoid races".

That would be simple for clients - just copy this snippet of code and use it, which is no different than with the 'cmd' logic proposed above, but requires no changes to providers and no need for feature detection.

nostrband avatar Apr 24 '24 07:04 nostrband

This looks like a complication for both clients and providers - we split the problem and require both sides to make changes. And now we need to detect if provider supports this and fall back to something if they don't, which means I have to code a client-side solution anyway.

Instead, if we're about to recommend a particular way clients should be written ("this is how you init cmd, this is how you enqueue, etc"), then why not just recommend in the nip07 a purely client-side reliable and simple solution like one @bumi is proposing? "Here is how you wrap your calls to make sure you avoid races".

That would be simple for clients - just copy this snippet of code and use it, which is no different than with the 'cmd' logic proposed above, but requires no changes to providers and no need for feature detection.

Personally I believe the worst tech-debt is the one one drag along, because you fear the transition. But in the end its up to the implementers and as the two most relevant implementers are against this proposal I would be an idiot to die on this hill. I will rewrite this PR to include client best practices instead.

Egge21M avatar Apr 24 '24 16:04 Egge21M