@adobe/spacecat-shared-rum-api-client 2.20.2 → 2.21.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [@adobe/spacecat-shared-rum-api-client-v2.21.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.20.2...@adobe/spacecat-shared-rum-api-client-v2.21.0) (2025-02-11)
2
+
3
+
4
+ ### Features
5
+
6
+ * **rum-api-client:** admin key support ([#590](https://github.com/adobe/spacecat-shared/issues/590)) ([0213fa3](https://github.com/adobe/spacecat-shared/commit/0213fa3697cfc006c9edb5038a10ecc896cb6b6e))
7
+
1
8
  # [@adobe/spacecat-shared-rum-api-client-v2.20.2](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.20.1...@adobe/spacecat-shared-rum-api-client-v2.20.2) (2025-02-08)
2
9
 
3
10
 
package/README.md CHANGED
@@ -12,18 +12,22 @@ npm install @adobe/spacecat-shared-rum-api-client
12
12
 
13
13
  ## Usage
14
14
 
15
- #### Creating and instance from Helix UniversalContext
15
+ #### Creating an instance from Helix UniversalContext
16
16
 
17
17
  ```js
18
- const context = {}; // Your AWS Lambda context object
18
+ // The context must include an 'env' property so that the client can use RUM_ADMIN_KEY if needed.
19
+ const context = { env: process.env };
19
20
  const rumApiClient = RUMAPIClient.createFrom(context);
20
-
21
21
  ```
22
22
 
23
- #### From constructor
23
+ #### Using the constructor
24
24
 
25
25
  ```js
26
- const rumApiClient = new RUMAPIClient();
26
+ // Optionally, pass a configuration and a logger objects to the constructor.
27
+ // If you want the client to automatically fetch the domainkey for a domain,
28
+ // provide the admin key as 'rumAdminKey'. If omitted, you must provide the domainkey
29
+ // in the query options.
30
+ const rumApiClient = new RUMAPIClient({ rumAdminKey: '<admin-key>' }, logger);
27
31
  ```
28
32
 
29
33
  ### Running a query
@@ -31,25 +35,38 @@ const rumApiClient = new RUMAPIClient();
31
35
  ```js
32
36
  const opts = {
33
37
  domain: 'www.aem.live',
38
+ // Either provide the domainkey directly...
34
39
  domainkey: '<domain-key>',
40
+ // ...or omit it to let the client auto-fetch it if an admin key is configured.
35
41
  granularity: 'hourly',
36
42
  interval: 10
37
- }
43
+ };
38
44
 
39
45
  const result = await rumApiClient.query('cwv', opts);
40
- console.log(`Query result: ${result}`)
46
+ console.log(`Query result: ${result}`);
41
47
  ```
42
48
 
43
- **Note**: all queries must be lowercase
49
+ **Note**: All query names must be lowercase.
44
50
 
45
51
  ### Query Options: the 'opts' object
46
52
 
47
- | option | required | default | remarks |
48
- |-------------|----------|---------|---------------------|
49
- | domain | yes | | |
50
- | domainkey | yes | | |
51
- | interval | no | 7 | days in integer |
52
- | granularity | no | daily | 'daily' or 'hourly' |
53
+ | Option | Required | Default | Remarks |
54
+ |-------------|----------|---------|----------------------------------------------------------|
55
+ | domain | yes | | The domain for which to fetch data. |
56
+ | domainkey | no | | Provide directly or omit to auto-fetch using `RUM_ADMIN_KEY`. |
57
+ | interval | no | 7 | Interval in days (integer). |
58
+ | granularity | no | daily | 'daily' or 'hourly'. |
59
+
60
+
61
+ ### Retrieving and Caching the Domainkey
62
+
63
+ You can also retrieve the domainkey for a given domain directly using the new `retrieveDomainkey` method.
64
+ This method will fetch the domainkey using the admin key (if necessary) and cache it for subsequent calls.
65
+
66
+ ```js
67
+ const domainKey = await rumApiClient.retrieveDomainkey('www.example.com');
68
+ console.log(`Domain key: ${domainKey}`);
69
+ ```
53
70
 
54
71
  ## Available queries
55
72
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-rum-api-client",
3
- "version": "2.20.2",
3
+ "version": "2.21.0",
4
4
  "description": "Shared modules of the Spacecat Services - Rum API client",
5
5
  "type": "module",
6
6
  "engines": {
package/src/index.d.ts CHANGED
@@ -13,52 +13,85 @@
13
13
  import { UniversalContext } from '@adobe/helix-universal';
14
14
 
15
15
  export interface RUMAPIOptions {
16
- domain: string;
17
- domainkey: string;
18
- interval?: number;
19
- granularity?: 'hourly' | 'daily';
20
- groupedURLs?: Array<{
21
- name: string;
22
- pattern: string;
23
- }>;
16
+ /** The domain for which to fetch data. */
17
+ domain: string;
18
+
19
+ /**
20
+ * The domain key. If not provided, the client will attempt to auto-fetch the domainkey
21
+ * using the admin key (if configured). Fetched domainkeys are cached for subsequent calls.
22
+ */
23
+ domainkey?: string;
24
+
25
+ /**
26
+ * Interval in days.
27
+ * @default 7
28
+ */
29
+ interval?: number;
30
+
31
+ /**
32
+ * Granularity can be 'hourly' or 'daily'.
33
+ * @default 'daily'
34
+ */
35
+ granularity?: 'hourly' | 'daily';
36
+
37
+ groupedURLs?: Array<{
38
+ name: string;
39
+ pattern: string;
40
+ }>;
24
41
  }
25
42
 
26
43
  export default class RUMAPIClient {
27
44
  /**
28
45
  * Static factory method to create an instance of RUMAPIClient.
29
- * @param {UniversalContext} context - An object containing the AWS Lambda context information
46
+ *
47
+ * @param {UniversalContext} context - An object containing the HelixUniversal context.
48
+ * The context must include an `env` property that can optionally include a `RUM_ADMIN_KEY`.
30
49
  * @returns An instance of RUMAPIClient.
31
- * @remarks This method is designed to create a new instance from an AWS Lambda context.
32
- * The created instance is stored in the Lambda context, and subsequent calls to
33
- * this method will return the singleton instance if previously created.
50
+ * @remarks This method creates a new instance from a HelixUniversal context and
51
+ * caches it on the context.
34
52
  */
35
53
  static createFrom(context: UniversalContext): RUMAPIClient;
36
54
 
37
55
  /**
38
56
  * Constructor for creating an instance of RUMAPIClient.
57
+ *
58
+ * @param options Optional configuration. If you want the client to auto-fetch the domainkey,
59
+ * provide the admin key as `rumAdminKey`.
60
+ * @param log Optional logger, defaults to `console`.
39
61
  */
40
- constructor();
62
+ constructor(options?: { rumAdminKey?: string }, log?: Console);
41
63
 
42
64
  /**
43
- * Asynchronous method to run queries against RUM Bundler API.
44
- * @param {string} query - Name of the query to run.
45
- * @param {RUMAPIOptions} opts - A object containing options for query to run.
65
+ * Asynchronous method to run a query against the RUM Bundler API.
66
+ *
67
+ * @param query - Name of the query to run.
68
+ * @param opts - A object containing options for the query. Either provide a `domainkey`
69
+ * here or configure an admin key so that the client can fetch it automatically.
46
70
  * @returns A Promise resolving to an object with the query results.
47
71
  * @remarks See the README.md for the available queries.
48
72
  */
49
- query(query: string, opts?: RUMAPIOptions): Promise<object>;
73
+ query(query: string, opts: RUMAPIOptions): Promise<object>;
50
74
 
51
75
  /**
52
- * Asynchronous method to run multiple queries against the data fetched from RUM Bundler API.
76
+ * Asynchronous method to run multiple queries against the data fetched from the RUM Bundler API.
53
77
  *
54
78
  * This method makes a single call to the RUM Bundler API to fetch the raw data, then applies
55
79
  * all the requested queries to this raw data. The results are returned in an object where each
56
80
  * key corresponds to a query name and each value contains the result of that query.
57
81
  *
58
- * @param {string[]} queries - An array of query names to execute.
59
- * @param {RUMAPIOptions} [opts] - Optional object containing options for the queries.
60
- * @returns {Promise<object>} A Promise that resolves to an object where each key is the name
61
- * of a query, and each value is the result of that query.
82
+ * @param queries - An array of query names to execute.
83
+ * @param opts - Optional object containing options for the queries.
84
+ * @returns A Promise that resolves to an object where each key is the name
85
+ * of a query, and each value is the result of that query.
86
+ */
87
+ queryMulti(queries: string[], opts: RUMAPIOptions): Promise<object>;
88
+
89
+ /**
90
+ * Retrieves the domainkey for the given domain. If the domainkey was already fetched,
91
+ * the cached value is returned.
92
+ *
93
+ * @param domain - The domain for which to retrieve the domainkey.
94
+ * @returns A Promise resolving to the domainkey string.
62
95
  */
63
- queryMulti(queries: string[], opts?: RUMAPIOptions): Promise<object>;
96
+ retrieveDomainkey(domain: string): Promise<string>;
64
97
  }
package/src/index.js CHANGED
@@ -9,7 +9,7 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
- import { hasText } from '@adobe/spacecat-shared-utils';
12
+ import { hasText, fetch } from '@adobe/spacecat-shared-utils';
13
13
  import { fetchBundles } from './common/rum-bundler-client.js';
14
14
  import notfound from './functions/404.js';
15
15
  import notfoundInternalLinks from './functions/404-internal-links.js';
@@ -23,6 +23,9 @@ import rageclick from './functions/opportunities/rageclick.js';
23
23
  import highInorganicHighBounceRate from './functions/opportunities/high-inorganic-high-bounce-rate.js';
24
24
  import highOrganicLowCtr from './functions/opportunities/high-organic-low-ctr.js';
25
25
 
26
+ // exported for tests
27
+ export const RUM_BUNDLER_API_HOST = 'https://bundles.aem.page';
28
+
26
29
  const HANDLERS = {
27
30
  404: notfound,
28
31
  '404-internal-links': notfoundInternalLinks,
@@ -47,17 +50,65 @@ function sanitize(opts) {
47
50
 
48
51
  export default class RUMAPIClient {
49
52
  static createFrom(context) {
50
- const { log = console } = context;
53
+ const { env, log = console } = context;
54
+ const { RUM_ADMIN_KEY: rumAdminKey } = env;
51
55
 
52
56
  if (context.rumApiClient) return context.rumApiClient;
53
57
 
54
- const client = new RUMAPIClient(log);
58
+ const client = new RUMAPIClient({ rumAdminKey }, log);
55
59
  context.rumApiClient = client;
56
60
  return client;
57
61
  }
58
62
 
59
- constructor(log) {
63
+ constructor({ rumAdminKey }, log) {
60
64
  this.log = log;
65
+ this.rumAdminKey = rumAdminKey;
66
+ this.domainkeyCache = {};
67
+ }
68
+
69
+ async _exchangeDomainkey(domain) {
70
+ if (hasText(this.domainkeyCache[domain])) {
71
+ return this.domainkeyCache[domain];
72
+ }
73
+
74
+ const resp = await fetch(`${RUM_BUNDLER_API_HOST}/domainkey/${domain}`, {
75
+ headers: {
76
+ Authorization: `Bearer ${this.rumAdminKey}`,
77
+ },
78
+ });
79
+
80
+ if (!resp.ok) {
81
+ throw new Error(`Error during fetching domainkey for domain '${domain} using admin key. Status: ${resp.status}`);
82
+ }
83
+
84
+ try {
85
+ const json = await resp.json();
86
+ if (!hasText(json.domainkey)) {
87
+ throw new Error(`Unexpected response: ${JSON.stringify(json)}`);
88
+ }
89
+ this.domainkeyCache[domain] = json.domainkey;
90
+ return json.domainkey;
91
+ } catch (e) {
92
+ throw new Error(`Error during fetching domainkey for domain '${domain} using admin key. Error: ${e.message}`);
93
+ }
94
+ }
95
+
96
+ async _getDomainkey(opts) {
97
+ const { domain, domainkey } = opts;
98
+
99
+ if (!hasText(domainkey) && !hasText(this.rumAdminKey)) {
100
+ throw new Error('You need to provide a \'domainkey\' or set RUM_ADMIN_KEY env variable');
101
+ }
102
+
103
+ if (hasText(domainkey)) {
104
+ return domainkey;
105
+ }
106
+
107
+ return this._exchangeDomainkey(domain);
108
+ }
109
+
110
+ async retrieveDomainkey(domain) {
111
+ return this._exchangeDomainkey(domain);
61
112
  }
62
113
 
63
114
  // eslint-disable-next-line class-methods-use-this
@@ -66,8 +117,11 @@ export default class RUMAPIClient {
66
117
  if (!handler) throw new Error(`Unknown query ${query}`);
67
118
 
68
119
  try {
120
+ const domainkey = await this._getDomainkey(opts);
121
+
69
122
  const bundles = await fetchBundles({
70
123
  ...opts,
124
+ domainkey,
71
125
  checkpoints,
72
126
  }, this.log);
73
127
 
@@ -96,9 +150,12 @@ export default class RUMAPIClient {
96
150
  }
97
151
 
98
152
  try {
153
+ const domainkey = await this._getDomainkey(opts);
154
+
99
155
  // Fetch bundles with deduplicated checkpoints
100
156
  const bundles = await fetchBundles({
101
157
  ...opts,
158
+ domainkey,
102
159
  checkpoints: [...allCheckpoints],
103
160
  }, this.log);
104
161