Repository: TypeScript
Issue: `CredentialsContainer.create()` & `.get()` should be able to return `PublicKeyCredential`, and the PKC interface should be more well-defined
Labels: Suggestion,Help Wanted,Domain: lib.d.ts,Experience Enhancement

### ⚙ Compilation target

ESNext or ES2024

(I am building for current up-to-date browsers.)

### ⚙ Library

DOM

### Missing / Incorrect Definition

[`CredentialsContainer.create()`](https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/create) and [`CredentialsContainer.get()`](https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/get) currently always return a `Promise<Credential | null>` regardless of config. This is incomplete and less helpful than it could be.

In actual implementation, if a `publicKey` config is specified (and _one_ type of credential config _must_ be specified for both methods), both methods _will_ return a `PublicKeyCredential` (or null). Currently we're forced to downcast to the correct type, e.g.:

```ts
const credential = await CredentialsContainer.create({ publicKey: { ... } });
if (credential) {
  const pkc = credential as PublicKeyCredential;
  // do things with pkc
}
```

Additionally, the `PublicKeyCredential` type definition is incomplete.

- Instances are missing the [`pkc.toJSON()`](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/toJSON) helper method.
   - Browser support still varies, so leaving it out might be deliberate. Firefox and Chrome support it; Safari does not, Opera is developing it, and all device WebViews do not support it.
- The `pkc.response` field currently always has type `AuthenticatorResponse`. This is incomplete. [Per MDN](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/response):
   - if the PublicKeyCredential was obtained via `create()`, the response field will be of type `AuthenticatorAttestationResponse` (attestation)
   - if it was obtained via `get()`, the response field will be of type `AuthenticatorAssertionResponse` (assertion).

Both `AuthenticatorAttestationResponse` and `AuthenticatorAssertionResponse` are already defined in `lib.dom.d.ts`.

### Recommendation

`PublicKeyCredential` should instead be defined with a generic property that accepts the response type, e.g.:

```ts
interface PublicKeyCredential<Response extends AuthenticatorResponse = AuthenticatorResponse> {
  response: Response,
  // (other fields omitted)
}
```

Then `create()` and `get()` should be overloaded methods approximately like the following. All the types referenced below already exist in `lib.dom.d.ts`.

```ts
interface CredentialsContainer {
  create(options?: CredentialCreationOptions & { publicKey: PublicKeyCredentialCreationOptions }): Promise<PublicKeyCredential<AuthenticatorAttestationResponse> | null>;
  create(options?: CredentialCreationOptions): Promise<Credential | null>;

  get(options?: CredentialRequestOptions & { publicKey: PublicKeyCredentialRequestOptions }): Promise<PublicKeyCredential<AuthenticatorAssertionResponse> | null>;
  get(options?: CredentialRequestOptions): Promise<Credential | null>;
}
```

### Sample Code

```TypeScript
const toUint8Array = (str: string) => Uint8Array.from(str, c => c.charCodeAt(0));

const credential = await navigator.credentials.create({
  publicKey: {
    challenge: toUint8Array("random challenge from server"),
    rp: {
      name: "WebAuthN",
      id: "webauth.io", // Run this code on the webauthn.io domain, or change this to another domain.
    },
    user: {
      id: toUint8Array("username"),
      name: "Name",
      displayName: "Display name",
    },
    pubKeyCredParams: [
      { alg: -7, type: "public-key" },
    ],
    attestation: "direct",
  },
});

if (credential) {
  console.debug(credential.toJSON());
  //                       ~~~~~~
  // Property 'toJSON' does not exist on type 'Credential'.ts(2339)

  console.debug(credential.response);
  //                       ~~~~~~~~
  // Property 'response' does not exist on type 'Credential'.ts(2339)

  console.debug((credential as PublicKeyCredential).response.getAuthenticatorData());
  //                                                         ~~~~~~~~~~~~~~~~~~~~
  // Property 'getAuthenticatorData' does not exist on type 'AuthenticatorResponse'.ts(2339)
  
  // Currently forced to downcast twice like this essentially, which TypeScript permits:
  const pkc = credential as (PublicKeyCredential & { response: AuthenticatorAttestationResponse });
  console.debug(pkc.response.getAuthenticatorData());
}
```

### Documentation Link

- `CredentialsContainer.create()`: https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/create
- `CredentialsContainer.get()`: https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/get
- `PublicKeyCredential.toJSON()`: https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/toJSON
- `PublicKeyCredential.response`: https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/response
- `AuthenticatorAttestationResponse.getAuthenticatorData()`: https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAttestationResponse/getAuthenticatorData