@peassoft/mnr-web-topline 3.0.0 → 4.0.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 (93) hide show
  1. package/README.md +105 -18
  2. package/dist/css/index.css +2 -2
  3. package/dist/en/modules/keyboard-navigation/key-codes.d.ts +16 -18
  4. package/dist/en/modules/keyboard-navigation/key-codes.js +17 -18
  5. package/dist/en/modules/keyboard-navigation/vertical-menu.d.ts +1 -1
  6. package/dist/en/modules/keyboard-navigation/vertical-menu.js +8 -8
  7. package/dist/en/modules/local-db/actions/delete-all-data/index.js +2 -2
  8. package/dist/en/modules/local-db/actions/get-all-data/index.js +2 -2
  9. package/dist/en/modules/local-db/actions/get-is-user-known/index.d.ts +5 -0
  10. package/dist/en/modules/local-db/actions/get-is-user-known/index.js +33 -0
  11. package/dist/en/modules/local-db/actions/update-grant-token/index.js +3 -3
  12. package/dist/en/modules/local-db/actions/update-is-user-known/index.d.ts +5 -0
  13. package/dist/en/modules/local-db/actions/update-is-user-known/index.js +27 -0
  14. package/dist/en/modules/local-db/actions/update-refresh-token/index.js +3 -3
  15. package/dist/en/modules/local-db/actions/update-user/index.js +3 -3
  16. package/dist/en/modules/local-db/index.d.ts +2 -0
  17. package/dist/en/modules/local-db/index.js +3 -1
  18. package/dist/en/modules/local-db/init-db.d.ts +2 -1
  19. package/dist/en/modules/local-db/init-db.js +19 -10
  20. package/dist/en/modules/topline-service/index.d.ts +3 -1
  21. package/dist/en/modules/topline-service/index.js +6 -0
  22. package/dist/en/modules/topline-service/inner-service.d.ts +6 -1
  23. package/dist/en/modules/topline-service/inner-service.js +18 -0
  24. package/dist/en/modules/topline-service/types.d.ts +12 -0
  25. package/dist/en/modules/topline-service/types.js +4 -0
  26. package/dist/en/parts/login/actions/perform-login/index.d.ts +1 -15
  27. package/dist/en/parts/login/actions/perform-login/index.js +8 -17
  28. package/dist/en/parts/password-recovery/actions/save-password/index.d.ts +1 -15
  29. package/dist/en/parts/password-recovery/actions/save-password/index.js +8 -17
  30. package/dist/en/parts/profile/ui/password-change-confirmation/index.js +1 -0
  31. package/dist/en/parts/profile/ui/password-change-form/use-state-with-validation-reset.js +1 -1
  32. package/dist/en/parts/shell/context.d.ts +1 -1
  33. package/dist/en/parts/shell/ui/logged-out-user-menu/index.js +2 -0
  34. package/dist/en/parts/shell/ui/loggeg-in-user-menu/index.js +2 -0
  35. package/dist/en/parts/shell/ui/shell/index.js +11 -5
  36. package/dist/en/parts/shell/ui/user-menu-item/index.d.ts +1 -0
  37. package/dist/en/parts/shell/ui/user-menu-item/index.js +2 -0
  38. package/dist/en/parts/signup/actions/perform-signup/index.d.ts +1 -15
  39. package/dist/en/parts/signup/actions/perform-signup/index.js +8 -17
  40. package/dist/en/shared/components/modal/index.js +2 -2
  41. package/dist/en/shared/procedures/process-response.d.ts +13 -0
  42. package/dist/en/shared/procedures/process-response.js +53 -0
  43. package/dist/en/topline.d.ts +1 -1
  44. package/dist/en/types/result.d.ts +9 -0
  45. package/dist/en/types/result.js +12 -0
  46. package/dist/ru/modules/keyboard-navigation/key-codes.d.ts +16 -18
  47. package/dist/ru/modules/keyboard-navigation/key-codes.js +17 -18
  48. package/dist/ru/modules/keyboard-navigation/vertical-menu.d.ts +1 -1
  49. package/dist/ru/modules/keyboard-navigation/vertical-menu.js +8 -8
  50. package/dist/ru/modules/local-db/actions/delete-all-data/index.js +2 -2
  51. package/dist/ru/modules/local-db/actions/get-all-data/index.js +2 -2
  52. package/dist/ru/modules/local-db/actions/get-is-user-known/index.d.ts +5 -0
  53. package/dist/ru/modules/local-db/actions/get-is-user-known/index.js +33 -0
  54. package/dist/ru/modules/local-db/actions/update-grant-token/index.js +3 -3
  55. package/dist/ru/modules/local-db/actions/update-is-user-known/index.d.ts +5 -0
  56. package/dist/ru/modules/local-db/actions/update-is-user-known/index.js +27 -0
  57. package/dist/ru/modules/local-db/actions/update-refresh-token/index.js +3 -3
  58. package/dist/ru/modules/local-db/actions/update-user/index.js +3 -3
  59. package/dist/ru/modules/local-db/index.d.ts +2 -0
  60. package/dist/ru/modules/local-db/index.js +3 -1
  61. package/dist/ru/modules/local-db/init-db.d.ts +2 -1
  62. package/dist/ru/modules/local-db/init-db.js +19 -10
  63. package/dist/ru/modules/topline-service/index.d.ts +3 -1
  64. package/dist/ru/modules/topline-service/index.js +6 -0
  65. package/dist/ru/modules/topline-service/inner-service.d.ts +6 -1
  66. package/dist/ru/modules/topline-service/inner-service.js +18 -0
  67. package/dist/ru/modules/topline-service/types.d.ts +12 -0
  68. package/dist/ru/modules/topline-service/types.js +4 -0
  69. package/dist/ru/parts/login/actions/perform-login/index.d.ts +1 -15
  70. package/dist/ru/parts/login/actions/perform-login/index.js +8 -17
  71. package/dist/ru/parts/password-recovery/actions/save-password/index.d.ts +1 -15
  72. package/dist/ru/parts/password-recovery/actions/save-password/index.js +8 -17
  73. package/dist/ru/parts/profile/ui/password-change-confirmation/index.js +1 -0
  74. package/dist/ru/parts/profile/ui/password-change-form/use-state-with-validation-reset.js +1 -1
  75. package/dist/ru/parts/shell/context.d.ts +1 -1
  76. package/dist/ru/parts/shell/ui/logged-out-user-menu/index.js +2 -0
  77. package/dist/ru/parts/shell/ui/loggeg-in-user-menu/index.js +2 -0
  78. package/dist/ru/parts/shell/ui/shell/index.js +11 -5
  79. package/dist/ru/parts/shell/ui/user-menu-item/index.d.ts +1 -0
  80. package/dist/ru/parts/shell/ui/user-menu-item/index.js +2 -0
  81. package/dist/ru/parts/signup/actions/perform-signup/index.d.ts +1 -15
  82. package/dist/ru/parts/signup/actions/perform-signup/index.js +8 -17
  83. package/dist/ru/shared/components/modal/index.js +2 -2
  84. package/dist/ru/shared/procedures/process-response.d.ts +13 -0
  85. package/dist/ru/shared/procedures/process-response.js +53 -0
  86. package/dist/ru/topline.d.ts +1 -1
  87. package/dist/ru/types/result.d.ts +9 -0
  88. package/dist/ru/types/result.js +12 -0
  89. package/package.json +10 -4
  90. package/dist/en/shared/procedures/process-successful-response/index.d.ts +0 -30
  91. package/dist/en/shared/procedures/process-successful-response/index.js +0 -66
  92. package/dist/ru/shared/procedures/process-successful-response/index.d.ts +0 -30
  93. package/dist/ru/shared/procedures/process-successful-response/index.js +0 -66
package/README.md CHANGED
@@ -1,31 +1,35 @@
1
1
  # @peassoft/mnr-web-topline
2
2
 
3
- Topline widget for mem'n'rev web applications.
3
+ Topline widget for Memorize'n'Revise web applications.
4
+
5
+ ## Installation
6
+
7
+ ```shell
8
+ npm i @peassoft/mnr-web-topline
9
+ ```
4
10
 
5
11
  ## Usage Example
6
12
 
7
13
  ```jsx
8
- import { useCallback } from 'react';
14
+ import { useCallback, type JSX } from 'react';
9
15
  import WebTopline, {
10
- ToplineService,
11
- ToplineUser,
16
+ type ToplineService,
17
+ type ToplineUser,
18
+ ToplineEventName,
12
19
  } from '@peassoft/mnr-web-topline';
13
20
 
14
21
  export default function Topline(): JSX.Element {
15
22
  const handleReady = useCallback(
16
- async(toplineService: ToplineService, toplineUser: ToplineUser | null) => {
17
- toplineService
18
- .on('userChange', user => {
19
- //
20
- })
21
- .on('logout', () => {
22
- //
23
- })
24
- .on('syncNotification', syncId => {
25
- //
26
- });
27
-
28
- // Use toplineService and toplineUser somehow else
23
+ (toplineService: ToplineService, toplineUser: ToplineUser | null, isUserKnown: boolean) => {
24
+ toplineService.on(ToplineEventName.Login, user => {/*...*/});
25
+ toplineService.on(ToplineEventName.Signup, user => {/*...*/});
26
+ toplineService.on(ToplineEventName.Logout, () => {/*...*/});
27
+ toplineService.on(ToplineEventName.UserChange, user => {/*...*/});
28
+ toplineService.on(ToplineEventName.SyncNotification, syncId => {/*...*/});
29
+
30
+ // Use `toplineUser`...
31
+
32
+ // Use `isUserKnown` e.g. to determine whether to fetch the default set of data.
29
33
  },
30
34
  [],
31
35
  );
@@ -41,4 +45,87 @@ export default function Topline(): JSX.Element {
41
45
 
42
46
  ## API Reference
43
47
 
44
- Refer to the file `index.d.ts`.
48
+ ### Props
49
+
50
+ ```ts
51
+ type ToplineProps = {
52
+ /** User to use in SSR */
53
+ ssrUser?: ToplineUser | null;
54
+
55
+ /**
56
+ * If set to `true`, the link to the home page opens the home page in a separate window.
57
+ *
58
+ * For use in PWA.
59
+ */
60
+ forceBlankHomeLink?: boolean;
61
+
62
+ /** Callback that is invoked after user is restored from a local DB */
63
+ onReady?: (
64
+ toplineService: ToplineService,
65
+ user: ToplineUser | null,
66
+ isUserKnown: boolean,
67
+ ) => unknown;
68
+
69
+ /**
70
+ * Callback that is invoked if an unexpected error is caught by React's error boundary
71
+ * component
72
+ *
73
+ * This callback invokation will almost always indicate that the user management system
74
+ * is broken or in an undefined state. Thus, the hosting app should not try to recover
75
+ * from this error, but take reasonable measures depending on the situation.
76
+ */
77
+ onError?: (err: Error) => unknown;
78
+ };
79
+ ```
80
+
81
+ ### `onReady` Callback Function Parameters
82
+
83
+ * `toplineService: ToplineService` - An object containing functionality of the topline (see below).
84
+
85
+ * `user: ToplineUser | null` - User data as it's restored from the local DB. Later, topline will make an attempt to fetch fresh user data from the server which may result in the following scenarios:
86
+
87
+ * User data fetched is identical to the local data: Nothing will happen.
88
+
89
+ * User data fetched defers from the local data: `ToplineEventName.UserChange` event will be emitted on the `toplineService`.
90
+
91
+ * Request is not authenticated: `ToplineEventName.Logout` event will be emitted on the `toplineService`.
92
+
93
+ * Request fails (e.g. no Internet connection): Nothing will happen.
94
+
95
+ * `isUserKnown: boolean` - Flag that if set to `true` indicates that the current browser once been used for logging in Memorize'n'Revise. The `false` value is possible when `user` value is `null`.
96
+
97
+
98
+
99
+ ### `ToplineService`
100
+
101
+ #### Events
102
+
103
+ * `ToplineService.on(ToplineEventName.Login, (user: ToplineUser) => unknown)` - Emitted after user has successfully logged in (possibly, after password restoration procedure).
104
+
105
+ * `ToplineService.on(ToplineEventName.Signup, (user: ToplineUser) => unknown)` - Emitted after user has successfully created a new account and is now logged-in.
106
+
107
+ * `ToplineService.on(ToplineEventName.Logout, () => unknown)` - Emitted when:
108
+
109
+ * user has explicitely logged out;
110
+
111
+ * a sync notification is received about user's log-out in another application instance;
112
+
113
+ * initialization sync request resulted in status code 401.
114
+
115
+ * `ToplineService.on(ToplineEventName.UserChange, (user: ToplineUser) => unknown)` - Emitted when:
116
+
117
+ * a sync notification is received about user's data having been changed in another application instance;
118
+
119
+ * initialization sync returns different user data as of stored in the local DB.
120
+
121
+ * `ToplineService.on(ToplineEventName.SyncNotification, (syncId: string) => unknown)` - Emitted when a notification about sync is received. `syncId` is supposed to be used by applications to filter out syncs which are initiated by the application itself.
122
+
123
+ #### Methods
124
+
125
+ * `ToplineService.grantToken(): string | null` - Get current `grantToken` to use in a request which requires authorization.
126
+
127
+ * `ToplineService.upgradeGrantToken(): Promise<string | null>` - Upgrade `grantToken` with `refreshToken`. Supposed to be used after a request that used an expired `grantToken` resulted in status code `401`.
128
+
129
+ * `ToplineService.logIn(): void` - Command to open logging-in form.
130
+
131
+ * `ToplineService.createAccount(): void` - Command to open creating account form.
@@ -102,7 +102,7 @@
102
102
 
103
103
  .topline_shell_userMenu {
104
104
  position: absolute;
105
- z-index: 1;
105
+ z-index: 2;
106
106
  bottom: 0;
107
107
  right: 0;
108
108
  width: 300px;
@@ -203,7 +203,7 @@
203
203
 
204
204
  .topline_c_modal_overlay {
205
205
  position: fixed;
206
- z-index: 1;
206
+ z-index: 2;
207
207
  top: 0;
208
208
  bottom: 0;
209
209
  left: 0;
@@ -1,18 +1,16 @@
1
- /**
2
- * Keyboard key codes
3
- */
4
- declare const keyCodes: Readonly<{
5
- TAB: 9;
6
- RETURN: 13;
7
- ESC: 27;
8
- SPACE: 32;
9
- PAGEUP: 33;
10
- PAGEDOWN: 34;
11
- END: 35;
12
- HOME: 36;
13
- LEFT: 37;
14
- UP: 38;
15
- RIGHT: 39;
16
- DOWN: 40;
17
- }>;
18
- export default keyCodes;
1
+ export declare enum Key {
2
+ Tab = "Tab",
3
+ Enter = "Enter",
4
+ Escape = "Escape",
5
+ Space = " ",
6
+ PageUp = "PageUp",
7
+ PageDown = "PageDown",
8
+ End = "End",
9
+ Home = "Home",
10
+ Left = "ArrowLeft",
11
+ Up = "ArrowUp",
12
+ Right = "ArrowRight",
13
+ Down = "ArrowDown",
14
+ F2 = "F2",
15
+ F10 = "F10"
16
+ }
@@ -1,18 +1,17 @@
1
- /**
2
- * Keyboard key codes
3
- */
4
- const keyCodes = Object.freeze({
5
- TAB: 9,
6
- RETURN: 13,
7
- ESC: 27,
8
- SPACE: 32,
9
- PAGEUP: 33,
10
- PAGEDOWN: 34,
11
- END: 35,
12
- HOME: 36,
13
- LEFT: 37,
14
- UP: 38,
15
- RIGHT: 39,
16
- DOWN: 40
17
- });
18
- export default keyCodes;
1
+ export var Key;
2
+ (function (Key) {
3
+ Key["Tab"] = "Tab";
4
+ Key["Enter"] = "Enter";
5
+ Key["Escape"] = "Escape";
6
+ Key["Space"] = " ";
7
+ Key["PageUp"] = "PageUp";
8
+ Key["PageDown"] = "PageDown";
9
+ Key["End"] = "End";
10
+ Key["Home"] = "Home";
11
+ Key["Left"] = "ArrowLeft";
12
+ Key["Up"] = "ArrowUp";
13
+ Key["Right"] = "ArrowRight";
14
+ Key["Down"] = "ArrowDown";
15
+ Key["F2"] = "F2";
16
+ Key["F10"] = "F10";
17
+ })(Key || (Key = {}));
@@ -1,7 +1,7 @@
1
1
  import type { ToplineAction } from '../../types/app.js';
2
2
  export type ProcessKeyDownParams = {
3
3
  currIndex: number;
4
- e: Pick<React.KeyboardEvent, 'which' | 'preventDefault' | 'stopPropagation'>;
4
+ e: Pick<React.KeyboardEvent, 'key' | 'preventDefault' | 'stopPropagation'>;
5
5
  setCurrItemIdx: (idx: number) => unknown;
6
6
  menuItemRefs: React.RefObject<HTMLDivElement | null>[];
7
7
  actionName: ToplineAction;
@@ -1,4 +1,4 @@
1
- import keyCodes from './key-codes.js';
1
+ import { Key } from './key-codes.js';
2
2
  /**
3
3
  * Process pressed key for vertical menu
4
4
  */
@@ -14,17 +14,17 @@ export function processKeyDown(params) {
14
14
  } = params;
15
15
  const itemsNum = menuItemRefs.length;
16
16
  let nextIndex = -Infinity;
17
- switch (e.which) {
18
- case keyCodes.DOWN:
17
+ switch (e.key) {
18
+ case Key.Down:
19
19
  nextIndex = currIndex === itemsNum - 1 ? 0 : currIndex + 1;
20
20
  break;
21
- case keyCodes.UP:
21
+ case Key.Up:
22
22
  nextIndex = currIndex === 0 ? itemsNum - 1 : currIndex - 1;
23
23
  break;
24
- case keyCodes.HOME:
24
+ case Key.Home:
25
25
  nextIndex = 0;
26
26
  break;
27
- case keyCodes.END:
27
+ case Key.End:
28
28
  nextIndex = itemsNum - 1;
29
29
  }
30
30
  if (nextIndex > -Infinity) {
@@ -34,11 +34,11 @@ export function processKeyDown(params) {
34
34
  menuItemRefs[nextIndex]?.current?.focus();
35
35
  return;
36
36
  }
37
- if (e.which === keyCodes.RETURN) {
37
+ if (e.key === Key.Enter) {
38
38
  onSelect(actionName);
39
39
  return;
40
40
  }
41
- if (e.which === keyCodes.ESC) {
41
+ if (e.key === Key.Escape) {
42
42
  onClose();
43
43
  return;
44
44
  }
@@ -1,4 +1,4 @@
1
- import { getDb, STORE_NAME } from '../../init-db.js';
1
+ import { getDb, MAIN_STORE_NAME } from '../../init-db.js';
2
2
  import createRequestError from '../../create-request-error.js';
3
3
  import { logError } from '../../../logger/index.js';
4
4
  /**
@@ -11,7 +11,7 @@ export default function deleteAllData() {
11
11
  resolve();
12
12
  return;
13
13
  }
14
- const req = db.transaction(STORE_NAME, 'readwrite').objectStore(STORE_NAME).clear();
14
+ const req = db.transaction(MAIN_STORE_NAME, 'readwrite').objectStore(MAIN_STORE_NAME).clear();
15
15
  req.onsuccess = () => resolve();
16
16
  req.onerror = () => {
17
17
  logError(createRequestError('deleteAllData'));
@@ -1,4 +1,4 @@
1
- import { getDb, STORE_NAME } from '../../init-db.js';
1
+ import { getDb, MAIN_STORE_NAME } from '../../init-db.js';
2
2
  import createRequestError from '../../create-request-error.js';
3
3
  import { logError } from '../../../logger/index.js';
4
4
  /**
@@ -15,7 +15,7 @@ export default function getAllData() {
15
15
  });
16
16
  return;
17
17
  }
18
- const req = db.transaction(STORE_NAME).objectStore(STORE_NAME).getAll();
18
+ const req = db.transaction(MAIN_STORE_NAME).objectStore(MAIN_STORE_NAME).getAll();
19
19
  req.onsuccess = function () {
20
20
  const recordset = this.result;
21
21
  let user = null;
@@ -0,0 +1,5 @@
1
+ import { type Result } from '../../../../types/result.js';
2
+ /**
3
+ * Retrieve the value of flag `isUserKnown`.
4
+ */
5
+ export default function getIsUserKnown(): Promise<Result<boolean, never>>;
@@ -0,0 +1,33 @@
1
+ import { getDb, PERSISTENT_STORE_NAME } from '../../init-db.js';
2
+ import createRequestError from '../../create-request-error.js';
3
+ import { logError } from '../../../logger/index.js';
4
+ import { Ok } from '../../../../types/result.js';
5
+ /**
6
+ * Retrieve the value of flag `isUserKnown`.
7
+ */
8
+ export default function getIsUserKnown() {
9
+ return new Promise(resolve => {
10
+ const db = getDb();
11
+ if (!db) {
12
+ // If something is wrong with the DB, we would be better off returning "true"
13
+ // to prevent presenting the user the default set of content when it might
14
+ // be unreasonable.
15
+ resolve(Ok(true));
16
+ return;
17
+ }
18
+ const req = db.transaction(PERSISTENT_STORE_NAME).objectStore(PERSISTENT_STORE_NAME).get('isUserKnown');
19
+ req.onsuccess = function () {
20
+ const record = this.result;
21
+ if (record) {
22
+ resolve(Ok(record.isUserKnown));
23
+ } else {
24
+ resolve(Ok(false));
25
+ }
26
+ };
27
+ req.onerror = () => {
28
+ logError(createRequestError('getIsUserKnown'));
29
+ // For reasoning for returning "true" see comment on the "db" instance absence above.
30
+ resolve(Ok(true));
31
+ };
32
+ });
33
+ }
@@ -1,4 +1,4 @@
1
- import { getDb, STORE_NAME } from '../../init-db.js';
1
+ import { getDb, MAIN_STORE_NAME } from '../../init-db.js';
2
2
  import createRequestError from '../../create-request-error.js';
3
3
  import { logError } from '../../../logger/index.js';
4
4
  /**
@@ -11,13 +11,13 @@ export default function updateGrantToken(grantToken) {
11
11
  resolve();
12
12
  return;
13
13
  }
14
- const transaction = db.transaction(STORE_NAME, 'readwrite');
14
+ const transaction = db.transaction(MAIN_STORE_NAME, 'readwrite');
15
15
  transaction.oncomplete = () => resolve();
16
16
  transaction.onerror = () => {
17
17
  logError(createRequestError('updateGrantToken'));
18
18
  resolve();
19
19
  };
20
- const objectStore = transaction.objectStore(STORE_NAME);
20
+ const objectStore = transaction.objectStore(MAIN_STORE_NAME);
21
21
  objectStore.put({
22
22
  entityType: 'grantToken',
23
23
  grantToken
@@ -0,0 +1,5 @@
1
+ import { type Result } from '../../../../types/result.js';
2
+ /**
3
+ * Update the value of flag `isUserKnown`.
4
+ */
5
+ export default function updateIsUserKnown(value: boolean): Promise<Result<void, never>>;
@@ -0,0 +1,27 @@
1
+ import { getDb, PERSISTENT_STORE_NAME } from '../../init-db.js';
2
+ import createRequestError from '../../create-request-error.js';
3
+ import { logError } from '../../../logger/index.js';
4
+ import { Ok } from '../../../../types/result.js';
5
+ /**
6
+ * Update the value of flag `isUserKnown`.
7
+ */
8
+ export default function updateIsUserKnown(value) {
9
+ return new Promise(resolve => {
10
+ const db = getDb();
11
+ if (!db) {
12
+ resolve(Ok(undefined));
13
+ return;
14
+ }
15
+ const transaction = db.transaction(PERSISTENT_STORE_NAME, 'readwrite');
16
+ transaction.oncomplete = () => resolve(Ok(undefined));
17
+ transaction.onerror = () => {
18
+ logError(createRequestError('updateRefreshToken'));
19
+ resolve(Ok(undefined));
20
+ };
21
+ const objectStore = transaction.objectStore(PERSISTENT_STORE_NAME);
22
+ objectStore.put({
23
+ entityType: 'isUserKnown',
24
+ isUserKnown: value
25
+ });
26
+ });
27
+ }
@@ -1,4 +1,4 @@
1
- import { getDb, STORE_NAME } from '../../init-db.js';
1
+ import { getDb, MAIN_STORE_NAME } from '../../init-db.js';
2
2
  import createRequestError from '../../create-request-error.js';
3
3
  import { logError } from '../../../logger/index.js';
4
4
  /**
@@ -11,13 +11,13 @@ export default function updateRefreshToken(refreshToken) {
11
11
  resolve();
12
12
  return;
13
13
  }
14
- const transaction = db.transaction(STORE_NAME, 'readwrite');
14
+ const transaction = db.transaction(MAIN_STORE_NAME, 'readwrite');
15
15
  transaction.oncomplete = () => resolve();
16
16
  transaction.onerror = () => {
17
17
  logError(createRequestError('updateRefreshToken'));
18
18
  resolve();
19
19
  };
20
- const objectStore = transaction.objectStore(STORE_NAME);
20
+ const objectStore = transaction.objectStore(MAIN_STORE_NAME);
21
21
  objectStore.put({
22
22
  entityType: 'refreshToken',
23
23
  refreshToken
@@ -1,4 +1,4 @@
1
- import { getDb, STORE_NAME } from '../../init-db.js';
1
+ import { getDb, MAIN_STORE_NAME } from '../../init-db.js';
2
2
  import createRequestError from '../../create-request-error.js';
3
3
  import { logError } from '../../../logger/index.js';
4
4
  /**
@@ -11,13 +11,13 @@ export default function updateUser(user) {
11
11
  resolve();
12
12
  return;
13
13
  }
14
- const transaction = db.transaction(STORE_NAME, 'readwrite');
14
+ const transaction = db.transaction(MAIN_STORE_NAME, 'readwrite');
15
15
  transaction.oncomplete = () => resolve();
16
16
  transaction.onerror = () => {
17
17
  logError(createRequestError('updateUser'));
18
18
  resolve();
19
19
  };
20
- const objectStore = transaction.objectStore(STORE_NAME);
20
+ const objectStore = transaction.objectStore(MAIN_STORE_NAME);
21
21
  objectStore.put({
22
22
  entityType: 'user',
23
23
  user
@@ -4,3 +4,5 @@ export { default as deleteAllData } from './actions/delete-all-data/index.js';
4
4
  export { default as updateUser } from './actions/update-user/index.js';
5
5
  export { default as updateGrantToken } from './actions/update-grant-token/index.js';
6
6
  export { default as updateRefreshToken } from './actions/update-refresh-token/index.js';
7
+ export { default as getIsUserKnown } from './actions/get-is-user-known/index.js';
8
+ export { default as updateIsUserKnown } from './actions/update-is-user-known/index.js';
@@ -3,4 +3,6 @@ export { default as getAllData } from './actions/get-all-data/index.js';
3
3
  export { default as deleteAllData } from './actions/delete-all-data/index.js';
4
4
  export { default as updateUser } from './actions/update-user/index.js';
5
5
  export { default as updateGrantToken } from './actions/update-grant-token/index.js';
6
- export { default as updateRefreshToken } from './actions/update-refresh-token/index.js';
6
+ export { default as updateRefreshToken } from './actions/update-refresh-token/index.js';
7
+ export { default as getIsUserKnown } from './actions/get-is-user-known/index.js';
8
+ export { default as updateIsUserKnown } from './actions/update-is-user-known/index.js';
@@ -1,4 +1,5 @@
1
- export declare const STORE_NAME = "main";
1
+ export declare const MAIN_STORE_NAME = "main";
2
+ export declare const PERSISTENT_STORE_NAME = "persistent";
2
3
  /**
3
4
  * Get IndexedDb database object
4
5
  *
@@ -1,7 +1,10 @@
1
1
  import WebError from '@memnrev/web-error';
2
2
  const DB_NAME = 'mnr_topline';
3
- const DB_VERSION = 1;
4
- export const STORE_NAME = 'main';
3
+ const DB_VERSION = 2;
4
+ // Main store data is deleted when a user logs out.
5
+ export const MAIN_STORE_NAME = 'main';
6
+ // Persistent store is not cleaned up when a user logs out.
7
+ export const PERSISTENT_STORE_NAME = 'persistent';
5
8
  let _db = null;
6
9
  /**
7
10
  * Get IndexedDb database object
@@ -33,14 +36,20 @@ export function initDb() {
33
36
  resolve();
34
37
  };
35
38
  openDbReq.onupgradeneeded = function (e) {
36
- if (e.oldVersion < DB_VERSION) {
37
- const db = this.result;
38
- for (const objectStoreName of db.objectStoreNames) {
39
- db.deleteObjectStore(objectStoreName);
40
- }
41
- db.createObjectStore(STORE_NAME, {
42
- keyPath: 'entityType'
43
- });
39
+ const db = this.result;
40
+ switch (e.oldVersion) {
41
+ case 0:
42
+ db.createObjectStore(MAIN_STORE_NAME, {
43
+ keyPath: 'entityType'
44
+ });
45
+ db.createObjectStore(PERSISTENT_STORE_NAME, {
46
+ keyPath: 'entityType'
47
+ });
48
+ break;
49
+ case 1:
50
+ db.createObjectStore(PERSISTENT_STORE_NAME, {
51
+ keyPath: 'entityType'
52
+ });
44
53
  }
45
54
  };
46
55
  openDbReq.onblocked = function () {
@@ -1,4 +1,4 @@
1
- import { type LogoutListener, type UserChangeListener, type SyncNotificationListener, ToplineEventName } from './types.js';
1
+ import { type LoginListener, type SignupListener, type LogoutListener, type UserChangeListener, type SyncNotificationListener, ToplineEventName } from './types.js';
2
2
  export { ToplineEventName, InnerToplineEventName } from './types.js';
3
3
  export { default as innerToplineService, type InnerService } from './inner-service.js';
4
4
  /**
@@ -13,6 +13,8 @@ declare class _ToplineService {
13
13
  /** Upgrade grant token with refresh token */
14
14
  upgradeGrantToken(): Promise<string | null>;
15
15
  /** Register an event listener */
16
+ on(eventName: ToplineEventName.Login, cb: LoginListener): this;
17
+ on(eventName: ToplineEventName.Signup, cb: SignupListener): this;
16
18
  on(eventName: ToplineEventName.Logout, cb: LogoutListener): this;
17
19
  on(eventName: ToplineEventName.UserChange, cb: UserChangeListener): this;
18
20
  on(eventName: ToplineEventName.SyncNotification, cb: SyncNotificationListener): this;
@@ -17,6 +17,12 @@ class _ToplineService {
17
17
  }
18
18
  on(eventName, cb) {
19
19
  switch (eventName) {
20
+ case ToplineEventName.Login:
21
+ this.#innerSrvs.on(eventName, cb);
22
+ break;
23
+ case ToplineEventName.Signup:
24
+ this.#innerSrvs.on(eventName, cb);
25
+ break;
20
26
  case ToplineEventName.Logout:
21
27
  this.#innerSrvs.on(eventName, cb);
22
28
  break;
@@ -4,7 +4,7 @@ import { getApiBaseUrl } from '../env/index.js';
4
4
  import request from '../request/index.js';
5
5
  import { logError } from '../logger/index.js';
6
6
  import type { ToplineUser } from '../../types/data.js';
7
- import { type LogoutListener, type UserChangeListener, type SyncNotificationListener, type InnerListenerCallback, ToplineEventName, InnerToplineEventName } from './types.js';
7
+ import { type LoginListener, type SignupListener, type LogoutListener, type UserChangeListener, type SyncNotificationListener, type InnerListenerCallback, ToplineEventName, InnerToplineEventName } from './types.js';
8
8
  export type Deps = {
9
9
  getRefreshToken: typeof getRefreshToken;
10
10
  setGrantToken: typeof setGrantToken;
@@ -21,13 +21,18 @@ export declare class InnerService {
21
21
  /** Get grant token */
22
22
  grantToken(): string | null;
23
23
  /** Register an event listener */
24
+ on(eventName: ToplineEventName.Login, cb: LoginListener): void;
25
+ on(eventName: ToplineEventName.Signup, cb: SignupListener): void;
24
26
  on(eventName: ToplineEventName.Logout, cb: LogoutListener): void;
25
27
  on(eventName: ToplineEventName.UserChange, cb: UserChangeListener): void;
26
28
  on(eventName: ToplineEventName.SyncNotification, cb: SyncNotificationListener): void;
27
29
  /** Emit an outer event */
30
+ emit(eventName: ToplineEventName.Login, payload: ToplineUser): void;
31
+ emit(eventName: ToplineEventName.Signup, payload: ToplineUser): void;
28
32
  emit(eventName: ToplineEventName.Logout): void;
29
33
  emit(eventName: ToplineEventName.UserChange, payload: ToplineUser): void;
30
34
  emit(eventName: ToplineEventName.SyncNotification, payload: string): void;
35
+ emit(eventName: ToplineEventName, payload?: ToplineUser | string): void;
31
36
  /** Upgrade grant token with refresh token */
32
37
  upgradeGrantToken(): Promise<string | null>;
33
38
  /**
@@ -38,6 +38,18 @@ export class InnerService {
38
38
  }
39
39
  on(eventName, cb) {
40
40
  switch (eventName) {
41
+ case ToplineEventName.Login:
42
+ this.#listeners.add({
43
+ eventName,
44
+ cb: cb
45
+ });
46
+ break;
47
+ case ToplineEventName.Signup:
48
+ this.#listeners.add({
49
+ eventName,
50
+ cb: cb
51
+ });
52
+ break;
41
53
  case ToplineEventName.Logout:
42
54
  this.#listeners.add({
43
55
  eventName,
@@ -67,6 +79,12 @@ export class InnerService {
67
79
  this.#listeners.forEach(listener => {
68
80
  if (eventName === listener.eventName) {
69
81
  switch (listener.eventName) {
82
+ case ToplineEventName.Login:
83
+ listener.cb.call(this, payload);
84
+ break;
85
+ case ToplineEventName.Signup:
86
+ listener.cb.call(this, payload);
87
+ break;
70
88
  case ToplineEventName.Logout:
71
89
  listener.cb.call(this);
72
90
  break;
@@ -5,6 +5,10 @@ import type { ToplineUser } from '../../types/data.js';
5
5
  * @public
6
6
  */
7
7
  export declare enum ToplineEventName {
8
+ /** User has been logged in */
9
+ Login = "login",
10
+ /** User has been signed up */
11
+ Signup = "signup",
8
12
  /** User has been logged out */
9
13
  Logout = "logout",
10
14
  /** User data changed */
@@ -12,10 +16,18 @@ export declare enum ToplineEventName {
12
16
  /** A sync notification has been received */
13
17
  SyncNotification = "syncNotification"
14
18
  }
19
+ export type LoginListener = (user: ToplineUser) => unknown;
20
+ export type SignupListener = (user: ToplineUser) => unknown;
15
21
  export type LogoutListener = () => unknown;
16
22
  export type UserChangeListener = (user: ToplineUser) => unknown;
17
23
  export type SyncNotificationListener = (syncId: string) => unknown;
18
24
  export type ToplineEventListener = {
25
+ eventName: ToplineEventName.Login;
26
+ cb: LoginListener;
27
+ } | {
28
+ eventName: ToplineEventName.Signup;
29
+ cb: SignupListener;
30
+ } | {
19
31
  eventName: ToplineEventName.Logout;
20
32
  cb: LogoutListener;
21
33
  } | {