@algolia/monitoring 1.0.0-alpha.9 → 1.0.0-beta.1

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.
@@ -26,14 +26,35 @@ function createBrowserLocalStorageCache(options) {
26
26
  function getNamespace() {
27
27
  return JSON.parse(getStorage().getItem(namespaceKey) || '{}');
28
28
  }
29
+ function setNamespace(namespace) {
30
+ getStorage().setItem(namespaceKey, JSON.stringify(namespace));
31
+ }
32
+ function removeOutdatedCacheItems() {
33
+ const timeToLive = options.timeToLive ? options.timeToLive * 1000 : null;
34
+ const namespace = getNamespace();
35
+ const filteredNamespaceWithoutOldFormattedCacheItems = Object.fromEntries(Object.entries(namespace).filter(([, cacheItem]) => {
36
+ return cacheItem.timestamp !== undefined;
37
+ }));
38
+ setNamespace(filteredNamespaceWithoutOldFormattedCacheItems);
39
+ if (!timeToLive) {
40
+ return;
41
+ }
42
+ const filteredNamespaceWithoutExpiredItems = Object.fromEntries(Object.entries(filteredNamespaceWithoutOldFormattedCacheItems).filter(([, cacheItem]) => {
43
+ const currentTimestamp = new Date().getTime();
44
+ const isExpired = cacheItem.timestamp + timeToLive < currentTimestamp;
45
+ return !isExpired;
46
+ }));
47
+ setNamespace(filteredNamespaceWithoutExpiredItems);
48
+ }
29
49
  return {
30
50
  get(key, defaultValue, events = {
31
51
  miss: () => Promise.resolve()
32
52
  }) {
33
53
  return Promise.resolve().then(() => {
34
- const keyAsString = JSON.stringify(key);
35
- const value = getNamespace()[keyAsString];
36
- return Promise.all([value || defaultValue(), value !== undefined]);
54
+ removeOutdatedCacheItems();
55
+ return getNamespace()[JSON.stringify(key)];
56
+ }).then(value => {
57
+ return Promise.all([value ? value.value : defaultValue(), value !== undefined]);
37
58
  }).then(([value, exists]) => {
38
59
  return Promise.all([value, exists || events.miss(value)]);
39
60
  }).then(([value]) => value);
@@ -41,7 +62,10 @@ function createBrowserLocalStorageCache(options) {
41
62
  set(key, value) {
42
63
  return Promise.resolve().then(() => {
43
64
  const namespace = getNamespace();
44
- namespace[JSON.stringify(key)] = value;
65
+ namespace[JSON.stringify(key)] = {
66
+ timestamp: new Date().getTime(),
67
+ value
68
+ };
45
69
  getStorage().setItem(namespaceKey, JSON.stringify(namespace));
46
70
  return value;
47
71
  });
@@ -171,6 +195,20 @@ function createStatefulHost(host, status = 'up') {
171
195
  };
172
196
  }
173
197
 
198
+ function _toPrimitive(t, r) {
199
+ if ("object" != typeof t || !t) return t;
200
+ var e = t[Symbol.toPrimitive];
201
+ if (void 0 !== e) {
202
+ var i = e.call(t, r || "default");
203
+ if ("object" != typeof i) return i;
204
+ throw new TypeError("@@toPrimitive must return a primitive value.");
205
+ }
206
+ return ("string" === r ? String : Number)(t);
207
+ }
208
+ function _toPropertyKey(t) {
209
+ var i = _toPrimitive(t, "string");
210
+ return "symbol" == typeof i ? i : i + "";
211
+ }
174
212
  function _defineProperty(obj, key, value) {
175
213
  key = _toPropertyKey(key);
176
214
  if (key in obj) {
@@ -185,20 +223,6 @@ function _defineProperty(obj, key, value) {
185
223
  }
186
224
  return obj;
187
225
  }
188
- function _toPrimitive(input, hint) {
189
- if (typeof input !== "object" || input === null) return input;
190
- var prim = input[Symbol.toPrimitive];
191
- if (prim !== undefined) {
192
- var res = prim.call(input, hint || "default");
193
- if (typeof res !== "object") return res;
194
- throw new TypeError("@@toPrimitive must return a primitive value.");
195
- }
196
- return (hint === "string" ? String : Number)(input);
197
- }
198
- function _toPropertyKey(arg) {
199
- var key = _toPrimitive(arg, "string");
200
- return typeof key === "symbol" ? key : String(key);
201
- }
202
226
 
203
227
  class AlgoliaError extends Error {
204
228
  constructor(message, name) {
@@ -219,7 +243,7 @@ class ErrorWithStackTrace extends AlgoliaError {
219
243
  }
220
244
  class RetryError extends ErrorWithStackTrace {
221
245
  constructor(stackTrace) {
222
- super('Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.', stackTrace, 'RetryError');
246
+ super('Unreachable hosts - your application id may be incorrect. If the error persists, please create a ticket at https://support.algolia.com/ sharing steps we can use to reproduce the issue.', stackTrace, 'RetryError');
223
247
  }
224
248
  }
225
249
  class ApiError extends ErrorWithStackTrace {
@@ -244,20 +268,9 @@ class DetailedApiError extends ApiError {
244
268
  this.error = error;
245
269
  }
246
270
  }
247
-
248
- function shuffle(array) {
249
- const shuffledArray = array;
250
- for (let c = array.length - 1; c > 0; c--) {
251
- const b = Math.floor(Math.random() * (c + 1));
252
- const a = array[c];
253
- shuffledArray[c] = array[b];
254
- shuffledArray[b] = a;
255
- }
256
- return shuffledArray;
257
- }
258
271
  function serializeUrl(host, path, queryParameters) {
259
272
  const queryParametersAsString = serializeQueryParameters(queryParameters);
260
- let url = `${host.protocol}://${host.url}/${path.charAt(0) === '/' ? path.substr(1) : path}`;
273
+ let url = `${host.protocol}://${host.url}${host.port ? `:${host.port}` : ''}/${path.charAt(0) === '/' ? path.substring(1) : path}`;
261
274
  if (queryParametersAsString.length) {
262
275
  url += `?${queryParametersAsString}`;
263
276
  }
@@ -265,7 +278,7 @@ function serializeUrl(host, path, queryParameters) {
265
278
  }
266
279
  function serializeQueryParameters(parameters) {
267
280
  const isObjectOrArray = value => Object.prototype.toString.call(value) === '[object Object]' || Object.prototype.toString.call(value) === '[object Array]';
268
- return Object.keys(parameters).map(key => `${key}=${encodeURIComponent(isObjectOrArray(parameters[key]) ? JSON.stringify(parameters[key]) : parameters[key])}`).join('&');
281
+ return Object.keys(parameters).map(key => `${key}=${encodeURIComponent(isObjectOrArray(parameters[key]) ? JSON.stringify(parameters[key]) : parameters[key]).replaceAll('+', '%20')}`).join('&');
269
282
  }
270
283
  function serializeData(request, requestOptions) {
271
284
  if (request.method === 'GET' || request.data === undefined && requestOptions.data === undefined) {
@@ -436,16 +449,13 @@ function createTransporter({
436
449
  if (host === undefined) {
437
450
  throw new RetryError(stackTraceWithoutCredentials(stackTrace));
438
451
  }
439
- let responseTimeout = requestOptions.timeout;
440
- if (responseTimeout === undefined) {
441
- responseTimeout = isRead ? timeouts.read : timeouts.write;
442
- }
452
+ let responseTimeout = isRead ? requestOptions.timeouts?.read || timeouts.read : requestOptions.timeouts?.write || timeouts.write;
443
453
  const payload = {
444
454
  data,
445
455
  headers,
446
456
  method: request.method,
447
457
  url: serializeUrl(host, request.path, queryParameters),
448
- connectTimeout: getTimeout(timeoutsCount, timeouts.connect),
458
+ connectTimeout: getTimeout(timeoutsCount, requestOptions.timeouts?.connect || timeouts.connect),
449
459
  responseTimeout: getTimeout(timeoutsCount, responseTimeout)
450
460
  };
451
461
  /**
@@ -673,42 +683,17 @@ function createXhrRequester() {
673
683
  }
674
684
 
675
685
  // Code generated by OpenAPI Generator (https://openapi-generator.tech), manual changes will be lost - read more on https://github.com/algolia/api-clients-automation. DO NOT EDIT.
676
- const apiClientVersion = '1.0.0-alpha.9';
677
- function getDefaultHosts(appId) {
686
+ const apiClientVersion = '1.0.0-beta.1';
687
+ function getDefaultHosts() {
678
688
  return [
679
- {
680
- url: `${appId}-dsn.algolia.net`,
681
- accept: 'read',
682
- protocol: 'https',
683
- },
684
- {
685
- url: `${appId}.algolia.net`,
686
- accept: 'write',
687
- protocol: 'https',
688
- },
689
- ].concat(shuffle([
690
- {
691
- url: `${appId}-1.algolianet.com`,
692
- accept: 'readWrite',
693
- protocol: 'https',
694
- },
695
- {
696
- url: `${appId}-2.algolianet.com`,
697
- accept: 'readWrite',
698
- protocol: 'https',
699
- },
700
- {
701
- url: `${appId}-3.algolianet.com`,
702
- accept: 'readWrite',
703
- protocol: 'https',
704
- },
705
- ]));
689
+ { url: 'status.algolia.com', accept: 'readWrite', protocol: 'https' },
690
+ ];
706
691
  }
707
692
  // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
708
693
  function createMonitoringClient({ appId: appIdOption, apiKey: apiKeyOption, authMode, algoliaAgents, ...options }) {
709
694
  const auth = createAuth(appIdOption, apiKeyOption, authMode);
710
695
  const transporter = createTransporter({
711
- hosts: getDefaultHosts(appIdOption),
696
+ hosts: getDefaultHosts(),
712
697
  ...options,
713
698
  algoliaAgent: getAlgoliaAgent({
714
699
  algoliaAgents,
@@ -758,17 +743,16 @@ function createMonitoringClient({ appId: appIdOption, apiKey: apiKeyOption, auth
758
743
  /**
759
744
  * This method allow you to send requests to the Algolia REST API.
760
745
  *
761
- * @summary Send requests to the Algolia REST API.
762
- * @param del - The del object.
763
- * @param del.path - Path of the endpoint, anything after \"/1\" must be specified.
764
- * @param del.parameters - Query parameters to apply to the current query.
746
+ * @param customDelete - The customDelete object.
747
+ * @param customDelete.path - Path of the endpoint, anything after \"/1\" must be specified.
748
+ * @param customDelete.parameters - Query parameters to apply to the current query.
765
749
  * @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions.
766
750
  */
767
- del({ path, parameters }, requestOptions) {
751
+ customDelete({ path, parameters }, requestOptions) {
768
752
  if (!path) {
769
- throw new Error('Parameter `path` is required when calling `del`.');
753
+ throw new Error('Parameter `path` is required when calling `customDelete`.');
770
754
  }
771
- const requestPath = '/1{path}'.replace('{path}', path);
755
+ const requestPath = '/{path}'.replace('{path}', path);
772
756
  const headers = {};
773
757
  const queryParameters = parameters ? parameters : {};
774
758
  const request = {
@@ -782,17 +766,16 @@ function createMonitoringClient({ appId: appIdOption, apiKey: apiKeyOption, auth
782
766
  /**
783
767
  * This method allow you to send requests to the Algolia REST API.
784
768
  *
785
- * @summary Send requests to the Algolia REST API.
786
- * @param get - The get object.
787
- * @param get.path - Path of the endpoint, anything after \"/1\" must be specified.
788
- * @param get.parameters - Query parameters to apply to the current query.
769
+ * @param customGet - The customGet object.
770
+ * @param customGet.path - Path of the endpoint, anything after \"/1\" must be specified.
771
+ * @param customGet.parameters - Query parameters to apply to the current query.
789
772
  * @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions.
790
773
  */
791
- get({ path, parameters }, requestOptions) {
774
+ customGet({ path, parameters }, requestOptions) {
792
775
  if (!path) {
793
- throw new Error('Parameter `path` is required when calling `get`.');
776
+ throw new Error('Parameter `path` is required when calling `customGet`.');
794
777
  }
795
- const requestPath = '/1{path}'.replace('{path}', path);
778
+ const requestPath = '/{path}'.replace('{path}', path);
796
779
  const headers = {};
797
780
  const queryParameters = parameters ? parameters : {};
798
781
  const request = {
@@ -804,9 +787,58 @@ function createMonitoringClient({ appId: appIdOption, apiKey: apiKeyOption, auth
804
787
  return transporter.request(request, requestOptions);
805
788
  },
806
789
  /**
807
- * List known incidents for selected clusters.
790
+ * This method allow you to send requests to the Algolia REST API.
791
+ *
792
+ * @param customPost - The customPost object.
793
+ * @param customPost.path - Path of the endpoint, anything after \"/1\" must be specified.
794
+ * @param customPost.parameters - Query parameters to apply to the current query.
795
+ * @param customPost.body - Parameters to send with the custom request.
796
+ * @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions.
797
+ */
798
+ customPost({ path, parameters, body }, requestOptions) {
799
+ if (!path) {
800
+ throw new Error('Parameter `path` is required when calling `customPost`.');
801
+ }
802
+ const requestPath = '/{path}'.replace('{path}', path);
803
+ const headers = {};
804
+ const queryParameters = parameters ? parameters : {};
805
+ const request = {
806
+ method: 'POST',
807
+ path: requestPath,
808
+ queryParameters,
809
+ headers,
810
+ data: body ? body : {},
811
+ };
812
+ return transporter.request(request, requestOptions);
813
+ },
814
+ /**
815
+ * This method allow you to send requests to the Algolia REST API.
816
+ *
817
+ * @param customPut - The customPut object.
818
+ * @param customPut.path - Path of the endpoint, anything after \"/1\" must be specified.
819
+ * @param customPut.parameters - Query parameters to apply to the current query.
820
+ * @param customPut.body - Parameters to send with the custom request.
821
+ * @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions.
822
+ */
823
+ customPut({ path, parameters, body }, requestOptions) {
824
+ if (!path) {
825
+ throw new Error('Parameter `path` is required when calling `customPut`.');
826
+ }
827
+ const requestPath = '/{path}'.replace('{path}', path);
828
+ const headers = {};
829
+ const queryParameters = parameters ? parameters : {};
830
+ const request = {
831
+ method: 'PUT',
832
+ path: requestPath,
833
+ queryParameters,
834
+ headers,
835
+ data: body ? body : {},
836
+ };
837
+ return transporter.request(request, requestOptions);
838
+ },
839
+ /**
840
+ * Retrieves known incidents for the selected clusters.
808
841
  *
809
- * @summary List incidents for selected clusters.
810
842
  * @param getClusterIncidents - The getClusterIncidents object.
811
843
  * @param getClusterIncidents.clusters - Subset of clusters, separated by comma.
812
844
  * @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions.
@@ -827,9 +859,8 @@ function createMonitoringClient({ appId: appIdOption, apiKey: apiKeyOption, auth
827
859
  return transporter.request(request, requestOptions);
828
860
  },
829
861
  /**
830
- * Report whether a cluster is operational.
862
+ * Retrieves the status of selected clusters.
831
863
  *
832
- * @summary List statuses of selected clusters.
833
864
  * @param getClusterStatus - The getClusterStatus object.
834
865
  * @param getClusterStatus.clusters - Subset of clusters, separated by comma.
835
866
  * @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions.
@@ -850,9 +881,8 @@ function createMonitoringClient({ appId: appIdOption, apiKey: apiKeyOption, auth
850
881
  return transporter.request(request, requestOptions);
851
882
  },
852
883
  /**
853
- * List known incidents for all clusters.
884
+ * Retrieves known incidents for all clusters.
854
885
  *
855
- * @summary List incidents.
856
886
  * @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions.
857
887
  */
858
888
  getIncidents(requestOptions) {
@@ -868,9 +898,8 @@ function createMonitoringClient({ appId: appIdOption, apiKey: apiKeyOption, auth
868
898
  return transporter.request(request, requestOptions);
869
899
  },
870
900
  /**
871
- * List the average times for indexing operations for selected clusters.
901
+ * Retrieves average times for indexing operations for selected clusters.
872
902
  *
873
- * @summary Get indexing times.
874
903
  * @param getIndexingTime - The getIndexingTime object.
875
904
  * @param getIndexingTime.clusters - Subset of clusters, separated by comma.
876
905
  * @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions.
@@ -891,27 +920,8 @@ function createMonitoringClient({ appId: appIdOption, apiKey: apiKeyOption, auth
891
920
  return transporter.request(request, requestOptions);
892
921
  },
893
922
  /**
894
- * List the servers belonging to clusters. The response depends on whether you authenticate your API request: - With authentication, the response lists the servers assigned to your Algolia application\'s cluster. - Without authentication, the response lists the servers for all Algolia clusters.
923
+ * Retrieves the average latency for search requests for selected clusters.
895
924
  *
896
- * @summary List servers.
897
- * @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions.
898
- */
899
- getInventory(requestOptions) {
900
- const requestPath = '/1/inventory/servers';
901
- const headers = {};
902
- const queryParameters = {};
903
- const request = {
904
- method: 'GET',
905
- path: requestPath,
906
- queryParameters,
907
- headers,
908
- };
909
- return transporter.request(request, requestOptions);
910
- },
911
- /**
912
- * List the average latency for search requests for selected clusters.
913
- *
914
- * @summary Get search latency times.
915
925
  * @param getLatency - The getLatency object.
916
926
  * @param getLatency.clusters - Subset of clusters, separated by comma.
917
927
  * @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions.
@@ -932,11 +942,10 @@ function createMonitoringClient({ appId: appIdOption, apiKey: apiKeyOption, auth
932
942
  return transporter.request(request, requestOptions);
933
943
  },
934
944
  /**
935
- * Report the aggregate value of a metric for a selected period of time.
945
+ * Retrieves metrics related to your Algolia infrastructure, aggregated over a selected time window. Access to this API is available as part of the [Premium or Elevate plans](https://www.algolia.com/pricing). You must authenticate requests with the `x-algolia-application-id` and `x-algolia-api-key` headers (using the Monitoring API key).
936
946
  *
937
- * @summary Get metrics for a given period.
938
947
  * @param getMetrics - The getMetrics object.
939
- * @param getMetrics.metric - Metric to report. For more information about the individual metrics, see the response. To include all metrics, use `*` as the parameter.
948
+ * @param getMetrics.metric - Metric to report. For more information about the individual metrics, see the description of the API response. To include all metrics, use `*`.
940
949
  * @param getMetrics.period - Period over which to aggregate the metrics: - `minute`. Aggregate the last minute. 1 data point per 10 seconds. - `hour`. Aggregate the last hour. 1 data point per minute. - `day`. Aggregate the last day. 1 data point per 10 minutes. - `week`. Aggregate the last week. 1 data point per hour. - `month`. Aggregate the last month. 1 data point per day.
941
950
  * @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions.
942
951
  */
@@ -963,7 +972,6 @@ function createMonitoringClient({ appId: appIdOption, apiKey: apiKeyOption, auth
963
972
  /**
964
973
  * Test whether clusters are reachable or not.
965
974
  *
966
- * @summary Test the reachability of clusters.
967
975
  * @param getReachability - The getReachability object.
968
976
  * @param getReachability.clusters - Subset of clusters, separated by comma.
969
977
  * @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions.
@@ -984,13 +992,12 @@ function createMonitoringClient({ appId: appIdOption, apiKey: apiKeyOption, auth
984
992
  return transporter.request(request, requestOptions);
985
993
  },
986
994
  /**
987
- * Report whether clusters are operational. The response depends on whether you authenticate your API request. - With authentication, the response includes the status of the cluster assigned to your Algolia application. - Without authentication, the response lists the statuses of all public Algolia clusters.
995
+ * Retrieves the servers that belong to clusters. The response depends on whether you authenticate your API request: - With authentication, the response lists the servers assigned to your Algolia application\'s cluster. - Without authentication, the response lists the servers for all Algolia clusters.
988
996
  *
989
- * @summary List cluster statuses.
990
997
  * @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions.
991
998
  */
992
- getStatus(requestOptions) {
993
- const requestPath = '/1/status';
999
+ getServers(requestOptions) {
1000
+ const requestPath = '/1/inventory/servers';
994
1001
  const headers = {};
995
1002
  const queryParameters = {};
996
1003
  const request = {
@@ -1002,54 +1009,19 @@ function createMonitoringClient({ appId: appIdOption, apiKey: apiKeyOption, auth
1002
1009
  return transporter.request(request, requestOptions);
1003
1010
  },
1004
1011
  /**
1005
- * This method allow you to send requests to the Algolia REST API.
1012
+ * Retrieves the status of all Algolia clusters and instances.
1006
1013
  *
1007
- * @summary Send requests to the Algolia REST API.
1008
- * @param post - The post object.
1009
- * @param post.path - Path of the endpoint, anything after \"/1\" must be specified.
1010
- * @param post.parameters - Query parameters to apply to the current query.
1011
- * @param post.body - Parameters to send with the custom request.
1012
1014
  * @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions.
1013
1015
  */
1014
- post({ path, parameters, body }, requestOptions) {
1015
- if (!path) {
1016
- throw new Error('Parameter `path` is required when calling `post`.');
1017
- }
1018
- const requestPath = '/1{path}'.replace('{path}', path);
1019
- const headers = {};
1020
- const queryParameters = parameters ? parameters : {};
1021
- const request = {
1022
- method: 'POST',
1023
- path: requestPath,
1024
- queryParameters,
1025
- headers,
1026
- data: body ? body : {},
1027
- };
1028
- return transporter.request(request, requestOptions);
1029
- },
1030
- /**
1031
- * This method allow you to send requests to the Algolia REST API.
1032
- *
1033
- * @summary Send requests to the Algolia REST API.
1034
- * @param put - The put object.
1035
- * @param put.path - Path of the endpoint, anything after \"/1\" must be specified.
1036
- * @param put.parameters - Query parameters to apply to the current query.
1037
- * @param put.body - Parameters to send with the custom request.
1038
- * @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions.
1039
- */
1040
- put({ path, parameters, body }, requestOptions) {
1041
- if (!path) {
1042
- throw new Error('Parameter `path` is required when calling `put`.');
1043
- }
1044
- const requestPath = '/1{path}'.replace('{path}', path);
1016
+ getStatus(requestOptions) {
1017
+ const requestPath = '/1/status';
1045
1018
  const headers = {};
1046
- const queryParameters = parameters ? parameters : {};
1019
+ const queryParameters = {};
1047
1020
  const request = {
1048
- method: 'PUT',
1021
+ method: 'GET',
1049
1022
  path: requestPath,
1050
1023
  queryParameters,
1051
1024
  headers,
1052
- data: body ? body : {},
1053
1025
  };
1054
1026
  return transporter.request(request, requestOptions);
1055
1027
  },
@@ -1057,6 +1029,7 @@ function createMonitoringClient({ appId: appIdOption, apiKey: apiKeyOption, auth
1057
1029
  }
1058
1030
 
1059
1031
  // Code generated by OpenAPI Generator (https://openapi-generator.tech), manual changes will be lost - read more on https://github.com/algolia/api-clients-automation. DO NOT EDIT.
1032
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
1060
1033
  function monitoringClient(appId, apiKey, options) {
1061
1034
  if (!appId || typeof appId !== 'string') {
1062
1035
  throw new Error('`appId` is missing.');