Repository: auth0/auth0-spa-js
Pull request #490 (2020-06-01 14:57:29)

[SDK-1695] Add `auth0Client` option so wrapper libraries can send their own client info
### Description

Add `auth0Client: { name: 'my-custom-client', version: '9.9.9' }` to `Auth0ClientOptions` options to send your own user agent details to the authorization server

### Testing

- [x] This change adds test coverage for new/changed/fixed functionality

### Checklist

- [x] I have added documentation for new/changed functionality in this PR or in auth0.com/docs
- [ ] All active GitHub checks for tests, formatting, and security are passing
- [x] The correct base branch is being used, if not `master`



Patch:
diff --git a/jest.config.js b/jest.config.js
index b725f53..c80199d 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,7 +1,7 @@
 module.exports = {
   rootDir: './',
   moduleFileExtensions: ['ts', 'js'],
-  testMatch: ['**/__tests__/*.(ts)'],
+  testMatch: ['**/__tests__/*.test.ts'],
   coveragePathIgnorePatterns: [
     '/node_modules/',
     './cypress',
diff --git a/src/Auth0Client.ts b/src/Auth0Client.ts
index 81856d7..236667b 100644
--- a/src/Auth0Client.ts
+++ b/src/Auth0Client.ts
@@ -139,15 +139,17 @@ export default class Auth0Client {
   }
 
   private _url(path) {
-    const telemetry = encodeURIComponent(
+    const auth0Client = encodeURIComponent(
       btoa(
-        JSON.stringify({
-          name: 'auth0-spa-js',
-          version: version
-        })
+        JSON.stringify(
+          this.options.auth0Client || {
+            name: 'auth0-spa-js',
+            version: version
+          }
+        )
       )
     );
-    return `${this.domainUrl}${path}&auth0Client=${telemetry}`;
+    return `${this.domainUrl}${path}&auth0Client=${auth0Client}`;
   }
 
   private _getParams(
@@ -161,6 +163,7 @@ export default class Auth0Client {
       domain,
       leeway,
       useRefreshTokens,
+      auth0Client,
       cacheLocation,
       advancedOptions,
       ...withoutDomain
diff --git a/src/global.ts b/src/global.ts
index d48e8a7..f836a9f 100644
--- a/src/global.ts
+++ b/src/global.ts
@@ -124,6 +124,12 @@ export interface Auth0ClientOptions extends BaseLoginOptions {
    */
   authorizeTimeoutInSeconds?: number;
 
+  /**
+   * Internal property to send information about the client to the authorization server.
+   * @internal
+   */
+  auth0Client?: { name: string; version: string };
+
   /**
    * Changes to recommended defaults, like defaultScope
    */
diff --git a/typedoc.js b/typedoc.js
index dc147b1..7bd70a3 100644
--- a/typedoc.js
+++ b/typedoc.js
@@ -21,6 +21,7 @@ module.exports = {
   excludeExternals: true,
   excludePrivate: true,
   includeDeclarations: true,
+  stripInternal: true,
   hideGenerator: true,
   theme: 'minimal'
 };


Test patch:
diff --git a/__tests__/Auth0Client.test.ts b/__tests__/Auth0Client.test.ts
index 1145ae2..712a512 100644
--- a/__tests__/Auth0Client.test.ts
+++ b/__tests__/Auth0Client.test.ts
@@ -6,6 +6,7 @@ import { MessageChannel } from 'worker_threads';
 import * as utils from '../src/utils';
 import { Auth0ClientOptions, IdToken } from '../src';
 import * as scope from '../src/scope';
+import { expectToHaveBeenCalledWithAuth0ClientParam } from './helpers';
 
 jest.mock('unfetch');
 jest.mock('../src/jwt');
@@ -163,6 +164,16 @@ describe('Auth0Client', () => {
     });
   });
 
+  it('should log the user in with custom auth0Client', async () => {
+    const auth0Client = { name: '__test_client__', version: '0.0.0' };
+    const auth0 = setup({ auth0Client });
+    await login(auth0);
+    expectToHaveBeenCalledWithAuth0ClientParam(
+      mockWindow.location.assign,
+      auth0Client
+    );
+  });
+
   it('refreshes the token from a web worker', async () => {
     const auth0 = setup({
       useRefreshTokens: true
diff --git a/__tests__/helpers.ts b/__tests__/helpers.ts
new file mode 100644
index 0000000..0242540
--- /dev/null
+++ b/__tests__/helpers.ts
@@ -0,0 +1,7 @@
+export const expectToHaveBeenCalledWithAuth0ClientParam = (mock, expected) => {
+  const [[url]] = (<jest.Mock>mock).mock.calls;
+  const param = new URL(url).searchParams.get('auth0Client');
+  const decodedParam = decodeURIComponent(atob(param));
+  const actual = JSON.parse(decodedParam);
+  expect(actual).toStrictEqual(expected);
+};
diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts
index 214d74a..1351580 100644
--- a/__tests__/index.test.ts
+++ b/__tests__/index.test.ts
@@ -4,6 +4,7 @@ jest.mock('../src/storage');
 jest.mock('../src/transaction-manager');
 jest.mock('../src/utils');
 
+import { expectToHaveBeenCalledWithAuth0ClientParam } from './helpers';
 import { CacheLocation, Auth0ClientOptions } from '../src/global';
 import * as scope from '../src/scope';
 
@@ -34,7 +35,7 @@ const TEST_REFRESH_TOKEN = 'refresh-token';
 const TEST_USER_ID = 'user-id';
 const TEST_USER_EMAIL = 'user@email.com';
 const TEST_APP_STATE = { bestPet: 'dog' };
-const TEST_TELEMETRY_QUERY_STRING = `&auth0Client=${encodeURIComponent(
+const TEST_AUTH0_CLIENT_QUERY_STRING = `&auth0Client=${encodeURIComponent(
   btoa(
     JSON.stringify({
       name: 'auth0-spa-js',
@@ -245,7 +246,7 @@ describe('Auth0', () => {
       await auth0.loginWithPopup({}, { popup });
 
       expect(utils.runPopup).toHaveBeenCalledWith(
-        `https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_TELEMETRY_QUERY_STRING}`,
+        `https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_AUTH0_CLIENT_QUERY_STRING}`,
         {
           popup,
           timeoutInSeconds: 60
@@ -357,7 +358,7 @@ describe('Auth0', () => {
       await auth0.loginWithPopup();
 
       expect(utils.runPopup).toHaveBeenCalledWith(
-        `https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_TELEMETRY_QUERY_STRING}`,
+        `https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_AUTH0_CLIENT_QUERY_STRING}`,
         DEFAULT_POPUP_CONFIG_OPTIONS
       );
     });
@@ -367,7 +368,7 @@ describe('Auth0', () => {
       expect(
         utils.runPopup
       ).toHaveBeenCalledWith(
-        `https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_TELEMETRY_QUERY_STRING}`,
+        `https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_AUTH0_CLIENT_QUERY_STRING}`,
         { timeoutInSeconds: 1 }
       );
     });
@@ -385,11 +386,20 @@ describe('Auth0', () => {
       expect(
         utils.runPopup
       ).toHaveBeenCalledWith(
-        `https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_TELEMETRY_QUERY_STRING}`,
+        `https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_AUTH0_CLIENT_QUERY_STRING}`,
         { timeoutInSeconds: 25 }
       );
     });
 
+    it('opens popup with custom auth0Client', async () => {
+      const auth0Client = { name: '__test_client_name__', version: '9.9.9' };
+      const { auth0, utils } = await setup({ auth0Client });
+
+      await auth0.loginWithPopup();
+
+      expectToHaveBeenCalledWithAuth0ClientParam(utils.runPopup, auth0Client);
+    });
+
     it('throws error if state from popup response is different from the provided state', async () => {
       const { auth0, utils } = await setup();
 
@@ -779,7 +789,7 @@ describe('Auth0', () => {
       });
 
       expect(url).toBe(
-        `https://test.auth0.com/authorize?query=params${TEST_TELEMETRY_QUERY_STRING}`
+        `https://test.auth0.com/authorize?query=params${TEST_AUTH0_CLIENT_QUERY_STRING}`
       );
     });
 
@@ -789,7 +799,7 @@ describe('Auth0', () => {
       const url = await auth0.buildAuthorizeUrl();
 
       expect(url).toBe(
-        `https://test.auth0.com/authorize?query=params${TEST_TELEMETRY_QUERY_STRING}`
+        `https://test.auth0.com/authorize?query=params${TEST_AUTH0_CLIENT_QUERY_STRING}`
       );
     });
   });
@@ -806,7 +816,7 @@ describe('Auth0', () => {
 
       await auth0.loginWithRedirect(REDIRECT_OPTIONS);
       expect(window.location.assign).toHaveBeenCalledWith(
-        `https://test.auth0.com/authorize?query=params${TEST_TELEMETRY_QUERY_STRING}`
+        `https://test.auth0.com/authorize?query=params${TEST_AUTH0_CLIENT_QUERY_STRING}`
       );
     });
 
@@ -818,7 +828,7 @@ describe('Auth0', () => {
         fragment: '/reset'
       });
       expect(window.location.assign).toHaveBeenCalledWith(
-        `https://test.auth0.com/authorize?query=params${TEST_TELEMETRY_QUERY_STRING}#/reset`
+        `https://test.auth0.com/authorize?query=params${TEST_AUTH0_CLIENT_QUERY_STRING}#/reset`
       );
     });
 
@@ -828,7 +838,19 @@ describe('Auth0', () => {
       await auth0.loginWithRedirect();
 
       expect(window.location.assign).toHaveBeenCalledWith(
-        `https://test.auth0.com/authorize?query=params${TEST_TELEMETRY_QUERY_STRING}`
+        `https://test.auth0.com/authorize?query=params${TEST_AUTH0_CLIENT_QUERY_STRING}`
+      );
+    });
+
+    it('redirects to authorize with custom auth0Client', async () => {
+      const auth0Client = { name: '__test_client_name__', version: '9.9.9' };
+      const { auth0, utils } = await setup({ auth0Client });
+
+      await auth0.loginWithRedirect();
+
+      expectToHaveBeenCalledWithAuth0ClientParam(
+        window.location.assign,
+        auth0Client
       );
     });
   });
@@ -1577,6 +1599,22 @@ describe('Auth0', () => {
           });
         });
 
+        it('loads authorize iframe with custom auth0Client', async () => {
+          const auth0Client = {
+            name: '__test_client_name__',
+            version: '9.9.9'
+          };
+
+          const { auth0, utils } = await setup({ auth0Client });
+
+          await auth0.getTokenSilently();
+
+          expectToHaveBeenCalledWithAuth0ClientParam(
+            utils.runIframe,
+            auth0Client
+          );
+        });
+
         it('calls the token endpoint with the correct params with different default scopes', async () => {
           const { auth0, cache, utils } = await setup({
             useRefreshTokens: true,
@@ -1639,7 +1677,7 @@ describe('Auth0', () => {
           });
 
           expect(utils.runIframe).toHaveBeenCalledWith(
-            `https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_TELEMETRY_QUERY_STRING}`,
+            `https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_AUTH0_CLIENT_QUERY_STRING}`,
             'https://test.auth0.com',
             undefined
           );
@@ -1799,7 +1837,7 @@ describe('Auth0', () => {
         const { auth0, utils } = await setup();
         await auth0.getTokenSilently(defaultOptionsIgnoreCacheTrue);
         expect(utils.runIframe).toHaveBeenCalledWith(
-          `https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_TELEMETRY_QUERY_STRING}`,
+          `https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_AUTH0_CLIENT_QUERY_STRING}`,
           'https://test.auth0.com',
           defaultOptionsIgnoreCacheTrue.timeoutInSeconds
         );
@@ -1809,7 +1847,7 @@ describe('Auth0', () => {
         const { auth0, utils } = await setup({ authorizeTimeoutInSeconds: 1 });
         await auth0.getTokenSilently(defaultOptionsIgnoreCacheTrue);
         expect(utils.runIframe).toHaveBeenCalledWith(
-          `https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_TELEMETRY_QUERY_STRING}`,
+          `https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_AUTH0_CLIENT_QUERY_STRING}`,
           'https://test.auth0.com',
           1
         );
@@ -1822,7 +1860,7 @@ describe('Auth0', () => {
           timeoutInSeconds: 1
         });
         expect(utils.runIframe).toHaveBeenCalledWith(
-          `https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_TELEMETRY_QUERY_STRING}`,
+          `https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_AUTH0_CLIENT_QUERY_STRING}`,
           'https://test.auth0.com',
           1
         );
@@ -2052,7 +2090,7 @@ describe('Auth0', () => {
 
       auth0.logout();
       expect(window.location.assign).toHaveBeenCalledWith(
-        `https://test.auth0.com/v2/logout?query=params${TEST_TELEMETRY_QUERY_STRING}`
+        `https://test.auth0.com/v2/logout?query=params${TEST_AUTH0_CLIENT_QUERY_STRING}`
       );
     });
 
@@ -2061,7 +2099,17 @@ describe('Auth0', () => {
 
       auth0.logout({ federated: true });
       expect(window.location.assign).toHaveBeenCalledWith(
-        `https://test.auth0.com/v2/logout?query=params${TEST_TELEMETRY_QUERY_STRING}&federated`
+        `https://test.auth0.com/v2/logout?query=params${TEST_AUTH0_CLIENT_QUERY_STRING}&federated`
+      );
+    });
+
+    it('calls `window.location.assign` with the correct url with custom `options.auth0Client`', async () => {
+      const auth0Client = { name: '__test_client_name__', version: '9.9.9' };
+      const { auth0 } = await setup({ auth0Client });
+      auth0.logout();
+      expectToHaveBeenCalledWithAuth0ClientParam(
+        window.location.assign,
+        auth0Client
       );
     });