Readit News logoReadit News
jauntywundrkind · a year ago
Not that it's super important, but Web Locks API is getting some circulation after a question about how you keep multiple pages from trying to use a (single-use only) oauth refresh token at the same time. Which is a pretty good use case for this feature! https://bsky.app/profile/ambarvm.bsky.social/post/3lakznzipt...
unilynx · a year ago
> navigator.locks.request("my_resource", async (lock) => {

This would be so much more readable with `using`

  {
    using lock = navigator.lock('my_resource');
    await do_something();
    await do_something_else();
  }
(https://github.com/tc39/proposal-explicit-resource-managemen...)

yoavm · a year ago
It doesn't actually feel more readable to me. I find the idea that the lock declaration sits at the same level as the lock content confusing.
zeroxfe · a year ago
It's readable if you're familiar with the RAII pattern which is used in languages like C++ and Go.
nesarkvechnep · a year ago
Yes, it sits at the same level to signify the lifetime of the lock.
chrisfosterelli · a year ago
Why can't we just `await` the lock call?

Edit: Nevermind, release of the lock is automatic when the callback resolves its promise. I get it now.

KTibow · a year ago
If you really wanted it to be at the top level you could probably turn it into an explicit `release` call using a wrapper and two `Promise` constructors, though that would probably be a bad idea since it could introduce bugs
creativenolo · a year ago
Subjectiveness aside on what’s more readable… your proposing a new language feature would be more readable than an API design. To me, the MDN proposal is declarative whereas your proposal is imperative. And with my subjectiveness, JavaScript shines in declarative programming.
mpweiher · a year ago

   navigator whileLocked:'my_resource' do:{ :protected |
      protected doSomething.
      protected doOtherThing.
   }

sureIy · a year ago
You can probably wrap it to have that API
n2d4 · a year ago
Sadly it needs a language feature that doesn't exist yet
pwdisswordfishz · a year ago
If it only weren’t for that crippled "using" syntax. On the other hand, though:

    const denialOfService = res => void navigator.lock(res);
I mean, one might write this just as well with the callback interface, but this is much easier to do accidentally.

simonw · a year ago
I wish the compatibility tables on MDN gave an indicator of when a feature became available.

My ideal would be a thing that says "this hit 90% of deployed browsers 4 years ago", but just seeing the date it was added to each of the significant browser families would be amazingly useful.

spartanatreyu · a year ago
It does.

Using the Web Locks API page as an example, let's say we want to know when `LockManager` was added to Chrome. Here are the steps:

1. View the page: https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_A... 2. Scroll down to the Browser compatibility table 3. Find the cell you're interested in, in this example we're looking at where the `LockManager` row meets the Chrome column. 4. We see a check and a version number (in this case 69).

So at this point we know that it has existed since version 69.

Now in the case that we don't know that Chrome's current version is already < 100 and we need to know when a feature gained support:

5. Click on the cell to view the timeline of that cell.

Now we know that it was released on 2018-09-04, and it never had a smaller release requiring browser flags/prefixes/experiment flags/etc...

simonw · a year ago
I honestly never thought to click on those! Thanks very much.

Now I'm digging around in https://github.com/mdn/browser-compat-data/blob/main/api/Loc... and trying to find the browser release dates data...

Ooh, https://bcd.developer.mozilla.org/bcd/api/v0/current/api.Loc... is even better - it expands those browser dates and it's served with access-control-allow-origin: *

MayorMonty · a year ago
I think this is the idea behind the Baseline <Year> standard you see on a lot of mdn features now, it shows the year when the feature was available in all 3 major browser engines
pcl · a year ago
You can get this from caniuse, right? Would be a simple enough browser extension to marry the two together.

https://caniuse.com/mdn-api_lock

paulddraper · a year ago
caniuse.com
remram · a year ago
Why only in secure contexts? You can use storage APIs in insecure contexts, doing this by spinning, but the lock API which seems much more innocuous requires a secure context?
chrismorgan · a year ago
I believe most new capabilities are limited to secure contexts, in part as a way of discouraging bad habits, even when there’s no particular risk. The ideal is that everything should run in a secure context, but it’s a hard sell removing existing functionality from insecure contexts. If it were being added now, I’m pretty sure storage APIs would be secure-only.

Search around and you’ll find various information and explanations about it. https://blog.mozilla.org/security/2018/01/15/secure-contexts... is one, though https://w3ctag.github.io/design-principles/#secure-context has apparently been significantly watered down from what it was originally—see the initial proposal in https://github.com/w3ctag/design-principles/pull/75, and what was then merged in https://github.com/w3ctag/design-principles/pull/89.

berkes · a year ago
I'd presume that a MITM could set, release and read locks, by which it might determine that you have certain sites open.

In general¹, I presume that any API that uses "same origin" as bounding criteria, must be secure. Since there's no way to enforce this "same origin" in insecure contexts.

--

¹ Aside from the idea that maybe just make all new APIs "secure only", just to discourage insecure contexts.

eddd-ddde · a year ago
Or also just lock resources and stop other pages from working.
endgame · a year ago
If certain lock names become well-known, maybe you could DoS browsers by holding random locks and never releasing them?
Spivak · a year ago
I guess but since they're origin-bound can you do anything except DoS your page?
nitwit005 · a year ago
How does a shared memory space work if you have different versions of scripts for the same domain?
horsawlarway · a year ago
Same way a shared database works across multiple client versions as you roll out a new deployment.

Or the same way two completely different processes can access the same address space with shared memory IPC.

You aren't running in the same memory space, you're just communicating with a shared resource.

owaislone · a year ago
Why would that matter? The tabs don't share memory. Any code doesn't run when it tries to acquire a lot that another piece of code from another tab has already acquired. The two tabs don't even need to run the same app.
mmazing · a year ago
Well, it might matter for functionality in the application.

After you fix a lock-related bug for example, how do you deal with an open tab running a different version of your code that is erroneously misusing a lock?

You need to account for that when you release new code, yeah? Rename the lock maybe? Some other logic?

nitwit005 · a year ago
The need to test both versions being active.
aabhay · a year ago
I’ve been using this for a while now. But one thing I recently worked on required these locks to be extremely efficient. Does anyone have any benchmarks on usage of these locks? Preferably compared to the use of rust’s tokio mutex from a wasm context.
jitl · a year ago
You should write your own benchmarks! I've been using mitata for microbenchmarks which is what the bun and deno people use for their cool benchmark charts. It's fast and tries to call the system GC between runs which helps reduce bias. github: https://github.com/evanwashere/mitata

I find iterating in mitata super fun and a little addictive. It's hard to write a representative micro-benchmark, but optimizing them is still useful as long as you aren't making anything worse, which is often easy to avoid. I recently used mitata-benchmark-guided optimization to rewrite a core data structure at Notion for a 5% latency decrease on a few endpoints at p90/95/99. One of our returning interns used it to assess serialization libraries and she found one 3x faster. a+++ would recommend

orf · a year ago
If you’ve been using them for a while, don’t you have any benchmarks?
afavour · a year ago
Don’t see why you’d assume that. Not all applications are time critical.
tantalor · a year ago
Weird API to release the lock. What if you want to hold on to it? Then you need to do some silly promise wrapper. Would be better if there was a matching release() function.
jauntywundrkind · a year ago
The ergonomics here for 99.999% of uses seem great to me. Whatever async function you have can run for as long as it needs. That's using the language, not adding more userland craft. It's a good move.
rockwotj · a year ago
Probably because there is no RAII semantics in JS and they don’t want to allow forgetting releasing the lock. Although the promise workaround is explicitly opting into this behavior
slimsag · a year ago
Javascript in browsers already has a full atomics API:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

I'm not sure why Web Locks is useful TBH. I guess if you don't understand atomics it's a friendlier API?

wruza · a year ago

  p = Promise.withResolvers()
  navigator.locks.request(
    "foo",
    p.promise,
  )
  p.resolve()
I guess there’s room for .requestWithResolvers() still, they rarely learn the first lesson. Even the $subj article seems to be unaware of it and uses the silly wrapper way.

maxmcd · a year ago

  class Lock {
    #release: () => void | undefined;
    constructor(public name: string) {}
    acquire() {
      if (this.#release !== undefined) throw new Error("Already locked");
      navigator.locks.request(this.name, async (lock) => {
        await new Promise<void>(resolve => {
          this.#release = resolve;
        });
      });
    }
    release() {
      if (this.#release === undefined) throw new Error("Not locked");
      this.#release();
    }
  }

whilenot-dev · a year ago
You're not treating the aquire phase correctly:

  class Lock {
    #resolve: (() => void) | undefined;
    #releasePromise: Promise<void> | undefined;

    constructor(public name: string) {}

    acquire(): Promise<void> {
      if (this.#releasePromise !== undefined) {
        throw new Error("Already aquired");
      }

      return new Promise((resolveAquire, _rejectAquire) => {
        this.#releasePromise = new Promise((resolveRelease, _rejectRelease) => {
          navigator.locks.request(this.name, async (lock) => {
            await new Promise<void>((resolve, _reject) => {
              this.#resolve = resolve;
              resolveAquire();
            });

            resolveRelease();
          });
        });
      });
    }
    
    async release(): Promise<void> {
      if (this.#releasePromise === undefined) {
        throw new Error("Already released");
      }

      this.#resolve();
      await this.#releasePromise;

      this.#resolve = undefined;
      this.#releasePromise = undefined;
    }
  }
...the release phase still feels off without a Promise, but maybe somebody else can tackle that :D

EDIT: think I fixed it, untested though

gabrieledarrigo · a year ago
Ciao guys, what could be the use case for such API?
mooreds · a year ago
This was the use case[0] that brought it to my attention:

- single page application using access and refresh tokens to interact with an API

- refresh is one time use, as recommended by the OAuth security best practices[1]

- SPA is open in more than one tab

- two tabs try to refresh the token at the same time, second one fails because refresh token is used up

0: https://bsky.app/profile/ambarvm.bsky.social/post/3lakznzipt...

1: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-secur...

taosx · a year ago
Thanks for this, I've been using the localstorage for the same use case a while ago, I'll try to go back and update it.
bryanrasmussen · a year ago
I guess I don't actually get the point, because if I am locking a resource in one tab so that other tabs can't use that resource... how is that not going to lead to behavior that a user would think was broken.

Two tabs try to refresh the token at the same time, user opened tab 2, tab 2 can't refresh because things locked in tab 1 - user thinks app is broken? Isn't that the way it would happen. I guess you can detect it is locked in another tab though so you could give the user a warning about this?

I guess I am missing something about the scenario..

Voultapher · a year ago
Maybe it's old person yelling at cloud vibes, but I'm already annoyed enough at the majority of SPAs for breaking so many useful default browser features, such as stable links, navigation, multi-tab functionality, open in new tab etc. This is only gonna make them even more cancer my gut feeling tells me.
1oooqooq · a year ago
if a crypto miner infects a site you have several tabs open they won't fight for cpu at the same time.
loevborg · a year ago
Besides what sibling has written, write access to IndexedDB also often needs to be guarded by a mutex https://gist.github.com/pesterhazy/4de96193af89a6dd5ce682ce2...
CGamesPlay · a year ago
I don't get this complaint, because IndexedDB supports transactions. Why are these insufficient?
nchmy · a year ago
share a websocket/sse connection via a worker
sureIy · a year ago
Click the link, read the words. There's a list in it.