@account-kit/signer 4.9.0 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/esm/client/base.d.ts +45 -3
  2. package/dist/esm/client/base.js +141 -1
  3. package/dist/esm/client/base.js.map +1 -1
  4. package/dist/esm/client/index.d.ts +0 -2
  5. package/dist/esm/client/index.js +15 -103
  6. package/dist/esm/client/index.js.map +1 -1
  7. package/dist/esm/client/types.d.ts +16 -0
  8. package/dist/esm/client/types.js.map +1 -1
  9. package/dist/esm/errors.js +3 -1
  10. package/dist/esm/errors.js.map +1 -1
  11. package/dist/esm/index.d.ts +1 -0
  12. package/dist/esm/index.js +1 -0
  13. package/dist/esm/index.js.map +1 -1
  14. package/dist/esm/oauth.d.ts +8 -7
  15. package/dist/esm/oauth.js +10 -10
  16. package/dist/esm/oauth.js.map +1 -1
  17. package/dist/esm/utils/resolveRelativeUrl.d.ts +1 -0
  18. package/dist/esm/utils/resolveRelativeUrl.js +7 -0
  19. package/dist/esm/utils/resolveRelativeUrl.js.map +1 -0
  20. package/dist/esm/version.d.ts +1 -1
  21. package/dist/esm/version.js +1 -1
  22. package/dist/esm/version.js.map +1 -1
  23. package/dist/types/client/base.d.ts +45 -3
  24. package/dist/types/client/base.d.ts.map +1 -1
  25. package/dist/types/client/index.d.ts +0 -2
  26. package/dist/types/client/index.d.ts.map +1 -1
  27. package/dist/types/client/types.d.ts +16 -0
  28. package/dist/types/client/types.d.ts.map +1 -1
  29. package/dist/types/errors.d.ts.map +1 -1
  30. package/dist/types/index.d.ts +1 -0
  31. package/dist/types/index.d.ts.map +1 -1
  32. package/dist/types/oauth.d.ts +8 -7
  33. package/dist/types/oauth.d.ts.map +1 -1
  34. package/dist/types/utils/resolveRelativeUrl.d.ts +2 -0
  35. package/dist/types/utils/resolveRelativeUrl.d.ts.map +1 -0
  36. package/dist/types/version.d.ts +1 -1
  37. package/dist/types/version.d.ts.map +1 -1
  38. package/package.json +4 -4
  39. package/src/client/base.ts +170 -4
  40. package/src/client/index.ts +18 -128
  41. package/src/client/types.ts +24 -2
  42. package/src/errors.ts +3 -1
  43. package/src/index.ts +1 -0
  44. package/src/oauth.ts +11 -11
  45. package/src/utils/resolveRelativeUrl.ts +6 -0
  46. package/src/version.ts +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,OAAO,UAAU,CAAC"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,OAAO,WAAW,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@account-kit/signer",
3
- "version": "4.9.0",
3
+ "version": "4.11.0",
4
4
  "description": "Core interfaces and clients for interfacing with the Alchemy Signer API",
5
5
  "author": "Alchemy",
6
6
  "license": "MIT",
@@ -49,8 +49,8 @@
49
49
  "vitest": "^2.0.4"
50
50
  },
51
51
  "dependencies": {
52
- "@aa-sdk/core": "^4.9.0",
53
- "@account-kit/logging": "^4.9.0",
52
+ "@aa-sdk/core": "^4.11.0",
53
+ "@account-kit/logging": "^4.11.0",
54
54
  "@turnkey/http": "^2.6.2",
55
55
  "@turnkey/iframe-stamper": "^1.0.0",
56
56
  "@turnkey/viem": "^0.4.8",
@@ -73,5 +73,5 @@
73
73
  "url": "https://github.com/alchemyplatform/aa-sdk/issues"
74
74
  },
75
75
  "homepage": "https://github.com/alchemyplatform/aa-sdk#readme",
76
- "gitHead": "9cd4dc759879defa6ac90f1ae7f8a378d9d1666f"
76
+ "gitHead": "789a1db1e3cfdca61393d8328693dd9d1a4ebb0f"
77
77
  }
@@ -2,19 +2,22 @@ import { ConnectionConfigSchema, type ConnectionConfig } from "@aa-sdk/core";
2
2
  import { TurnkeyClient, type TSignedRequest } from "@turnkey/http";
3
3
  import EventEmitter from "eventemitter3";
4
4
  import { jwtDecode } from "jwt-decode";
5
- import type { Hex } from "viem";
6
- import { NotAuthenticatedError } from "../errors.js";
5
+ import { sha256, type Hex } from "viem";
6
+ import { NotAuthenticatedError, OAuthProvidersError } from "../errors.js";
7
7
  import { base64UrlEncode } from "../utils/base64UrlEncode.js";
8
8
  import { assertNever } from "../utils/typeAssertions.js";
9
+ import { resolveRelativeUrl } from "../utils/resolveRelativeUrl.js";
9
10
  import type {
10
11
  AlchemySignerClientEvent,
11
12
  AlchemySignerClientEvents,
12
13
  AuthenticatingEventMetadata,
13
14
  CreateAccountParams,
14
15
  EmailAuthParams,
16
+ GetOauthProviderUrlArgs,
15
17
  GetWebAuthnAttestationResult,
16
18
  OauthConfig,
17
19
  OauthParams,
20
+ OauthState,
18
21
  OtpParams,
19
22
  SignerBody,
20
23
  SignerResponse,
@@ -22,6 +25,8 @@ import type {
22
25
  SignupResponse,
23
26
  User,
24
27
  } from "./types.js";
28
+ import type { OauthMode } from "../signer.js";
29
+ import { addOpenIdIfAbsent, getDefaultScopeAndClaims } from "../oauth.js";
25
30
 
26
31
  export interface BaseSignerClientParams {
27
32
  stamper: TurnkeyClient["stamper"];
@@ -46,7 +51,6 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
46
51
  protected rootOrg: string;
47
52
  protected eventEmitter: EventEmitter<AlchemySignerClientEvents>;
48
53
  protected oauthConfig: OauthConfig | undefined;
49
-
50
54
  /**
51
55
  * Create a new instance of the Alchemy Signer client
52
56
  *
@@ -139,7 +143,7 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
139
143
 
140
144
  public abstract oauthWithRedirect(
141
145
  args: Extract<OauthParams, { mode: "redirect" }>
142
- ): Promise<never>;
146
+ ): Promise<User | never>;
143
147
 
144
148
  public abstract oauthWithPopup(
145
149
  args: Extract<OauthParams, { mode: "popup" }>
@@ -471,6 +475,158 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
471
475
  return result;
472
476
  };
473
477
 
478
+ /**
479
+ * Returns the authentication url for the selected OAuth Proivder
480
+ *
481
+ * @example
482
+ * ```ts
483
+ *
484
+ * cosnt oauthParams = {
485
+ * authProviderId: "google",
486
+ * isCustomProvider: false,
487
+ * auth0Connection: undefined,
488
+ * scope: undefined,
489
+ * claims: undefined,
490
+ * mode: "redirect",
491
+ * redirectUrl: "https://your-url-path/oauth-return",
492
+ * expirationSeconds: 3000
493
+ * };
494
+ *
495
+ * const turnkeyPublicKey = await this.initIframeStamper();
496
+ * const oauthCallbackUrl = this.oauthCallbackUrl;
497
+ * const oauthConfig = this.getOauthConfig() // Optional value for OauthConfig()
498
+ * const usesRelativeUrl = true // Optional value to determine if we use a relative (or absolute) url for the `redirect_url`
499
+ *
500
+ * const oauthProviderUrl = getOauthProviderUrl({
501
+ * oauthParams,
502
+ * turnkeyPublicKey,
503
+ * oauthCallbackUrl
504
+ * })
505
+ *
506
+ * ```
507
+ * @param {GetOauthProviderUrlArgs} args Required. The Oauth provider's auth parameters
508
+ *
509
+ * @returns {Promise<string>} returns the Oauth provider's url
510
+ */
511
+ protected getOauthProviderUrl = async (
512
+ args: GetOauthProviderUrlArgs
513
+ ): Promise<string> => {
514
+ const {
515
+ oauthParams,
516
+ turnkeyPublicKey,
517
+ oauthCallbackUrl,
518
+ oauthConfig,
519
+ usesRelativeUrl = true,
520
+ } = args;
521
+
522
+ const {
523
+ authProviderId,
524
+ isCustomProvider,
525
+ auth0Connection,
526
+ scope: providedScope,
527
+ claims: providedClaims,
528
+ mode,
529
+ redirectUrl,
530
+ expirationSeconds,
531
+ } = oauthParams;
532
+
533
+ const { codeChallenge, requestKey, authProviders } =
534
+ oauthConfig ?? (await this.getOauthConfigForMode(mode));
535
+
536
+ if (!authProviders) {
537
+ throw new OAuthProvidersError();
538
+ }
539
+
540
+ const authProvider = authProviders.find(
541
+ (provider) =>
542
+ provider.id === authProviderId &&
543
+ !!provider.isCustomProvider === !!isCustomProvider
544
+ );
545
+
546
+ if (!authProvider) {
547
+ throw new Error(`No auth provider found with id ${authProviderId}`);
548
+ }
549
+
550
+ let scope: string;
551
+ let claims: string | undefined;
552
+
553
+ if (providedScope) {
554
+ scope = addOpenIdIfAbsent(providedScope);
555
+ claims = providedClaims;
556
+ } else {
557
+ if (isCustomProvider) {
558
+ throw new Error("scope must be provided for a custom provider");
559
+ }
560
+ const scopeAndClaims = getDefaultScopeAndClaims(authProviderId);
561
+ if (!scopeAndClaims) {
562
+ throw new Error(
563
+ `Default scope not known for provider ${authProviderId}`
564
+ );
565
+ }
566
+ ({ scope, claims } = scopeAndClaims);
567
+ }
568
+ const { authEndpoint, clientId } = authProvider;
569
+
570
+ const nonce = this.getOauthNonce(turnkeyPublicKey);
571
+ const stateObject: OauthState = {
572
+ authProviderId,
573
+ isCustomProvider,
574
+ requestKey,
575
+ turnkeyPublicKey,
576
+ expirationSeconds,
577
+ redirectUrl:
578
+ mode === "redirect"
579
+ ? usesRelativeUrl
580
+ ? resolveRelativeUrl(redirectUrl)
581
+ : redirectUrl
582
+ : undefined,
583
+ openerOrigin: mode === "popup" ? window.location.origin : undefined,
584
+ };
585
+ const state = base64UrlEncode(
586
+ new TextEncoder().encode(JSON.stringify(stateObject))
587
+ );
588
+ const authUrl = new URL(authEndpoint);
589
+ const params: Record<string, string> = {
590
+ redirect_uri: oauthCallbackUrl,
591
+ response_type: "code",
592
+ scope,
593
+ state,
594
+ code_challenge: codeChallenge,
595
+ code_challenge_method: "S256",
596
+ prompt: "select_account",
597
+ client_id: clientId,
598
+ nonce,
599
+ };
600
+ if (claims) {
601
+ params.claims = claims;
602
+ }
603
+ if (auth0Connection) {
604
+ params.connection = auth0Connection;
605
+ }
606
+
607
+ Object.keys(params).forEach((param) => {
608
+ params[param] && authUrl.searchParams.append(param, params[param]);
609
+ });
610
+
611
+ const [urlPath, searchParams] = authUrl.href.split("?");
612
+
613
+ return `${urlPath?.replace(/\/$/, "")}?${searchParams}`;
614
+ };
615
+
616
+ private getOauthConfigForMode = async (
617
+ mode: OauthMode
618
+ ): Promise<OauthConfig> => {
619
+ if (this.oauthConfig) {
620
+ return this.oauthConfig;
621
+ } else if (mode === "redirect") {
622
+ return this.initOauth();
623
+ } else {
624
+ throw new Error(
625
+ "enablePopupOauth must be set in configuration or signer.preparePopupOauth must be called before using popup-based OAuth login"
626
+ );
627
+ }
628
+ };
629
+
474
630
  // eslint-disable-next-line eslint-rules/require-jsdoc-on-reexported-functions
475
631
  protected pollActivityCompletion = async <
476
632
  T extends keyof Awaited<
@@ -520,4 +676,14 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
520
676
  return this.pollActivityCompletion(activity, organizationId, resultKey);
521
677
  };
522
678
  // #endregion
679
+
680
+ /**
681
+ * Turnkey requires the nonce in the id token to be in this format.
682
+ *
683
+ * @param {string} turnkeyPublicKey key from a Turnkey iframe
684
+ * @returns {string} nonce to be used in OIDC
685
+ */
686
+ protected getOauthNonce = (turnkeyPublicKey: string): string => {
687
+ return sha256(new TextEncoder().encode(turnkeyPublicKey)).slice(2);
688
+ };
523
689
  }
@@ -3,9 +3,7 @@ import { getWebAuthnAttestation } from "@turnkey/http";
3
3
  import { IframeStamper } from "@turnkey/iframe-stamper";
4
4
  import { WebauthnStamper } from "@turnkey/webauthn-stamper";
5
5
  import { z } from "zod";
6
- import { OAuthProvidersError } from "../errors.js";
7
- import { getDefaultScopeAndClaims, getOauthNonce } from "../oauth.js";
8
- import type { AuthParams, OauthMode } from "../signer.js";
6
+ import type { AuthParams } from "../signer.js";
9
7
  import { base64UrlEncode } from "../utils/base64UrlEncode.js";
10
8
  import { generateRandomBuffer } from "../utils/generateRandomBuffer.js";
11
9
  import { BaseSignerClient } from "./base.js";
@@ -17,7 +15,6 @@ import type {
17
15
  EmailAuthParams,
18
16
  ExportWalletParams,
19
17
  OauthConfig,
20
- OauthParams,
21
18
  OtpParams,
22
19
  User,
23
20
  } from "./types.js";
@@ -46,16 +43,6 @@ export type AlchemySignerClientParams = z.input<
46
43
  typeof AlchemySignerClientParamsSchema
47
44
  >;
48
45
 
49
- type OauthState = {
50
- authProviderId: string;
51
- isCustomProvider?: boolean;
52
- requestKey: string;
53
- turnkeyPublicKey: string;
54
- expirationSeconds?: number;
55
- redirectUrl?: string;
56
- openerOrigin?: string;
57
- };
58
-
59
46
  /**
60
47
  * A lower level client used by the AlchemySigner used to communicate with
61
48
  * Alchemy's signer service.
@@ -462,7 +449,15 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
462
449
  public override oauthWithRedirect = async (
463
450
  args: Extract<AuthParams, { type: "oauth"; mode: "redirect" }>
464
451
  ): Promise<never> => {
465
- const providerUrl = await this.getOauthProviderUrl(args);
452
+ const turnkeyPublicKey = await this.initIframeStamper();
453
+
454
+ const oauthParams = args;
455
+ const providerUrl = await this.getOauthProviderUrl({
456
+ oauthParams,
457
+ turnkeyPublicKey,
458
+ oauthCallbackUrl: this.oauthCallbackUrl,
459
+ });
460
+
466
461
  window.location.href = providerUrl;
467
462
  return new Promise((_, reject) =>
468
463
  setTimeout(() => reject("Failed to redirect to OAuth provider"), 1000)
@@ -498,7 +493,13 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
498
493
  public override oauthWithPopup = async (
499
494
  args: Extract<AuthParams, { type: "oauth"; mode: "popup" }>
500
495
  ): Promise<User> => {
501
- const providerUrl = await this.getOauthProviderUrl(args);
496
+ const turnkeyPublicKey = await this.initIframeStamper();
497
+ const oauthParams = args;
498
+ const providerUrl = await this.getOauthProviderUrl({
499
+ oauthParams,
500
+ turnkeyPublicKey,
501
+ oauthCallbackUrl: this.oauthCallbackUrl,
502
+ });
502
503
  const popup = window.open(
503
504
  providerUrl,
504
505
  "_blank",
@@ -556,85 +557,6 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
556
557
  });
557
558
  };
558
559
 
559
- private getOauthProviderUrl = async (args: OauthParams): Promise<string> => {
560
- const {
561
- authProviderId,
562
- isCustomProvider,
563
- auth0Connection,
564
- scope: providedScope,
565
- claims: providedClaims,
566
- mode,
567
- redirectUrl,
568
- expirationSeconds,
569
- } = args;
570
- const { codeChallenge, requestKey, authProviders } =
571
- await this.getOauthConfigForMode(mode);
572
- if (!authProviders) {
573
- throw new OAuthProvidersError();
574
- }
575
- const authProvider = authProviders.find(
576
- (provider) =>
577
- provider.id === authProviderId &&
578
- !!provider.isCustomProvider === !!isCustomProvider
579
- );
580
- if (!authProvider) {
581
- throw new Error(`No auth provider found with id ${authProviderId}`);
582
- }
583
- let scope: string;
584
- let claims: string | undefined;
585
- if (providedScope) {
586
- scope = addOpenIdIfAbsent(providedScope);
587
- claims = providedClaims;
588
- } else {
589
- if (isCustomProvider) {
590
- throw new Error("scope must be provided for a custom provider");
591
- }
592
- const scopeAndClaims = getDefaultScopeAndClaims(authProviderId);
593
- if (!scopeAndClaims) {
594
- throw new Error(
595
- `Default scope not known for provider ${authProviderId}`
596
- );
597
- }
598
- ({ scope, claims } = scopeAndClaims);
599
- }
600
- const { authEndpoint, clientId } = authProvider;
601
- const turnkeyPublicKey = await this.initIframeStamper();
602
- const nonce = getOauthNonce(turnkeyPublicKey);
603
- const stateObject: OauthState = {
604
- authProviderId,
605
- isCustomProvider,
606
- requestKey,
607
- turnkeyPublicKey,
608
- expirationSeconds,
609
- redirectUrl:
610
- mode === "redirect" ? resolveRelativeUrl(redirectUrl) : undefined,
611
- openerOrigin: mode === "popup" ? window.location.origin : undefined,
612
- };
613
- const state = base64UrlEncode(
614
- new TextEncoder().encode(JSON.stringify(stateObject))
615
- );
616
- const authUrl = new URL(authEndpoint);
617
- const params: Record<string, string> = {
618
- redirect_uri: this.oauthCallbackUrl,
619
- response_type: "code",
620
- scope,
621
- state,
622
- code_challenge: codeChallenge,
623
- code_challenge_method: "S256",
624
- prompt: "select_account",
625
- client_id: clientId,
626
- nonce,
627
- };
628
- if (claims) {
629
- params.claims = claims;
630
- }
631
- if (auth0Connection) {
632
- params.connection = auth0Connection;
633
- }
634
- authUrl.search = new URLSearchParams(params).toString();
635
- return authUrl.toString();
636
- };
637
-
638
560
  private initIframeStamper = async () => {
639
561
  if (!this.iframeStamper.publicKey()) {
640
562
  await this.iframeStamper.init();
@@ -720,41 +642,9 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
720
642
 
721
643
  // swap the stamper back in case the user logged in with a different stamper (passkeys)
722
644
  this.setStamper(currentStamper);
723
- const nonce = getOauthNonce(publicKey);
645
+ const nonce = this.getOauthNonce(publicKey);
724
646
  return this.request("/v1/prepare-oauth", { nonce });
725
647
  };
726
-
727
- private getOauthConfigForMode = async (
728
- mode: OauthMode
729
- ): Promise<OauthConfig> => {
730
- if (this.oauthConfig) {
731
- return this.oauthConfig;
732
- } else if (mode === "redirect") {
733
- return this.initOauth();
734
- } else {
735
- throw new Error(
736
- "enablePopupOauth must be set in configuration or signer.preparePopupOauth must be called before using popup-based OAuth login"
737
- );
738
- }
739
- };
740
- }
741
-
742
- function resolveRelativeUrl(url: string): string {
743
- // Funny trick.
744
- const a = document.createElement("a");
745
- a.href = url;
746
- return a.href;
747
- }
748
-
749
- /**
750
- * "openid" is a required scope in the OIDC protocol. Insert it if the user
751
- * forgot.
752
- *
753
- * @param {string} scope scope param which may be missing "openid"
754
- * @returns {string} scope which most definitely contains "openid"
755
- */
756
- function addOpenIdIfAbsent(scope: string): string {
757
- return scope.match(/\bopenid\b/) ? scope : `openid ${scope}`;
758
648
  }
759
649
 
760
650
  /**
@@ -99,7 +99,9 @@ export type SignerEndpoints = [
99
99
  {
100
100
  Route: "/v1/signup";
101
101
  Body:
102
- | (Omit<EmailAuthParams, "redirectParams"> & { redirectParams?: string })
102
+ | (Omit<EmailAuthParams, "redirectParams"> & {
103
+ redirectParams?: string;
104
+ })
103
105
  | {
104
106
  passkey: {
105
107
  challenge: string;
@@ -117,7 +119,9 @@ export type SignerEndpoints = [
117
119
  },
118
120
  {
119
121
  Route: "/v1/auth";
120
- Body: Omit<EmailAuthParams, "redirectParams"> & { redirectParams?: string };
122
+ Body: Omit<EmailAuthParams, "redirectParams"> & {
123
+ redirectParams?: string;
124
+ };
121
125
  Response: {
122
126
  orgId: string;
123
127
  otpId?: string;
@@ -177,3 +181,21 @@ export type GetWebAuthnAttestationResult = {
177
181
  challenge: ArrayBuffer;
178
182
  authenticatorUserId: ArrayBuffer;
179
183
  };
184
+
185
+ export type OauthState = {
186
+ authProviderId: string;
187
+ isCustomProvider?: boolean;
188
+ requestKey: string;
189
+ turnkeyPublicKey: string;
190
+ expirationSeconds?: number;
191
+ redirectUrl?: string;
192
+ openerOrigin?: string;
193
+ };
194
+
195
+ export type GetOauthProviderUrlArgs = {
196
+ oauthParams: OauthParams;
197
+ turnkeyPublicKey: string;
198
+ oauthCallbackUrl: string;
199
+ oauthConfig?: OauthConfig;
200
+ usesRelativeUrl?: boolean;
201
+ };
package/src/errors.ts CHANGED
@@ -18,6 +18,8 @@ export class NotAuthenticatedError extends BaseError {
18
18
  export class OAuthProvidersError extends BaseError {
19
19
  override name = "OAuthProvidersError";
20
20
  constructor() {
21
- super("OAuth providers not found", { docsPath: "/react/getting-started" });
21
+ super("OAuth providers not found", {
22
+ docsPath: "/react/getting-started",
23
+ });
22
24
  }
23
25
  }
package/src/index.ts CHANGED
@@ -14,3 +14,4 @@ export type * from "./signer.js";
14
14
  export { AlchemyWebSigner } from "./signer.js";
15
15
  export type * from "./types.js";
16
16
  export { AlchemySignerStatus } from "./types.js";
17
+ export { OAuthProvidersError, NotAuthenticatedError } from "./errors.js";
package/src/oauth.ts CHANGED
@@ -1,16 +1,5 @@
1
- import { sha256 } from "viem";
2
1
  import type { KnownAuthProvider } from "./signer";
3
2
 
4
- /**
5
- * Turnkey requires the nonce in the id token to be in this format.
6
- *
7
- * @param {string} turnkeyPublicKey key from a Turnkey iframe
8
- * @returns {string} nonce to be used in OIDC
9
- */
10
- export function getOauthNonce(turnkeyPublicKey: string): string {
11
- return sha256(new TextEncoder().encode(turnkeyPublicKey)).slice(2);
12
- }
13
-
14
3
  export type ScopeAndClaims = {
15
4
  scope: string;
16
5
  claims?: string;
@@ -34,3 +23,14 @@ export function getDefaultScopeAndClaims(
34
23
  ): ScopeAndClaims | undefined {
35
24
  return DEFAULT_SCOPE_AND_CLAIMS[knownAuthProviderId];
36
25
  }
26
+
27
+ /**
28
+ * "openid" is a required scope in the OIDC protocol. Insert it if the user
29
+ * forgot.
30
+ *
31
+ * @param {string} scope scope param which may be missing "openid"
32
+ * @returns {string} scope which most definitely contains "openid"
33
+ */
34
+ export function addOpenIdIfAbsent(scope: string): string {
35
+ return scope.match(/\bopenid\b/) ? scope : `openid ${scope}`;
36
+ }
@@ -0,0 +1,6 @@
1
+ export function resolveRelativeUrl(url: string): string {
2
+ // Funny trick.
3
+ const a = document.createElement("a");
4
+ a.href = url;
5
+ return a.href;
6
+ }
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  // This file is autogenerated by inject-version.ts. Any changes will be
2
2
  // overwritten on commit!
3
- export const VERSION = "4.9.0";
3
+ export const VERSION = "4.11.0";