@sisense/sdk-rest-client 1.14.0 → 1.15.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.
@@ -1,2 +1,12 @@
1
1
  import { Authenticator } from './interfaces.js';
2
- export declare function getAuthenticator(url: string, username: string | undefined, password: string | undefined, token: string | undefined, wat: string | undefined, ssoEnabled: boolean | undefined, enableSilentPreAuth?: boolean): Authenticator | null;
2
+ declare type AuthenticatorConfig = {
3
+ url: string;
4
+ username?: string;
5
+ password?: string;
6
+ token?: string;
7
+ wat?: string;
8
+ ssoEnabled?: boolean;
9
+ enableSilentPreAuth?: boolean;
10
+ };
11
+ export declare function getAuthenticator({ url, username, password, token, wat, ssoEnabled, enableSilentPreAuth, }: AuthenticatorConfig): Authenticator | null;
12
+ export {};
@@ -2,7 +2,7 @@ import { PasswordAuthenticator } from './password-authenticator.js';
2
2
  import { BearerAuthenticator } from './bearer-authenticator.js';
3
3
  import { WatAuthenticator } from './wat-authenticator.js';
4
4
  import { SsoAuthenticator } from './sso-authenticator.js';
5
- export function getAuthenticator(url, username, password, token, wat, ssoEnabled, enableSilentPreAuth = false) {
5
+ export function getAuthenticator({ url, username, password, token, wat, ssoEnabled = false, enableSilentPreAuth = false, }) {
6
6
  // sso overrides all other auth methods
7
7
  if (ssoEnabled) {
8
8
  return new SsoAuthenticator(url, enableSilentPreAuth);
@@ -0,0 +1,16 @@
1
+ import { Authenticator } from './interfaces.js';
2
+ export declare class BaseAuthenticator implements Authenticator {
3
+ readonly type: Authenticator['type'];
4
+ private _valid;
5
+ protected _authenticating: boolean;
6
+ protected _tried: boolean;
7
+ protected _resolve: (value: boolean) => void;
8
+ protected readonly _result: Promise<boolean>;
9
+ protected constructor(type: Authenticator['type']);
10
+ isValid(): boolean;
11
+ invalidate(): void;
12
+ authenticate(): Promise<boolean>;
13
+ isAuthenticating(): boolean;
14
+ authenticated(): Promise<boolean>;
15
+ applyHeader(headers: HeadersInit): void;
16
+ }
@@ -0,0 +1,30 @@
1
+ export class BaseAuthenticator {
2
+ constructor(type) {
3
+ this._valid = true;
4
+ this._authenticating = false;
5
+ this._tried = false;
6
+ this._result = new Promise((resolve) => {
7
+ this._resolve = resolve;
8
+ });
9
+ this.type = type;
10
+ }
11
+ isValid() {
12
+ return this._valid;
13
+ }
14
+ invalidate() {
15
+ this._valid = false;
16
+ }
17
+ authenticate() {
18
+ return this._result;
19
+ }
20
+ isAuthenticating() {
21
+ return this._authenticating;
22
+ }
23
+ authenticated() {
24
+ return this._result;
25
+ }
26
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
27
+ applyHeader(headers) {
28
+ // Do nothing
29
+ }
30
+ }
@@ -1,15 +1,11 @@
1
1
  import { Authenticator } from './interfaces.js';
2
- export declare class BearerAuthenticator implements Authenticator {
3
- readonly type = "bearer";
2
+ import { BaseAuthenticator } from './base-authenticator.js';
3
+ export declare class BearerAuthenticator extends BaseAuthenticator {
4
4
  readonly bearer: string;
5
- readonly url: string;
6
- private _valid;
7
5
  constructor(url: string, bearer: string);
8
- isValid(): boolean;
9
- invalidate(): void;
10
- isAuthenticating(): boolean;
11
6
  applyHeader(headers: HeadersInit): void;
12
7
  authenticate(): Promise<boolean>;
8
+ authenticated(): Promise<boolean>;
13
9
  }
14
10
  /**
15
11
  * Checks if an authenticator is a BearerAuthenticator.
@@ -1,40 +1,21 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- /* eslint-disable no-underscore-dangle */
11
- /* eslint-disable @typescript-eslint/no-unsafe-member-access */
1
+ import { BaseAuthenticator } from './base-authenticator.js';
12
2
  import { appendHeaders } from './helpers.js';
13
- export class BearerAuthenticator {
3
+ export class BearerAuthenticator extends BaseAuthenticator {
14
4
  constructor(url, bearer) {
15
- this.type = 'bearer';
16
- this._valid = true;
5
+ super('bearer');
17
6
  this.bearer = bearer;
18
- this.url = url;
19
- }
20
- isValid() {
21
- return this._valid;
22
- }
23
- invalidate() {
24
- this._valid = false;
25
- }
26
- isAuthenticating() {
27
- return false;
7
+ this._resolve(true);
28
8
  }
29
9
  applyHeader(headers) {
30
10
  const authHeader = 'Bearer ' + this.bearer;
31
11
  appendHeaders(headers, { Authorization: authHeader });
32
12
  }
33
13
  authenticate() {
34
- return __awaiter(this, void 0, void 0, function* () {
35
- // TODO: implement authentication test
36
- return Promise.resolve(true);
37
- });
14
+ // TODO: implement authentication test
15
+ return this._result;
16
+ }
17
+ authenticated() {
18
+ return this._result;
38
19
  }
39
20
  }
40
21
  /**
package/dist/helpers.js CHANGED
@@ -1,12 +1,13 @@
1
1
  export const appendHeaders = (existingHeaders, additionalHeaders) => {
2
2
  for (const [headerName, headerValue] of Object.entries(additionalHeaders)) {
3
- if (existingHeaders instanceof Headers) {
4
- existingHeaders.set(headerName, headerValue);
5
- }
6
- else if (Array.isArray(existingHeaders)) {
3
+ if (Array.isArray(existingHeaders)) {
7
4
  existingHeaders.push([headerName, headerValue]);
8
5
  }
6
+ else if (typeof existingHeaders.set === 'function') {
7
+ existingHeaders.set(headerName, headerValue);
8
+ }
9
9
  else {
10
+ // eslint-disable-next-line security/detect-object-injection
10
11
  existingHeaders[headerName] = headerValue;
11
12
  }
12
13
  }
@@ -9,9 +9,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import fetchIntercept from 'fetch-intercept';
11
11
  import { getResponseInterceptor, errorInterceptor } from './interceptors.js';
12
- import { SsoAuthenticator } from './sso-authenticator.js';
12
+ import { isSsoAuthenticator } from './sso-authenticator.js';
13
13
  import { addQueryParamsToUrl } from './helpers.js';
14
- const AUTH_WAIT_MS = 10;
15
14
  export class HttpClient {
16
15
  constructor(url, auth, env) {
17
16
  if (!url.endsWith('/')) {
@@ -26,27 +25,15 @@ export class HttpClient {
26
25
  });
27
26
  }
28
27
  login() {
29
- return __awaiter(this, void 0, void 0, function* () {
30
- return this.auth.authenticate();
31
- });
28
+ return this.auth.authenticate();
32
29
  }
33
30
  call(url, config, requestConfig) {
34
31
  return __awaiter(this, void 0, void 0, function* () {
35
32
  if (this.auth.isAuthenticating()) {
36
- return new Promise((res) => {
37
- const retry = () => {
38
- // wait if still authenticating
39
- if (this.auth.isAuthenticating()) {
40
- setTimeout(retry, AUTH_WAIT_MS);
41
- return;
42
- }
43
- void this.call(url, config, requestConfig).then((r) => res(r));
44
- };
45
- retry();
46
- });
33
+ yield this.auth.authenticated();
47
34
  }
48
35
  config.headers = config.headers || {};
49
- if (this.auth instanceof SsoAuthenticator) {
36
+ if (isSsoAuthenticator(this.auth)) {
50
37
  // allows cookies to be sent
51
38
  config.credentials = 'include';
52
39
  }
@@ -1,7 +1,7 @@
1
- import { BearerAuthenticator } from './bearer-authenticator.js';
2
- import { WatAuthenticator } from './wat-authenticator.js';
3
- import { PasswordAuthenticator } from './password-authenticator.js';
4
- import { SsoAuthenticator } from './sso-authenticator.js';
1
+ import { isBearerAuthenticator } from './bearer-authenticator.js';
2
+ import { isWatAuthenticator } from './wat-authenticator.js';
3
+ import { isPasswordAuthenticator } from './password-authenticator.js';
4
+ import { isSsoAuthenticator } from './sso-authenticator.js';
5
5
  import { TranslatableError } from './translation/translatable-error.js';
6
6
  function handleErrorResponse(response) {
7
7
  if (!response.ok) {
@@ -16,13 +16,13 @@ function handleErrorResponse(response) {
16
16
  function handleUnauthorizedResponse(response, auth) {
17
17
  auth.invalidate();
18
18
  // skip login redirect for token auth
19
- if (auth instanceof PasswordAuthenticator) {
19
+ if (isPasswordAuthenticator(auth)) {
20
20
  throw new TranslatableError('errors.passwordAuthFailed');
21
21
  }
22
- if (auth instanceof BearerAuthenticator || auth instanceof WatAuthenticator) {
22
+ if (isBearerAuthenticator(auth) || isWatAuthenticator(auth)) {
23
23
  throw new TranslatableError('errors.tokenAuthFailed');
24
24
  }
25
- if (auth instanceof SsoAuthenticator && !auth.isAuthenticating()) {
25
+ if (isSsoAuthenticator(auth) && !auth.isAuthenticating()) {
26
26
  // try to reauthenticate
27
27
  void auth.authenticate();
28
28
  }
@@ -1,8 +1,9 @@
1
1
  export interface Authenticator {
2
- readonly type: 'password' | 'bearer' | 'wat' | 'sso';
2
+ readonly type: 'password' | 'bearer' | 'wat' | 'sso' | 'base';
3
3
  isValid: () => boolean;
4
4
  invalidate: () => void;
5
5
  authenticate: () => Promise<boolean>;
6
6
  isAuthenticating: () => boolean;
7
+ authenticated: () => Promise<boolean>;
7
8
  applyHeader: (headers: HeadersInit) => void;
8
9
  }
@@ -1,19 +1,13 @@
1
1
  /// <reference lib="dom" />
2
2
  import { Authenticator } from './interfaces.js';
3
- export declare class PasswordAuthenticator implements Authenticator {
4
- readonly type = "password";
3
+ import { BaseAuthenticator } from './base-authenticator.js';
4
+ export declare class PasswordAuthenticator extends BaseAuthenticator {
5
+ private readonly url;
6
+ private readonly body;
5
7
  private _authheader;
6
- readonly user: string;
7
- readonly pass: string;
8
- readonly url: string;
9
- private _authenticating;
10
- private _valid;
11
8
  constructor(url: string, user: string, pass: string);
12
- isValid(): boolean;
13
- invalidate(): void;
14
- isAuthenticating(): boolean;
15
- applyHeader(headers: HeadersInit): void;
16
9
  authenticate(): Promise<boolean>;
10
+ applyHeader(headers: HeadersInit): void;
17
11
  }
18
12
  /**
19
13
  * Checks if an authenticator is a PasswordAuthenticator.
@@ -7,58 +7,49 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- /* eslint-disable no-underscore-dangle */
11
- /* eslint-disable @typescript-eslint/no-unsafe-member-access */
12
- /* eslint-disable @typescript-eslint/restrict-plus-operands */
13
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
14
- /* eslint-disable @typescript-eslint/no-unsafe-return */
15
- /// <reference lib="dom" />
10
+ import { BaseAuthenticator } from './base-authenticator.js';
16
11
  import { appendHeaders } from './helpers.js';
17
- export class PasswordAuthenticator {
12
+ export class PasswordAuthenticator extends BaseAuthenticator {
18
13
  constructor(url, user, pass) {
19
- this.type = 'password';
20
- this._authenticating = false;
21
- this._valid = true;
22
- this._authheader = undefined;
23
- this.url = url;
24
- this.user = user;
25
- this.pass = pass;
26
- }
27
- isValid() {
28
- return this._valid;
29
- }
30
- invalidate() {
31
- this._valid = false;
32
- }
33
- isAuthenticating() {
34
- return this._authenticating;
35
- }
36
- applyHeader(headers) {
37
- const authHeader = 'Bearer ' + this._authheader;
38
- appendHeaders(headers, { Authorization: authHeader });
14
+ super('password');
15
+ this._authheader = '';
16
+ this.url = `${url}${!url.endsWith('/') ? '/' : ''}api/v1/authentication/login`;
17
+ const username = encodeURIComponent(user);
18
+ const password = encodeURIComponent(pass);
19
+ this.body = `username=${username}&password=${password}`;
39
20
  }
40
21
  authenticate() {
41
22
  return __awaiter(this, void 0, void 0, function* () {
42
- this._authenticating = true;
43
- const url = `${this.url}${!this.url.endsWith('/') ? '/' : ''}api/v1/authentication/login`;
44
- const username = encodeURIComponent(this.user);
45
- const password = encodeURIComponent(this.pass);
46
- yield fetch(url, {
47
- method: 'POST',
48
- headers: {
49
- accept: 'application/json',
50
- 'Content-Type': 'application/x-www-form-urlencoded',
51
- },
52
- body: `username=${username}&password=${password}`,
53
- })
54
- .then((response) => response.json())
55
- .then((responseJson) => {
56
- this._authheader = responseJson.access_token;
23
+ if (this._tried) {
24
+ return this._result;
25
+ }
26
+ this._tried = true;
27
+ try {
28
+ this._authenticating = true;
29
+ const response = yield fetch(this.url, {
30
+ method: 'POST',
31
+ headers: {
32
+ accept: 'application/json',
33
+ 'Content-Type': 'application/x-www-form-urlencoded',
34
+ },
35
+ body: this.body,
36
+ });
37
+ if (response.ok) {
38
+ const json = yield response.json();
39
+ this._authheader = json.access_token;
40
+ }
41
+ }
42
+ finally {
43
+ this._resolve(!!this._authheader);
57
44
  this._authenticating = false;
58
- });
59
- return !!this._authheader;
45
+ }
46
+ return this._result;
60
47
  });
61
48
  }
49
+ applyHeader(headers) {
50
+ const authHeader = 'Bearer ' + this._authheader;
51
+ appendHeaders(headers, { Authorization: authHeader });
52
+ }
62
53
  }
63
54
  /**
64
55
  * Checks if an authenticator is a PasswordAuthenticator.
@@ -1,19 +1,13 @@
1
1
  /// <reference lib="dom" />
2
2
  import { Authenticator } from './interfaces.js';
3
- export declare class SsoAuthenticator implements Authenticator {
4
- readonly type = "sso";
3
+ import { BaseAuthenticator } from './base-authenticator.js';
4
+ export declare class SsoAuthenticator extends BaseAuthenticator {
5
5
  readonly url: string;
6
6
  private _enableSilentPreAuth;
7
- private _valid;
8
- private _authenticating;
9
7
  constructor(url: string, enableSilentPreAuth?: boolean);
10
- isValid(): boolean;
11
- invalidate(): void;
12
- isAuthenticating(): boolean;
13
- applyHeader(headers: HeadersInit): HeadersInit;
8
+ authenticate(silent?: boolean): Promise<boolean>;
14
9
  private authenticateSilent;
15
10
  private checkAuthentication;
16
- authenticate(silent?: boolean): Promise<boolean>;
17
11
  }
18
12
  /**
19
13
  * Checks if the authenticator is SSO authenticator
@@ -8,26 +8,40 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ import { BaseAuthenticator } from './base-authenticator.js';
11
12
  import { TranslatableError } from './translation/translatable-error.js';
12
- export class SsoAuthenticator {
13
+ export class SsoAuthenticator extends BaseAuthenticator {
13
14
  constructor(url, enableSilentPreAuth = false) {
14
- this.type = 'sso';
15
- this._valid = true;
16
- this._authenticating = false;
15
+ super('sso');
17
16
  this.url = url;
18
17
  this._enableSilentPreAuth = enableSilentPreAuth;
19
18
  }
20
- isValid() {
21
- return this._valid;
22
- }
23
- invalidate() {
24
- this._valid = false;
25
- }
26
- isAuthenticating() {
27
- return this._authenticating;
28
- }
29
- applyHeader(headers) {
30
- return headers;
19
+ authenticate(silent = true) {
20
+ var _a;
21
+ return __awaiter(this, void 0, void 0, function* () {
22
+ try {
23
+ this._authenticating = true;
24
+ const { isAuthenticated, loginUrl } = yield this.checkAuthentication();
25
+ if (isAuthenticated) {
26
+ this._resolve(true);
27
+ return yield this._result;
28
+ }
29
+ if (this._enableSilentPreAuth && silent) {
30
+ yield this.authenticateSilent(loginUrl);
31
+ const { isAuthenticated } = yield this.checkAuthentication();
32
+ if (isAuthenticated) {
33
+ this._resolve(true);
34
+ return yield this._result;
35
+ }
36
+ }
37
+ (_a = window === null || window === void 0 ? void 0 : window.location) === null || _a === void 0 ? void 0 : _a.replace(loginUrl);
38
+ }
39
+ finally {
40
+ this._resolve(false);
41
+ this._authenticating = false;
42
+ }
43
+ return this._result;
44
+ });
31
45
  }
32
46
  authenticateSilent(loginUrl) {
33
47
  return __awaiter(this, void 0, void 0, function* () {
@@ -65,26 +79,6 @@ export class SsoAuthenticator {
65
79
  };
66
80
  });
67
81
  }
68
- authenticate(silent = true) {
69
- var _a;
70
- return __awaiter(this, void 0, void 0, function* () {
71
- this._authenticating = true;
72
- const { isAuthenticated, loginUrl } = yield this.checkAuthentication();
73
- if (!isAuthenticated) {
74
- if (this._enableSilentPreAuth && silent) {
75
- yield this.authenticateSilent(loginUrl);
76
- return this.authenticate(false);
77
- }
78
- (_a = window === null || window === void 0 ? void 0 : window.location) === null || _a === void 0 ? void 0 : _a.assign(loginUrl);
79
- return false;
80
- }
81
- else {
82
- // no authentication needed, indicate success
83
- this._authenticating = false;
84
- return true;
85
- }
86
- });
87
- }
88
82
  }
89
83
  /**
90
84
  * Checks if the authenticator is SSO authenticator
@@ -1,19 +1,14 @@
1
1
  /// <reference lib="dom" />
2
2
  import { Authenticator } from './interfaces.js';
3
- export declare class WatAuthenticator implements Authenticator {
4
- readonly type = "wat";
3
+ import { BaseAuthenticator } from './base-authenticator.js';
4
+ export declare class WatAuthenticator extends BaseAuthenticator {
5
5
  private _initialiser;
6
6
  private _webSessionToken;
7
- readonly wat: string;
8
- readonly url: string;
9
- private _authenticating;
10
- private _valid;
7
+ private readonly url;
8
+ private readonly body;
11
9
  constructor(url: string, wat: string);
12
- isValid(): boolean;
13
- invalidate(): void;
14
- isAuthenticating(): boolean;
15
- applyHeader(headers: HeadersInit): void;
16
10
  authenticate(): Promise<boolean>;
11
+ applyHeader(headers: HeadersInit): void;
17
12
  }
18
13
  /**
19
14
  * Checks if an authenticator is a WatAuthenticator.
@@ -8,65 +8,54 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
- /* eslint-disable no-underscore-dangle */
11
+ import { BaseAuthenticator } from './base-authenticator.js';
12
12
  import { appendHeaders } from './helpers.js';
13
- export class WatAuthenticator {
13
+ export class WatAuthenticator extends BaseAuthenticator {
14
14
  constructor(url, wat) {
15
- this.type = 'wat';
16
- this._authenticating = false;
17
- this._valid = true;
18
- this._initialiser = undefined;
19
- this.url = url;
20
- this.wat = wat;
21
- }
22
- isValid() {
23
- return this._valid;
24
- }
25
- invalidate() {
26
- this._valid = false;
27
- }
28
- isAuthenticating() {
29
- return this._authenticating;
30
- }
31
- applyHeader(headers) {
32
- if (!!this._webSessionToken && !!this._initialiser) {
33
- const authHeader = this._webSessionToken;
34
- const initialiserHeader = this._initialiser;
35
- appendHeaders(headers, { Authorization: authHeader, Initialiser: initialiserHeader });
36
- }
15
+ super('wat');
16
+ this.url = `${url}${!url.endsWith('/') ? '/' : ''}api/v1/wat/sessionToken`;
17
+ this.body = `{"webAccessToken": "${wat}"}`;
37
18
  }
38
19
  authenticate() {
39
20
  return __awaiter(this, void 0, void 0, function* () {
40
- this._authenticating = true;
41
- // call API to generate web session token
42
- const url = `${this.url}${!this.url.endsWith('/') ? '/' : ''}api/v1/wat/sessionToken`;
21
+ if (this._tried) {
22
+ return this._result;
23
+ }
24
+ this._tried = true;
43
25
  try {
44
- const response = yield fetch(url, {
26
+ this._authenticating = true;
27
+ const response = yield fetch(this.url, {
45
28
  method: 'POST',
46
29
  headers: {
47
30
  accept: 'application/json',
48
31
  'Content-Type': 'application/json',
49
32
  },
50
- body: `{"webAccessToken": "${this.wat}"}`,
33
+ body: this.body,
51
34
  });
52
35
  if (response.ok) {
53
- const responseJson = (yield response.json());
36
+ const responseJson = yield response.json();
54
37
  this._initialiser = responseJson.initialiser;
55
38
  this._webSessionToken = responseJson.webSessionToken;
56
- this._authenticating = false;
57
- }
58
- else {
59
- return false;
39
+ this._resolve(true);
60
40
  }
61
41
  }
62
42
  catch (e) {
63
- this._initialiser = undefined;
43
+ // empty catch block
44
+ }
45
+ finally {
46
+ this._resolve(false);
64
47
  this._authenticating = false;
65
- return false;
66
48
  }
67
- return true;
49
+ return this._result;
68
50
  });
69
51
  }
52
+ applyHeader(headers) {
53
+ if (!!this._webSessionToken && !!this._initialiser) {
54
+ const authHeader = this._webSessionToken;
55
+ const initialiserHeader = this._initialiser;
56
+ appendHeaders(headers, { Authorization: authHeader, Initialiser: initialiserHeader });
57
+ }
58
+ }
70
59
  }
71
60
  /**
72
61
  * Checks if an authenticator is a WatAuthenticator.
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "Sisense",
12
12
  "Compose SDK"
13
13
  ],
14
- "version": "1.14.0",
14
+ "version": "1.15.1",
15
15
  "type": "module",
16
16
  "exports": "./dist/index.js",
17
17
  "main": "./dist/index.js",
@@ -20,7 +20,7 @@
20
20
  "author": "Sisense",
21
21
  "license": "SEE LICENSE IN LICENSE.md",
22
22
  "dependencies": {
23
- "@sisense/sdk-common": "^1.14.0",
23
+ "@sisense/sdk-common": "^1.15.1",
24
24
  "fetch-intercept": "^2.4.0"
25
25
  },
26
26
  "scripts": {