@peassoft/mnr-web-topline 4.1.0 → 4.2.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 (37) hide show
  1. package/dist/en/modules/env/index.d.ts +8 -0
  2. package/dist/en/modules/env/index.js +15 -0
  3. package/dist/en/modules/local-db/create-request-error.d.ts +2 -2
  4. package/dist/en/modules/local-db/create-request-error.js +2 -2
  5. package/dist/en/modules/local-db/init-db.js +3 -3
  6. package/dist/en/modules/logger/index.d.ts +1 -1
  7. package/dist/en/modules/logger/index.js +19 -2
  8. package/dist/en/modules/request/index.js +5 -5
  9. package/dist/en/modules/topline-service/inner-service.js +3 -3
  10. package/dist/en/modules/websocket/index.js +2 -2
  11. package/dist/en/parts/logout/actions/perform-logout/index.d.ts +1 -20
  12. package/dist/en/parts/logout/actions/perform-logout/index.js +9 -22
  13. package/dist/en/parts/password-recovery/actions/create-claim/index.d.ts +1 -13
  14. package/dist/en/parts/password-recovery/actions/create-claim/index.js +5 -14
  15. package/dist/en/parts/profile/actions/change-password/index.js +2 -2
  16. package/dist/en/parts/shell/actions/refresh-user/index.d.ts +1 -22
  17. package/dist/en/parts/shell/actions/refresh-user/index.js +13 -27
  18. package/dist/en/shared/procedures/process-response.js +3 -3
  19. package/dist/ru/modules/env/index.d.ts +8 -0
  20. package/dist/ru/modules/env/index.js +15 -0
  21. package/dist/ru/modules/local-db/create-request-error.d.ts +2 -2
  22. package/dist/ru/modules/local-db/create-request-error.js +2 -2
  23. package/dist/ru/modules/local-db/init-db.js +3 -3
  24. package/dist/ru/modules/logger/index.d.ts +1 -1
  25. package/dist/ru/modules/logger/index.js +19 -2
  26. package/dist/ru/modules/request/index.js +5 -5
  27. package/dist/ru/modules/topline-service/inner-service.js +3 -3
  28. package/dist/ru/modules/websocket/index.js +2 -2
  29. package/dist/ru/parts/logout/actions/perform-logout/index.d.ts +1 -20
  30. package/dist/ru/parts/logout/actions/perform-logout/index.js +9 -22
  31. package/dist/ru/parts/password-recovery/actions/create-claim/index.d.ts +1 -13
  32. package/dist/ru/parts/password-recovery/actions/create-claim/index.js +5 -14
  33. package/dist/ru/parts/profile/actions/change-password/index.js +2 -2
  34. package/dist/ru/parts/shell/actions/refresh-user/index.d.ts +1 -22
  35. package/dist/ru/parts/shell/actions/refresh-user/index.js +13 -27
  36. package/dist/ru/shared/procedures/process-response.js +3 -3
  37. package/package.json +7 -8
@@ -1,7 +1,15 @@
1
+ /**
2
+ * Detect whether the app is running in local environment
3
+ */
4
+ export declare function isLocal(): boolean;
1
5
  /**
2
6
  * Return base URL for requests to backend API
3
7
  */
4
8
  export declare function getApiBaseUrl(): string;
9
+ /**
10
+ * Return URL for logs backend
11
+ */
12
+ export declare function getLogsUrl(): string;
5
13
  /**
6
14
  * Return URL for websocket connection
7
15
  */
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Detect whether the app is running in local environment
3
+ */
4
+ export function isLocal() {
5
+ return window.location.hostname === 'localhost';
6
+ }
1
7
  /**
2
8
  * Return base URL for requests to backend API
3
9
  */
@@ -7,6 +13,15 @@ export function getApiBaseUrl() {
7
13
  }
8
14
  return '/api';
9
15
  }
16
+ /**
17
+ * Return URL for logs backend
18
+ */
19
+ export function getLogsUrl() {
20
+ if (window.location.hostname === 'localhost') {
21
+ return 'http://localhost:5005/logs';
22
+ }
23
+ return '/front-logs/logs';
24
+ }
10
25
  /**
11
26
  * Return URL for websocket connection
12
27
  */
@@ -1,5 +1,5 @@
1
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
2
2
  /**
3
3
  * Create error for failed request to local database
4
4
  */
5
- export default function createRequestError(reason: string): WebError;
5
+ export default function createRequestError(reason: string): VError;
@@ -1,9 +1,9 @@
1
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
2
2
  /**
3
3
  * Create error for failed request to local database
4
4
  */
5
5
  export default function createRequestError(reason) {
6
- return new WebError({
6
+ return new VError({
7
7
  name: 'LocalDbRequestError'
8
8
  }, `request to local database failed (${reason})`);
9
9
  }
@@ -1,4 +1,4 @@
1
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
2
2
  const DB_NAME = 'mnr_topline';
3
3
  const DB_VERSION = 2;
4
4
  // Main store data is deleted when a user logs out.
@@ -27,7 +27,7 @@ export function initDb() {
27
27
  return new Promise((resolve, reject) => {
28
28
  const openDbReq = window.indexedDB.open(DB_NAME, DB_VERSION);
29
29
  openDbReq.onerror = function (_e) {
30
- reject(new WebError({
30
+ reject(new VError({
31
31
  name: 'LocalDbInitializationError'
32
32
  }, 'opening local database failed'));
33
33
  };
@@ -53,7 +53,7 @@ export function initDb() {
53
53
  }
54
54
  };
55
55
  openDbReq.onblocked = function () {
56
- reject(new WebError({
56
+ reject(new VError({
57
57
  name: 'LocalDbInitializationError'
58
58
  }, 'failed upgrading local database due to it is blocked'));
59
59
  };
@@ -1,2 +1,2 @@
1
- export declare function logError(err: Error): void;
1
+ export declare function logError(err: Error | DOMException): void;
2
2
  export declare function logInvariant(msg: string): void;
@@ -1,6 +1,23 @@
1
+ import { WebLogger } from '@peassoft/web-logger';
2
+ import { getLogsUrl, isLocal } from '../env/index.js';
3
+ let _logger = null;
1
4
  export function logError(err) {
2
- console.error(err);
5
+ if (!_logger) {
6
+ _logger = createLogger();
7
+ }
8
+ _logger.error(err);
9
+ if (isLocal()) console.error(err);
3
10
  }
4
11
  export function logInvariant(msg) {
5
- console.error(msg);
12
+ if (!_logger) {
13
+ _logger = createLogger();
14
+ }
15
+ _logger.info(msg);
16
+ if (isLocal()) console.log(msg);
17
+ }
18
+ function createLogger() {
19
+ return new WebLogger({
20
+ appName: 'mnr-web-topline',
21
+ url: getLogsUrl()
22
+ });
6
23
  }
@@ -1,4 +1,4 @@
1
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
2
2
  import normalizeRequestOpts from './normalize-request-opts.js';
3
3
  import { logError } from '../logger/index.js';
4
4
  import shouldRetry from './should-retry.js';
@@ -37,11 +37,11 @@ export default async function request(url, fetchApiOpts, requestOpts = {}) {
37
37
  } catch (e) {
38
38
  if (e instanceof Error) {
39
39
  if (e.name === 'AbortError') {
40
- logError(new WebError({
40
+ logError(new VError({
41
41
  name: 'RequestError'
42
42
  }, `request to ${url} was aborted due to timeout`));
43
43
  } else {
44
- logError(new WebError({
44
+ logError(new VError({
45
45
  name: 'RequestError',
46
46
  cause: e
47
47
  }, `request to ${url} failed`));
@@ -65,12 +65,12 @@ export default async function request(url, fetchApiOpts, requestOpts = {}) {
65
65
  });
66
66
  }
67
67
  if (response.status >= 500 && response.status < 600) {
68
- logError(new WebError({
68
+ logError(new VError({
69
69
  name: 'ServiceUnavailable'
70
70
  }, `request to ${url} failed with ${response.status} status code`));
71
71
  return null;
72
72
  }
73
- logError(new WebError({
73
+ logError(new VError({
74
74
  name: 'UnexpectedError'
75
75
  }, `request to ${url} returned unexpected status code ${response.status}`));
76
76
  return null;
@@ -1,4 +1,4 @@
1
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
2
2
  import { getGrantToken, getRefreshToken, setGrantToken, setRefreshToken } from '../tokens/index.js';
3
3
  import { deleteAllData as deleteAllDataFromDb, updateGrantToken as updateGrantTokenInDb, updateRefreshToken as updateRefreshTokenInDb, updateUser as updateUserInDb } from '../local-db/index.js';
4
4
  import { getApiBaseUrl } from '../env/index.js';
@@ -143,13 +143,13 @@ export class InnerService {
143
143
  }
144
144
  return grantToken;
145
145
  } else {
146
- this.#logError(new WebError({
146
+ this.#logError(new VError({
147
147
  name: 'UnexpectedError'
148
148
  }, `unexpected payload of response to request to ${url}`));
149
149
  return null;
150
150
  }
151
151
  } catch (err) {
152
- this.#logError(new WebError({
152
+ this.#logError(new VError({
153
153
  name: 'UnexpectedError',
154
154
  cause: err
155
155
  }, `parsing 200 response of request to ${url} failed`));
@@ -1,4 +1,4 @@
1
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
2
2
  import { getWebsocketUrl } from '../env/index.js';
3
3
  import { getGrantToken } from '../tokens/index.js';
4
4
  import { logError } from '../logger/index.js';
@@ -97,7 +97,7 @@ async function handleSocketClose(e) {
97
97
  break;
98
98
  default:
99
99
  // Unexpected closing code
100
- logError(new WebError({
100
+ logError(new VError({
101
101
  name: 'UnexpectedWsClosingCode'
102
102
  }, `websocket was closed with unexpected code ${e.code}`));
103
103
  reconnect();
@@ -1,23 +1,4 @@
1
- import stampit from 'stampit';
2
- import { closeWebsocket } from '../../../../modules/websocket/index.js';
3
- import { getGrantToken } from '../../../../modules/tokens/index.js';
4
- import { type InnerService } from '../../../../modules/topline-service/index.js';
5
- import { getApiBaseUrl } from '../../../../modules/env/index.js';
6
- import request from '../../../../modules/request/index.js';
7
- import makeSecondAttempt from './make-second-attempt.js';
8
- export type This = {
9
- closeWebsocket: typeof closeWebsocket;
10
- getGrantToken: typeof getGrantToken;
11
- innerToplineService: Pick<InnerService, 'processLogout' | 'upgradeGrantToken'>;
12
- getApiBaseUrl: typeof getApiBaseUrl;
13
- request: typeof request;
14
- makeSecondAttempt: typeof makeSecondAttempt;
15
- performLogout: typeof performLogout;
16
- };
17
- export declare const PerformLogout: stampit.Stamp<This>;
18
- declare const self: () => Promise<void>;
19
- export default self;
20
1
  /**
21
2
  * Make logout request to api
22
3
  */
23
- declare function performLogout(this: This): Promise<void>;
4
+ export default function performLogout(): Promise<void>;
@@ -1,33 +1,20 @@
1
- import stampit from 'stampit';
2
1
  import { closeWebsocket } from '../../../../modules/websocket/index.js';
3
2
  import { getGrantToken } from '../../../../modules/tokens/index.js';
4
3
  import { innerToplineService } from '../../../../modules/topline-service/index.js';
5
4
  import { getApiBaseUrl } from '../../../../modules/env/index.js';
6
5
  import request from '../../../../modules/request/index.js';
7
6
  import makeSecondAttempt from './make-second-attempt.js';
8
- export const PerformLogout = stampit().props({
9
- closeWebsocket,
10
- getGrantToken,
11
- innerToplineService,
12
- getApiBaseUrl,
13
- request,
14
- makeSecondAttempt
15
- }).methods({
16
- performLogout
17
- });
18
- const self = performLogout.bind(PerformLogout());
19
- export default self;
20
7
  /**
21
8
  * Make logout request to api
22
9
  */
23
- async function performLogout() {
24
- this.closeWebsocket();
25
- const grantToken = this.getGrantToken();
10
+ export default async function performLogout() {
11
+ closeWebsocket();
12
+ const grantToken = getGrantToken();
26
13
  if (!grantToken) {
27
- this.innerToplineService.processLogout();
14
+ innerToplineService.processLogout();
28
15
  return;
29
16
  }
30
- const url = `${this.getApiBaseUrl()}/private/log-out`;
17
+ const url = `${getApiBaseUrl()}/private/log-out`;
31
18
  const options = {
32
19
  method: 'POST',
33
20
  headers: {
@@ -36,19 +23,19 @@ async function performLogout() {
36
23
  cache: 'no-store',
37
24
  redirect: 'error'
38
25
  };
39
- const response = await this.request(url, options, {
26
+ const response = await request(url, options, {
40
27
  maxRetries: 1
41
28
  });
42
29
  if (!response || response.status !== 401) {
43
- this.innerToplineService.processLogout();
30
+ innerToplineService.processLogout();
44
31
  return;
45
32
  }
46
33
  if (response.status === 401) {
47
34
  // innerToplineService.upgradeGrantToken() makes all necessary clean-up in case
48
35
  // the upgrade fails
49
- const grantToken = await this.innerToplineService.upgradeGrantToken();
36
+ const grantToken = await innerToplineService.upgradeGrantToken();
50
37
  if (grantToken) {
51
- await this.makeSecondAttempt(self);
38
+ await makeSecondAttempt(performLogout);
52
39
  }
53
40
  }
54
41
  }
@@ -1,6 +1,3 @@
1
- import stampit from 'stampit';
2
- import { getApiBaseUrl, getRequestCredentialsMode } from '../../../../modules/env/index.js';
3
- import request from '../../../../modules/request/index.js';
4
1
  export type ReturnValue = {
5
2
  error: 'network' | null;
6
3
  };
@@ -10,13 +7,4 @@ export type ReturnValue = {
10
7
  * Returns `null` if claim creation succeeds; otherwise:
11
8
  * - on network error or if backend responds with 5xx - "network".
12
9
  */
13
- declare function createClaim(this: This, email: string): Promise<ReturnValue>;
14
- export type This = {
15
- getApiBaseUrl: typeof getApiBaseUrl;
16
- getRequestCredentialsMode: typeof getRequestCredentialsMode;
17
- request: typeof request;
18
- createClaim: typeof createClaim;
19
- };
20
- export declare const CreateClaim: stampit.Stamp<This>;
21
- declare const _default: (email: string) => Promise<ReturnValue>;
22
- export default _default;
10
+ export default function createClaim(email: string): Promise<ReturnValue>;
@@ -1,4 +1,3 @@
1
- import stampit from 'stampit';
2
1
  import { getApiBaseUrl, getRequestCredentialsMode } from '../../../../modules/env/index.js';
3
2
  import request from '../../../../modules/request/index.js';
4
3
  /**
@@ -7,8 +6,8 @@ import request from '../../../../modules/request/index.js';
7
6
  * Returns `null` if claim creation succeeds; otherwise:
8
7
  * - on network error or if backend responds with 5xx - "network".
9
8
  */
10
- async function createClaim(email) {
11
- const url = `${this.getApiBaseUrl()}/public/create-password-recovery-claim`;
9
+ export default async function createClaim(email) {
10
+ const url = `${getApiBaseUrl()}/public/create-password-recovery-claim`;
12
11
  const options = {
13
12
  method: 'POST',
14
13
  headers: {
@@ -17,11 +16,11 @@ async function createClaim(email) {
17
16
  body: JSON.stringify({
18
17
  email
19
18
  }),
20
- credentials: this.getRequestCredentialsMode(),
19
+ credentials: getRequestCredentialsMode(),
21
20
  cache: 'no-store',
22
21
  redirect: 'error'
23
22
  };
24
- const response = await this.request(url, options);
23
+ const response = await request(url, options);
25
24
  if (!response) {
26
25
  return {
27
26
  error: 'network'
@@ -30,12 +29,4 @@ async function createClaim(email) {
30
29
  return {
31
30
  error: null
32
31
  };
33
- }
34
- export const CreateClaim = stampit().props({
35
- getApiBaseUrl,
36
- getRequestCredentialsMode,
37
- request
38
- }).methods({
39
- createClaim
40
- });
41
- export default createClaim.bind(CreateClaim());
32
+ }
@@ -1,4 +1,4 @@
1
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
2
2
  import { getGrantToken, setGrantToken, setRefreshToken } from '../../../../modules/tokens/index.js';
3
3
  import { logInvariant, logError } from '../../../../modules/logger/index.js';
4
4
  import { getApiBaseUrl, getRequestCredentialsMode } from '../../../../modules/env/index.js';
@@ -65,7 +65,7 @@ export default async function changePassword(beacon, oldPassword, newPassword) {
65
65
  try {
66
66
  payload = await response.json();
67
67
  } catch (e) {
68
- logError(new WebError({
68
+ logError(new VError({
69
69
  name: 'UnexpectedError',
70
70
  ...(e instanceof Error ? {
71
71
  cause: e
@@ -1,28 +1,7 @@
1
- import stampit from 'stampit';
2
- import { getGrantToken } from '../../../../modules/tokens/index.js';
3
- import { type InnerService } from '../../../../modules/topline-service/index.js';
4
- import { getApiBaseUrl } from '../../../../modules/env/index.js';
5
- import request from '../../../../modules/request/index.js';
6
- import hasUserBeenChanged from './has-user-been-changed.js';
7
- import { logError } from '../../../../modules/logger/index.js';
8
- import makeSecondAttempt from './make-second-attempt.js';
9
1
  import { type ToplineUser } from '../../../../types/data.js';
10
- export type This = {
11
- getGrantToken: typeof getGrantToken;
12
- innerToplineService: Pick<InnerService, 'processLogout' | 'updateUser' | 'upgradeGrantToken'>;
13
- getApiBaseUrl: typeof getApiBaseUrl;
14
- request: typeof request;
15
- hasUserBeenChanged: typeof hasUserBeenChanged;
16
- logError: typeof logError;
17
- makeSecondAttempt: typeof makeSecondAttempt;
18
- refreshUser: typeof refreshUser;
19
- };
20
- export declare const RefreshUser: stampit.Stamp<This>;
21
- declare const self: (currUser: ToplineUser) => Promise<void>;
22
- export default self;
23
2
  /**
24
3
  * Try to refresh user data after initialization
25
4
  *
26
5
  * @param currUser User data fetched from local DB on initialization
27
6
  */
28
- declare function refreshUser(this: This, currUser: ToplineUser): Promise<void>;
7
+ export default function refreshUser(currUser: ToplineUser): Promise<void>;
@@ -1,5 +1,4 @@
1
- import stampit from 'stampit';
2
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
3
2
  import { getGrantToken } from '../../../../modules/tokens/index.js';
4
3
  import { innerToplineService } from '../../../../modules/topline-service/index.js';
5
4
  import { getApiBaseUrl } from '../../../../modules/env/index.js';
@@ -9,28 +8,15 @@ import { logError } from '../../../../modules/logger/index.js';
9
8
  import makeSecondAttempt from './make-second-attempt.js';
10
9
  import { isToplineUser } from '../../../../types/data.js';
11
10
  import { isObject } from '../../../../types/helpers.js';
12
- export const RefreshUser = stampit().props({
13
- getGrantToken,
14
- innerToplineService,
15
- getApiBaseUrl,
16
- request,
17
- hasUserBeenChanged,
18
- logError,
19
- makeSecondAttempt
20
- }).methods({
21
- refreshUser
22
- });
23
- const self = refreshUser.bind(RefreshUser());
24
- export default self;
25
11
  /**
26
12
  * Try to refresh user data after initialization
27
13
  *
28
14
  * @param currUser User data fetched from local DB on initialization
29
15
  */
30
- async function refreshUser(currUser) {
31
- const grantToken = this.getGrantToken();
16
+ export default async function refreshUser(currUser) {
17
+ const grantToken = getGrantToken();
32
18
  if (!grantToken) return;
33
- const url = `${this.getApiBaseUrl()}/private/user`;
19
+ const url = `${getApiBaseUrl()}/private/user`;
34
20
  const options = {
35
21
  method: 'GET',
36
22
  headers: {
@@ -39,7 +25,7 @@ async function refreshUser(currUser) {
39
25
  cache: 'no-store',
40
26
  redirect: 'error'
41
27
  };
42
- const response = await this.request(url, options, {
28
+ const response = await request(url, options, {
43
29
  maxRetries: 1
44
30
  });
45
31
  if (!response) return;
@@ -50,12 +36,12 @@ async function refreshUser(currUser) {
50
36
  if (isObject(responsePayload) && 'user' in responsePayload && (isToplineUser(responsePayload.user) || responsePayload.user === null)) {
51
37
  newUser = responsePayload.user;
52
38
  } else {
53
- this.logError(new WebError({
39
+ logError(new VError({
54
40
  name: 'UnexpectedPayload'
55
- }, `unexpected payload of request to ${url}`));
41
+ }, `unexpected payload of response to request to ${url}`));
56
42
  }
57
43
  } catch (err) {
58
- this.logError(new WebError({
44
+ logError(new VError({
59
45
  name: 'UnexpectedError',
60
46
  ...(err instanceof Error ? {
61
47
  cause: err
@@ -64,16 +50,16 @@ async function refreshUser(currUser) {
64
50
  return;
65
51
  }
66
52
  if (!newUser) {
67
- this.innerToplineService.processLogout();
53
+ innerToplineService.processLogout();
68
54
  return;
69
55
  }
70
- if (this.hasUserBeenChanged(currUser, newUser)) {
71
- await this.innerToplineService.updateUser(newUser);
56
+ if (hasUserBeenChanged(currUser, newUser)) {
57
+ await innerToplineService.updateUser(newUser);
72
58
  }
73
59
  return;
74
60
  }
75
61
  if (response.status === 401) {
76
- await this.innerToplineService.upgradeGrantToken();
77
- await this.makeSecondAttempt(currUser, self);
62
+ await innerToplineService.upgradeGrantToken();
63
+ await makeSecondAttempt(currUser, refreshUser);
78
64
  }
79
65
  }
@@ -1,4 +1,4 @@
1
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
2
2
  import { updateUser, updateGrantToken, updateRefreshToken, updateIsUserKnown } from '../../modules/local-db/index.js';
3
3
  import { setGrantToken, setRefreshToken } from '../../modules/tokens/index.js';
4
4
  import { openWebsocket } from '../../modules/websocket/index.js';
@@ -28,7 +28,7 @@ export default async function processResponse(response, url, eventName) {
28
28
  error: null
29
29
  };
30
30
  }
31
- logError(new WebError({
31
+ logError(new VError({
32
32
  name: 'UnexpectedError'
33
33
  }, `unexpected payload of response to request to ${url}`));
34
34
  return {
@@ -36,7 +36,7 @@ export default async function processResponse(response, url, eventName) {
36
36
  error: 'network'
37
37
  };
38
38
  } catch (e) {
39
- logError(new WebError({
39
+ logError(new VError({
40
40
  name: 'UnexpectedError',
41
41
  ...(e instanceof Error ? {
42
42
  cause: e
@@ -1,7 +1,15 @@
1
+ /**
2
+ * Detect whether the app is running in local environment
3
+ */
4
+ export declare function isLocal(): boolean;
1
5
  /**
2
6
  * Return base URL for requests to backend API
3
7
  */
4
8
  export declare function getApiBaseUrl(): string;
9
+ /**
10
+ * Return URL for logs backend
11
+ */
12
+ export declare function getLogsUrl(): string;
5
13
  /**
6
14
  * Return URL for websocket connection
7
15
  */
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Detect whether the app is running in local environment
3
+ */
4
+ export function isLocal() {
5
+ return window.location.hostname === 'localhost';
6
+ }
1
7
  /**
2
8
  * Return base URL for requests to backend API
3
9
  */
@@ -7,6 +13,15 @@ export function getApiBaseUrl() {
7
13
  }
8
14
  return '/api';
9
15
  }
16
+ /**
17
+ * Return URL for logs backend
18
+ */
19
+ export function getLogsUrl() {
20
+ if (window.location.hostname === 'localhost') {
21
+ return 'http://localhost:5005/logs';
22
+ }
23
+ return '/front-logs/logs';
24
+ }
10
25
  /**
11
26
  * Return URL for websocket connection
12
27
  */
@@ -1,5 +1,5 @@
1
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
2
2
  /**
3
3
  * Create error for failed request to local database
4
4
  */
5
- export default function createRequestError(reason: string): WebError;
5
+ export default function createRequestError(reason: string): VError;
@@ -1,9 +1,9 @@
1
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
2
2
  /**
3
3
  * Create error for failed request to local database
4
4
  */
5
5
  export default function createRequestError(reason) {
6
- return new WebError({
6
+ return new VError({
7
7
  name: 'LocalDbRequestError'
8
8
  }, `request to local database failed (${reason})`);
9
9
  }
@@ -1,4 +1,4 @@
1
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
2
2
  const DB_NAME = 'mnr_topline';
3
3
  const DB_VERSION = 2;
4
4
  // Main store data is deleted when a user logs out.
@@ -27,7 +27,7 @@ export function initDb() {
27
27
  return new Promise((resolve, reject) => {
28
28
  const openDbReq = window.indexedDB.open(DB_NAME, DB_VERSION);
29
29
  openDbReq.onerror = function (_e) {
30
- reject(new WebError({
30
+ reject(new VError({
31
31
  name: 'LocalDbInitializationError'
32
32
  }, 'opening local database failed'));
33
33
  };
@@ -53,7 +53,7 @@ export function initDb() {
53
53
  }
54
54
  };
55
55
  openDbReq.onblocked = function () {
56
- reject(new WebError({
56
+ reject(new VError({
57
57
  name: 'LocalDbInitializationError'
58
58
  }, 'failed upgrading local database due to it is blocked'));
59
59
  };
@@ -1,2 +1,2 @@
1
- export declare function logError(err: Error): void;
1
+ export declare function logError(err: Error | DOMException): void;
2
2
  export declare function logInvariant(msg: string): void;
@@ -1,6 +1,23 @@
1
+ import { WebLogger } from '@peassoft/web-logger';
2
+ import { getLogsUrl, isLocal } from '../env/index.js';
3
+ let _logger = null;
1
4
  export function logError(err) {
2
- console.error(err);
5
+ if (!_logger) {
6
+ _logger = createLogger();
7
+ }
8
+ _logger.error(err);
9
+ if (isLocal()) console.error(err);
3
10
  }
4
11
  export function logInvariant(msg) {
5
- console.error(msg);
12
+ if (!_logger) {
13
+ _logger = createLogger();
14
+ }
15
+ _logger.info(msg);
16
+ if (isLocal()) console.log(msg);
17
+ }
18
+ function createLogger() {
19
+ return new WebLogger({
20
+ appName: 'mnr-web-topline',
21
+ url: getLogsUrl()
22
+ });
6
23
  }
@@ -1,4 +1,4 @@
1
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
2
2
  import normalizeRequestOpts from './normalize-request-opts.js';
3
3
  import { logError } from '../logger/index.js';
4
4
  import shouldRetry from './should-retry.js';
@@ -37,11 +37,11 @@ export default async function request(url, fetchApiOpts, requestOpts = {}) {
37
37
  } catch (e) {
38
38
  if (e instanceof Error) {
39
39
  if (e.name === 'AbortError') {
40
- logError(new WebError({
40
+ logError(new VError({
41
41
  name: 'RequestError'
42
42
  }, `request to ${url} was aborted due to timeout`));
43
43
  } else {
44
- logError(new WebError({
44
+ logError(new VError({
45
45
  name: 'RequestError',
46
46
  cause: e
47
47
  }, `request to ${url} failed`));
@@ -65,12 +65,12 @@ export default async function request(url, fetchApiOpts, requestOpts = {}) {
65
65
  });
66
66
  }
67
67
  if (response.status >= 500 && response.status < 600) {
68
- logError(new WebError({
68
+ logError(new VError({
69
69
  name: 'ServiceUnavailable'
70
70
  }, `request to ${url} failed with ${response.status} status code`));
71
71
  return null;
72
72
  }
73
- logError(new WebError({
73
+ logError(new VError({
74
74
  name: 'UnexpectedError'
75
75
  }, `request to ${url} returned unexpected status code ${response.status}`));
76
76
  return null;
@@ -1,4 +1,4 @@
1
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
2
2
  import { getGrantToken, getRefreshToken, setGrantToken, setRefreshToken } from '../tokens/index.js';
3
3
  import { deleteAllData as deleteAllDataFromDb, updateGrantToken as updateGrantTokenInDb, updateRefreshToken as updateRefreshTokenInDb, updateUser as updateUserInDb } from '../local-db/index.js';
4
4
  import { getApiBaseUrl } from '../env/index.js';
@@ -143,13 +143,13 @@ export class InnerService {
143
143
  }
144
144
  return grantToken;
145
145
  } else {
146
- this.#logError(new WebError({
146
+ this.#logError(new VError({
147
147
  name: 'UnexpectedError'
148
148
  }, `unexpected payload of response to request to ${url}`));
149
149
  return null;
150
150
  }
151
151
  } catch (err) {
152
- this.#logError(new WebError({
152
+ this.#logError(new VError({
153
153
  name: 'UnexpectedError',
154
154
  cause: err
155
155
  }, `parsing 200 response of request to ${url} failed`));
@@ -1,4 +1,4 @@
1
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
2
2
  import { getWebsocketUrl } from '../env/index.js';
3
3
  import { getGrantToken } from '../tokens/index.js';
4
4
  import { logError } from '../logger/index.js';
@@ -97,7 +97,7 @@ async function handleSocketClose(e) {
97
97
  break;
98
98
  default:
99
99
  // Unexpected closing code
100
- logError(new WebError({
100
+ logError(new VError({
101
101
  name: 'UnexpectedWsClosingCode'
102
102
  }, `websocket was closed with unexpected code ${e.code}`));
103
103
  reconnect();
@@ -1,23 +1,4 @@
1
- import stampit from 'stampit';
2
- import { closeWebsocket } from '../../../../modules/websocket/index.js';
3
- import { getGrantToken } from '../../../../modules/tokens/index.js';
4
- import { type InnerService } from '../../../../modules/topline-service/index.js';
5
- import { getApiBaseUrl } from '../../../../modules/env/index.js';
6
- import request from '../../../../modules/request/index.js';
7
- import makeSecondAttempt from './make-second-attempt.js';
8
- export type This = {
9
- closeWebsocket: typeof closeWebsocket;
10
- getGrantToken: typeof getGrantToken;
11
- innerToplineService: Pick<InnerService, 'processLogout' | 'upgradeGrantToken'>;
12
- getApiBaseUrl: typeof getApiBaseUrl;
13
- request: typeof request;
14
- makeSecondAttempt: typeof makeSecondAttempt;
15
- performLogout: typeof performLogout;
16
- };
17
- export declare const PerformLogout: stampit.Stamp<This>;
18
- declare const self: () => Promise<void>;
19
- export default self;
20
1
  /**
21
2
  * Make logout request to api
22
3
  */
23
- declare function performLogout(this: This): Promise<void>;
4
+ export default function performLogout(): Promise<void>;
@@ -1,33 +1,20 @@
1
- import stampit from 'stampit';
2
1
  import { closeWebsocket } from '../../../../modules/websocket/index.js';
3
2
  import { getGrantToken } from '../../../../modules/tokens/index.js';
4
3
  import { innerToplineService } from '../../../../modules/topline-service/index.js';
5
4
  import { getApiBaseUrl } from '../../../../modules/env/index.js';
6
5
  import request from '../../../../modules/request/index.js';
7
6
  import makeSecondAttempt from './make-second-attempt.js';
8
- export const PerformLogout = stampit().props({
9
- closeWebsocket,
10
- getGrantToken,
11
- innerToplineService,
12
- getApiBaseUrl,
13
- request,
14
- makeSecondAttempt
15
- }).methods({
16
- performLogout
17
- });
18
- const self = performLogout.bind(PerformLogout());
19
- export default self;
20
7
  /**
21
8
  * Make logout request to api
22
9
  */
23
- async function performLogout() {
24
- this.closeWebsocket();
25
- const grantToken = this.getGrantToken();
10
+ export default async function performLogout() {
11
+ closeWebsocket();
12
+ const grantToken = getGrantToken();
26
13
  if (!grantToken) {
27
- this.innerToplineService.processLogout();
14
+ innerToplineService.processLogout();
28
15
  return;
29
16
  }
30
- const url = `${this.getApiBaseUrl()}/private/log-out`;
17
+ const url = `${getApiBaseUrl()}/private/log-out`;
31
18
  const options = {
32
19
  method: 'POST',
33
20
  headers: {
@@ -36,19 +23,19 @@ async function performLogout() {
36
23
  cache: 'no-store',
37
24
  redirect: 'error'
38
25
  };
39
- const response = await this.request(url, options, {
26
+ const response = await request(url, options, {
40
27
  maxRetries: 1
41
28
  });
42
29
  if (!response || response.status !== 401) {
43
- this.innerToplineService.processLogout();
30
+ innerToplineService.processLogout();
44
31
  return;
45
32
  }
46
33
  if (response.status === 401) {
47
34
  // innerToplineService.upgradeGrantToken() makes all necessary clean-up in case
48
35
  // the upgrade fails
49
- const grantToken = await this.innerToplineService.upgradeGrantToken();
36
+ const grantToken = await innerToplineService.upgradeGrantToken();
50
37
  if (grantToken) {
51
- await this.makeSecondAttempt(self);
38
+ await makeSecondAttempt(performLogout);
52
39
  }
53
40
  }
54
41
  }
@@ -1,6 +1,3 @@
1
- import stampit from 'stampit';
2
- import { getApiBaseUrl, getRequestCredentialsMode } from '../../../../modules/env/index.js';
3
- import request from '../../../../modules/request/index.js';
4
1
  export type ReturnValue = {
5
2
  error: 'network' | null;
6
3
  };
@@ -10,13 +7,4 @@ export type ReturnValue = {
10
7
  * Returns `null` if claim creation succeeds; otherwise:
11
8
  * - on network error or if backend responds with 5xx - "network".
12
9
  */
13
- declare function createClaim(this: This, email: string): Promise<ReturnValue>;
14
- export type This = {
15
- getApiBaseUrl: typeof getApiBaseUrl;
16
- getRequestCredentialsMode: typeof getRequestCredentialsMode;
17
- request: typeof request;
18
- createClaim: typeof createClaim;
19
- };
20
- export declare const CreateClaim: stampit.Stamp<This>;
21
- declare const _default: (email: string) => Promise<ReturnValue>;
22
- export default _default;
10
+ export default function createClaim(email: string): Promise<ReturnValue>;
@@ -1,4 +1,3 @@
1
- import stampit from 'stampit';
2
1
  import { getApiBaseUrl, getRequestCredentialsMode } from '../../../../modules/env/index.js';
3
2
  import request from '../../../../modules/request/index.js';
4
3
  /**
@@ -7,8 +6,8 @@ import request from '../../../../modules/request/index.js';
7
6
  * Returns `null` if claim creation succeeds; otherwise:
8
7
  * - on network error or if backend responds with 5xx - "network".
9
8
  */
10
- async function createClaim(email) {
11
- const url = `${this.getApiBaseUrl()}/public/create-password-recovery-claim`;
9
+ export default async function createClaim(email) {
10
+ const url = `${getApiBaseUrl()}/public/create-password-recovery-claim`;
12
11
  const options = {
13
12
  method: 'POST',
14
13
  headers: {
@@ -17,11 +16,11 @@ async function createClaim(email) {
17
16
  body: JSON.stringify({
18
17
  email
19
18
  }),
20
- credentials: this.getRequestCredentialsMode(),
19
+ credentials: getRequestCredentialsMode(),
21
20
  cache: 'no-store',
22
21
  redirect: 'error'
23
22
  };
24
- const response = await this.request(url, options);
23
+ const response = await request(url, options);
25
24
  if (!response) {
26
25
  return {
27
26
  error: 'network'
@@ -30,12 +29,4 @@ async function createClaim(email) {
30
29
  return {
31
30
  error: null
32
31
  };
33
- }
34
- export const CreateClaim = stampit().props({
35
- getApiBaseUrl,
36
- getRequestCredentialsMode,
37
- request
38
- }).methods({
39
- createClaim
40
- });
41
- export default createClaim.bind(CreateClaim());
32
+ }
@@ -1,4 +1,4 @@
1
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
2
2
  import { getGrantToken, setGrantToken, setRefreshToken } from '../../../../modules/tokens/index.js';
3
3
  import { logInvariant, logError } from '../../../../modules/logger/index.js';
4
4
  import { getApiBaseUrl, getRequestCredentialsMode } from '../../../../modules/env/index.js';
@@ -65,7 +65,7 @@ export default async function changePassword(beacon, oldPassword, newPassword) {
65
65
  try {
66
66
  payload = await response.json();
67
67
  } catch (e) {
68
- logError(new WebError({
68
+ logError(new VError({
69
69
  name: 'UnexpectedError',
70
70
  ...(e instanceof Error ? {
71
71
  cause: e
@@ -1,28 +1,7 @@
1
- import stampit from 'stampit';
2
- import { getGrantToken } from '../../../../modules/tokens/index.js';
3
- import { type InnerService } from '../../../../modules/topline-service/index.js';
4
- import { getApiBaseUrl } from '../../../../modules/env/index.js';
5
- import request from '../../../../modules/request/index.js';
6
- import hasUserBeenChanged from './has-user-been-changed.js';
7
- import { logError } from '../../../../modules/logger/index.js';
8
- import makeSecondAttempt from './make-second-attempt.js';
9
1
  import { type ToplineUser } from '../../../../types/data.js';
10
- export type This = {
11
- getGrantToken: typeof getGrantToken;
12
- innerToplineService: Pick<InnerService, 'processLogout' | 'updateUser' | 'upgradeGrantToken'>;
13
- getApiBaseUrl: typeof getApiBaseUrl;
14
- request: typeof request;
15
- hasUserBeenChanged: typeof hasUserBeenChanged;
16
- logError: typeof logError;
17
- makeSecondAttempt: typeof makeSecondAttempt;
18
- refreshUser: typeof refreshUser;
19
- };
20
- export declare const RefreshUser: stampit.Stamp<This>;
21
- declare const self: (currUser: ToplineUser) => Promise<void>;
22
- export default self;
23
2
  /**
24
3
  * Try to refresh user data after initialization
25
4
  *
26
5
  * @param currUser User data fetched from local DB on initialization
27
6
  */
28
- declare function refreshUser(this: This, currUser: ToplineUser): Promise<void>;
7
+ export default function refreshUser(currUser: ToplineUser): Promise<void>;
@@ -1,5 +1,4 @@
1
- import stampit from 'stampit';
2
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
3
2
  import { getGrantToken } from '../../../../modules/tokens/index.js';
4
3
  import { innerToplineService } from '../../../../modules/topline-service/index.js';
5
4
  import { getApiBaseUrl } from '../../../../modules/env/index.js';
@@ -9,28 +8,15 @@ import { logError } from '../../../../modules/logger/index.js';
9
8
  import makeSecondAttempt from './make-second-attempt.js';
10
9
  import { isToplineUser } from '../../../../types/data.js';
11
10
  import { isObject } from '../../../../types/helpers.js';
12
- export const RefreshUser = stampit().props({
13
- getGrantToken,
14
- innerToplineService,
15
- getApiBaseUrl,
16
- request,
17
- hasUserBeenChanged,
18
- logError,
19
- makeSecondAttempt
20
- }).methods({
21
- refreshUser
22
- });
23
- const self = refreshUser.bind(RefreshUser());
24
- export default self;
25
11
  /**
26
12
  * Try to refresh user data after initialization
27
13
  *
28
14
  * @param currUser User data fetched from local DB on initialization
29
15
  */
30
- async function refreshUser(currUser) {
31
- const grantToken = this.getGrantToken();
16
+ export default async function refreshUser(currUser) {
17
+ const grantToken = getGrantToken();
32
18
  if (!grantToken) return;
33
- const url = `${this.getApiBaseUrl()}/private/user`;
19
+ const url = `${getApiBaseUrl()}/private/user`;
34
20
  const options = {
35
21
  method: 'GET',
36
22
  headers: {
@@ -39,7 +25,7 @@ async function refreshUser(currUser) {
39
25
  cache: 'no-store',
40
26
  redirect: 'error'
41
27
  };
42
- const response = await this.request(url, options, {
28
+ const response = await request(url, options, {
43
29
  maxRetries: 1
44
30
  });
45
31
  if (!response) return;
@@ -50,12 +36,12 @@ async function refreshUser(currUser) {
50
36
  if (isObject(responsePayload) && 'user' in responsePayload && (isToplineUser(responsePayload.user) || responsePayload.user === null)) {
51
37
  newUser = responsePayload.user;
52
38
  } else {
53
- this.logError(new WebError({
39
+ logError(new VError({
54
40
  name: 'UnexpectedPayload'
55
- }, `unexpected payload of request to ${url}`));
41
+ }, `unexpected payload of response to request to ${url}`));
56
42
  }
57
43
  } catch (err) {
58
- this.logError(new WebError({
44
+ logError(new VError({
59
45
  name: 'UnexpectedError',
60
46
  ...(err instanceof Error ? {
61
47
  cause: err
@@ -64,16 +50,16 @@ async function refreshUser(currUser) {
64
50
  return;
65
51
  }
66
52
  if (!newUser) {
67
- this.innerToplineService.processLogout();
53
+ innerToplineService.processLogout();
68
54
  return;
69
55
  }
70
- if (this.hasUserBeenChanged(currUser, newUser)) {
71
- await this.innerToplineService.updateUser(newUser);
56
+ if (hasUserBeenChanged(currUser, newUser)) {
57
+ await innerToplineService.updateUser(newUser);
72
58
  }
73
59
  return;
74
60
  }
75
61
  if (response.status === 401) {
76
- await this.innerToplineService.upgradeGrantToken();
77
- await this.makeSecondAttempt(currUser, self);
62
+ await innerToplineService.upgradeGrantToken();
63
+ await makeSecondAttempt(currUser, refreshUser);
78
64
  }
79
65
  }
@@ -1,4 +1,4 @@
1
- import WebError from '@memnrev/web-error';
1
+ import VError from '@peassoft/verror';
2
2
  import { updateUser, updateGrantToken, updateRefreshToken, updateIsUserKnown } from '../../modules/local-db/index.js';
3
3
  import { setGrantToken, setRefreshToken } from '../../modules/tokens/index.js';
4
4
  import { openWebsocket } from '../../modules/websocket/index.js';
@@ -28,7 +28,7 @@ export default async function processResponse(response, url, eventName) {
28
28
  error: null
29
29
  };
30
30
  }
31
- logError(new WebError({
31
+ logError(new VError({
32
32
  name: 'UnexpectedError'
33
33
  }, `unexpected payload of response to request to ${url}`));
34
34
  return {
@@ -36,7 +36,7 @@ export default async function processResponse(response, url, eventName) {
36
36
  error: 'network'
37
37
  };
38
38
  } catch (e) {
39
- logError(new WebError({
39
+ logError(new VError({
40
40
  name: 'UnexpectedError',
41
41
  ...(e instanceof Error ? {
42
42
  cause: e
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peassoft/mnr-web-topline",
3
- "version": "4.1.0",
3
+ "version": "4.2.0",
4
4
  "description": "Peassoft Topline widget for mem'n'rev web applications",
5
5
  "type": "module",
6
6
  "exports": {
@@ -38,8 +38,7 @@
38
38
  "@types/md5": "^2.3.3",
39
39
  "@types/react": "^19.0.2",
40
40
  "@types/react-dom": "^19.0.2",
41
- "@types/stampit": "^4.3.4",
42
- "@vitejs/plugin-react": "^5.1.4",
41
+ "@vitejs/plugin-react": "^6.0.1",
43
42
  "autoprefixer": "^10.3.4",
44
43
  "clean-webpack-plugin": "^4.0.0",
45
44
  "copy-webpack-plugin": "^14.0.0",
@@ -47,7 +46,7 @@
47
46
  "css-loader": "^7.1.2",
48
47
  "eslint": "^9.8.0",
49
48
  "html-webpack-plugin": "^5.3.2",
50
- "jsdom": "^28.0.0",
49
+ "jsdom": "^29.0.1",
51
50
  "postcss": "^8.3.6",
52
51
  "postcss-loader": "^8.0.0",
53
52
  "postcss-normalize": "^13.0.1",
@@ -59,19 +58,19 @@
59
58
  "stylelint": "^17.0.0",
60
59
  "stylelint-config-sass-guidelines": "^13.0.0",
61
60
  "ts-loader": "^9.4.4",
62
- "typescript": "^5.2.2",
61
+ "typescript": "^6.0.2",
63
62
  "vitest": "^4.0.7",
64
63
  "webpack": "^5.52.0",
65
- "webpack-cli": "^6.0.1",
64
+ "webpack-cli": "^7.0.2",
66
65
  "webpack-dev-server": "^5.2.0"
67
66
  },
68
67
  "dependencies": {
69
- "@memnrev/web-error": "^0.4.1",
70
68
  "@peassoft/mnr-langs": "^1.2.0",
69
+ "@peassoft/verror": "^1.0.0",
70
+ "@peassoft/web-logger": "^1.0.0",
71
71
  "email-validator": "^2.0.4",
72
72
  "md5": "^2.3.0",
73
73
  "react-error-boundary": "^6.0.0",
74
- "stampit": "^4.3.2",
75
74
  "uuid": "^13.0.0"
76
75
  },
77
76
  "peerDependencies": {