ua-client-hints icon indicating copy to clipboard operation
ua-client-hints copied to clipboard

Sync attributes should also be exposed in async API (Was: Partially synchronous interface is odd)

Open slightlyoff opened this issue 5 years ago • 5 comments

Howdy, client-hinters!

Per a conversation around a Blink I2S thread regarding this feature, I was looking at the DOM API as proposed here:

https://github.com/WICG/ua-client-hints/pull/48/files

It appears that the example code in the Explainer (which, surprisingly, has IDL in it?) hasn't been updated to match, and I don't see an example code that uses the APIs in any of the proposed use-cases section. At a minimum, this isn't great Explainer hygeine.

If I'm reading the new IDL correctly, this example code:

  const uaData = navigator.userAgentData;
  const brands = uaData.brands;     // [ {brand: "Google Chrome", version: "84"}, {brand: "Chromium", version: "84"} ]
  const mobileness = uaData.mobile; // false
  (async ()=>{
    // `getHighEntropyValues()` returns a Promise, so needs to be `await`ed on.
    const highEntropyValues = await uaData.getHighEntropyValues(
      ["platform", "platformVersion", "architecture", "model", "uaFullVersion"]);
    const platform = highEntropyValues.platform;               // "Mac OS X"
    const platformVersion = highEntropyValues.platformVersion; //"10_15_4"
    const architecture = highEntropyValues.architecture;       // "Intel"
    const model = highEntropyValues.model;                     // ""
    const uaFullVersion = highEntropyValues.uaFullVersion;     // "84.0.4113.0"
  })();

Becomes:

  (async () => {
    //
    // async entrypoint
    //
    const ua = await navigator.getUserAgent();

    //
    // sync accessors from async return value
    //
    // Plural return value but singular spelling?
    const brand = ua.brand; // [ {brand: "Chrome", version: "84"} ]
    const isMobile = ua.mobile;
   
    //
    // async accessors from async return value
    //
    // Singular async accessors, singular return value
    const platform = await ua.getPlatform(); // { brand: "Win", version: "10" }
    // What does this return for 32bit apps on a 64bit system, ala low-end Android?
    const arch = await ua.getArchitecture();       // "ARM64"
    // Singular async accessor without `get` prefix?
    const model = await ua.model();           // ""
    // UA full version no longer available anywhere?
  })();

At a minimum, this is pretty confusing. A few paths to consider:

  • Ensure that all async accessors have get prefixes
  • Ensure that, should a sync accessor be needed for some properties, an async version is available so that callers don't need to always understand which is which
  • Make array/object return values align with plurality of accessor names

Lastly, it'd be great to see the explainer work through examples to justify the various design choices (why are some things promoted to sync access, e.g.?).

slightlyoff avatar May 08 '20 22:05 slightlyoff

I suspect you may be looking in the wrong place. The following example code you included (below...) is the latest API shape, and there are no plans to modify that.

  const uaData = navigator.userAgentData;
  const brands = uaData.brands;     // [ {brand: "Google Chrome", version: "84"}, {brand: "Chromium", version: "84"} ]
  const mobileness = uaData.mobile; // false
  (async ()=>{
    // `getHighEntropyValues()` returns a Promise, so needs to be `await`ed on.
    const highEntropyValues = await uaData.getHighEntropyValues(
      ["platform", "platformVersion", "architecture", "model", "uaFullVersion"]);
    const platform = highEntropyValues.platform;               // "Mac OS X"
    const platformVersion = highEntropyValues.platformVersion; //"10_15_4"
    const architecture = highEntropyValues.architecture;       // "Intel"
    const model = highEntropyValues.model;                     // ""
    const uaFullVersion = highEntropyValues.uaFullVersion;     // "84.0.4113.0"
  })();

https://github.com/WICG/ua-client-hints/pull/48/files

This PR was since overridden by https://github.com/WICG/ua-client-hints/pull/70

Explainer (which, surprisingly, has IDL in it?)

Good point. I can remove the IDL from the explainer.

Lastly, it'd be great to see the explainer work through examples to justify the various design choices

Yeah, I can add explanations on that front

yoavweiss avatar May 11 '20 07:05 yoavweiss

Thanks for that.

I'm not sure it has net reduced my confusion. The following might be wrong too as I feel mightily confused.

It looks like there's a single async getter (uaData.getHighEntropyValues()) that's async, but the UADataValues doesn't also include brands or mobile, which means the developer still needs to remember which to use when/where.

Assuming the rest gets cleaned up, perhaps adding brands and mobile to UADataValues -- in addition to surfacing at the top level -- solves the issue? It would appear to.

Thoughts?

slightlyoff avatar May 12 '20 16:05 slightlyoff

It looks like there's a single async getter (uaData.getHighEntropyValues()) that's async, but the UADataValues doesn't also include brands or mobile, which means the developer still needs to remember which to use when/where.

Indeed, userAgentData contains 2 sync attributes for brands and mobile and a getter for the higher entropy values.

perhaps adding brands and mobile to UADataValues -- in addition to surfacing at the top level -- solves the issue? It would appear to.

OK, happy to add them as well if you think it'll make it clearer for developers.

yoavweiss avatar May 12 '20 16:05 yoavweiss

I'm -1 on including redundant information. If you want a single object, you can use JavaScript's built-in abilities to combine two objects into one. More commonly, I think it'll be important to separate the high-entry values from the low-entropy ones.

domenic avatar May 12 '20 18:05 domenic

@domenic - thanks for the feedback!

I renamed the issue and marked as an enhancement, as this seems non-blocking for the initial version.

yoavweiss avatar May 13 '20 08:05 yoavweiss

I'm too lazy to dig up the CL, but we did end up implementing and shipped what @slightlyoff suggested some years ago:

That is, the sync low-entropy hints are also available via the async API:

await uaData.getHighEntropyValues(["mobile", "brands", "platform"])

miketaylr avatar Nov 05 '25 13:11 miketaylr