@uxland/primary-shell 7.43.1 → 7.43.2

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,5 +1,4 @@
1
1
  import { ActivityMonitor } from '../activity-monitor/activity-monitor';
2
- import { PrimariaBroker } from '../broker/primaria-broker';
3
2
  import { TokenManager } from '../token-manager/token-manager';
4
3
  export interface SessionRefreshTimer {
5
4
  start: () => void;
@@ -8,13 +7,12 @@ export interface SessionRefreshTimer {
8
7
  export declare class SessionRefreshTimerImpl implements SessionRefreshTimer {
9
8
  private readonly tokenManager;
10
9
  private readonly activityMonitor;
11
- private readonly broker;
12
10
  private intervalId;
13
11
  private isRefreshing;
14
- constructor(tokenManager: TokenManager, activityMonitor: ActivityMonitor, broker: PrimariaBroker);
12
+ constructor(tokenManager: TokenManager, activityMonitor: ActivityMonitor);
15
13
  start: () => void;
16
14
  stop: () => void;
17
15
  private getTokenExpiry;
18
16
  private checkAndRefresh;
19
17
  }
20
- export declare const createSessionRefreshTimer: (tokenManager: TokenManager, activityMonitor: ActivityMonitor, broker: PrimariaBroker) => SessionRefreshTimer;
18
+ export declare const createSessionRefreshTimer: (tokenManager: TokenManager, activityMonitor: ActivityMonitor) => SessionRefreshTimer;
@@ -1,13 +1,16 @@
1
+ import { PrimariaBroker } from '../broker/primaria-broker';
1
2
  export interface TokenManager {
2
3
  setInitialTokens: (access_token: string, refresh_token: string) => void;
3
4
  getToken: () => string;
4
5
  refreshToken: () => Promise<string>;
5
6
  }
6
7
  export declare class TokenManagerImpl implements TokenManager {
8
+ private readonly broker;
9
+ constructor(broker: PrimariaBroker);
7
10
  getUrlParams: () => URLSearchParams;
8
11
  private initToken;
9
12
  setInitialTokens: (access_token: string, refresh_token: string) => void;
10
13
  getToken: () => string;
11
14
  refreshToken: () => Promise<string>;
12
15
  }
13
- export declare const createTokenManager: () => TokenManagerImpl;
16
+ export declare const createTokenManager: (broker: PrimariaBroker) => TokenManagerImpl;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uxland/primary-shell",
3
- "version": "7.43.1",
3
+ "version": "7.43.2",
4
4
  "description": "Primaria Shell",
5
5
  "author": "UXLand <dev@uxland.es>",
6
6
  "homepage": "https://github.com/uxland/harmonix/tree/app#readme",
package/src/api/api.ts CHANGED
@@ -44,10 +44,10 @@ export interface PrimariaApi extends HarmonixApi {
44
44
 
45
45
  const regionManager: RegionManager = createRegionManager("primaria");
46
46
  export const PrimariaRegionHost: any = createRegionHost(regionManager as any);
47
- const tokenManager = createTokenManager();
47
+ const tokenManager = createTokenManager(broker);
48
48
  const userManager = createUserManager(tokenManager);
49
49
  export const activityMonitor = createActivityMonitor();
50
- export const sessionRefreshTimer = createSessionRefreshTimer(tokenManager, activityMonitor, broker);
50
+ export const sessionRefreshTimer = createSessionRefreshTimer(tokenManager, activityMonitor);
51
51
  const globalStateManager: PrimariaGlobalStateManager = createGlobalStateManager(broker);
52
52
  const contextManager = createContextManager();
53
53
  const pluginBusyManager = new PluginBusyManagerImpl();
@@ -28,7 +28,7 @@ describe("HTTP Client", () => {
28
28
 
29
29
  describe("Without validateMpid", () => {
30
30
  beforeEach(() => {
31
- tokenManager = createTokenManager();
31
+ tokenManager = createTokenManager(broker);
32
32
  axiosInstance = createAxiosInstance(tokenManager, broker);
33
33
  axiosMockInstance = new AxiosMockAdapter(axiosInstance);
34
34
  axiosMockInstance.reset();
@@ -91,10 +91,7 @@ describe("HTTP Client", () => {
91
91
  );
92
92
 
93
93
  expect(axiosMockInstance.history.get?.length).toBe(1); // No retry happened
94
- expect(brokerSpy).toHaveBeenCalledWith(BROKER_EVENTS.shell.refreshTokenFailed, expect.any(Object));
95
- expect(brokerSpy).toHaveBeenCalledWith(BROKER_EVENTS.shell.refreshTokenFailed, {
96
- request: expect.objectContaining({ url: "/api/clinical-course" }),
97
- });
94
+ expect(brokerSpy).toHaveBeenCalledWith(BROKER_EVENTS.shell.refreshTokenFailed, {});
98
95
  });
99
96
 
100
97
  it("should fail request with error != 401 and should not retry", async () => {
@@ -113,7 +110,7 @@ describe("HTTP Client", () => {
113
110
  describe.skip("With validateMpid", () => {
114
111
  const validTokenHeaders = { "x-catsalut-mpid": "5ec74c7b-5aa5-4a84-b3b4-f02eddfdf6dc" };
115
112
  beforeEach(() => {
116
- tokenManager = createTokenManager();
113
+ tokenManager = createTokenManager(broker);
117
114
  axiosInstance = createAxiosInstance(tokenManager, broker, true);
118
115
  axiosMockInstance = new AxiosMockAdapter(axiosInstance);
119
116
  axiosMockInstance.reset();
@@ -200,10 +197,7 @@ describe("HTTP Client", () => {
200
197
  );
201
198
 
202
199
  expect(axiosMockInstance.history.get?.length).toBe(1); // No retry happened
203
- expect(brokerSpy).toHaveBeenCalledWith(BROKER_EVENTS.shell.refreshTokenFailed, expect.any(Object));
204
- expect(brokerSpy).toHaveBeenCalledWith(BROKER_EVENTS.shell.refreshTokenFailed, {
205
- request: expect.objectContaining({ url: "/api/clinical-course" }),
206
- });
200
+ expect(brokerSpy).toHaveBeenCalledWith(BROKER_EVENTS.shell.refreshTokenFailed, {});
207
201
  });
208
202
 
209
203
  it("should fail request with error != 401 and should not retry", async () => {
@@ -69,9 +69,6 @@ export const createAxiosInstance = (
69
69
  return instance(originalRequest);
70
70
  } catch (refreshError) {
71
71
  console.error("Error refreshing token:", refreshError);
72
- broker.publish(BROKER_EVENTS.shell.refreshTokenFailed, {
73
- request: originalRequest,
74
- });
75
72
  return Promise.reject(error);
76
73
  }
77
74
  }
@@ -1,5 +1,4 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import { BROKER_EVENTS } from "../broker/broker-events";
3
2
  import { SessionRefreshTimerImpl } from "./session-refresh-timer";
4
3
 
5
4
  const createJwt = (exp: number): string => {
@@ -23,20 +22,9 @@ const createMockActivityMonitor = (lastActivity = Date.now()) => ({
23
22
  getLastActivityTimestamp: vi.fn().mockReturnValue(lastActivity),
24
23
  });
25
24
 
26
- const createMockBroker = () => ({
27
- publish: vi.fn().mockResolvedValue(undefined),
28
- subscribe: vi.fn(),
29
- send: vi.fn(),
30
- registerRequest: vi.fn(),
31
- events: BROKER_EVENTS,
32
- });
33
-
34
25
  describe("SessionRefreshTimer", () => {
35
- let mockBroker: ReturnType<typeof createMockBroker>;
36
-
37
26
  beforeEach(() => {
38
27
  vi.useFakeTimers();
39
- mockBroker = createMockBroker();
40
28
  });
41
29
 
42
30
  afterEach(() => {
@@ -48,11 +36,7 @@ describe("SessionRefreshTimer", () => {
48
36
  const tokenManager = createMockTokenManager({
49
37
  getToken: vi.fn().mockReturnValue(createJwt(Math.floor(Date.now() / 1000) + 60)),
50
38
  });
51
- const timer = new SessionRefreshTimerImpl(
52
- tokenManager,
53
- createMockActivityMonitor(0),
54
- mockBroker,
55
- );
39
+ const timer = new SessionRefreshTimerImpl(tokenManager, createMockActivityMonitor(0));
56
40
 
57
41
  timer.start();
58
42
  await vi.advanceTimersByTimeAsync(30_000);
@@ -64,11 +48,7 @@ describe("SessionRefreshTimer", () => {
64
48
  const tokenManager = createMockTokenManager({
65
49
  getToken: vi.fn().mockReturnValue(createJwt(Math.floor(Date.now() / 1000) + 3600)),
66
50
  });
67
- const timer = new SessionRefreshTimerImpl(
68
- tokenManager,
69
- createMockActivityMonitor(),
70
- mockBroker,
71
- );
51
+ const timer = new SessionRefreshTimerImpl(tokenManager, createMockActivityMonitor());
72
52
 
73
53
  timer.start();
74
54
  await vi.advanceTimersByTimeAsync(30_000);
@@ -80,11 +60,7 @@ describe("SessionRefreshTimer", () => {
80
60
  const tokenManager = createMockTokenManager({
81
61
  getToken: vi.fn().mockReturnValue(createJwt(Math.floor(Date.now() / 1000) + 60)),
82
62
  });
83
- const timer = new SessionRefreshTimerImpl(
84
- tokenManager,
85
- createMockActivityMonitor(),
86
- mockBroker,
87
- );
63
+ const timer = new SessionRefreshTimerImpl(tokenManager, createMockActivityMonitor());
88
64
 
89
65
  timer.start();
90
66
  await vi.advanceTimersByTimeAsync(30_000);
@@ -96,11 +72,7 @@ describe("SessionRefreshTimer", () => {
96
72
  const tokenManager = createMockTokenManager({
97
73
  getToken: vi.fn().mockReturnValue(createJwt(Math.floor(Date.now() / 1000) - 60)),
98
74
  });
99
- const timer = new SessionRefreshTimerImpl(
100
- tokenManager,
101
- createMockActivityMonitor(),
102
- mockBroker,
103
- );
75
+ const timer = new SessionRefreshTimerImpl(tokenManager, createMockActivityMonitor());
104
76
 
105
77
  timer.start();
106
78
  await vi.advanceTimersByTimeAsync(30_000);
@@ -108,23 +80,6 @@ describe("SessionRefreshTimer", () => {
108
80
  expect(tokenManager.refreshToken).toHaveBeenCalledTimes(1);
109
81
  });
110
82
 
111
- it("should publish refreshTokenFailed when refresh throws", async () => {
112
- const tokenManager = createMockTokenManager({
113
- getToken: vi.fn().mockReturnValue(createJwt(Math.floor(Date.now() / 1000) - 60)),
114
- refreshToken: vi.fn().mockRejectedValue(new Error("Refresh failed")),
115
- });
116
- const timer = new SessionRefreshTimerImpl(
117
- tokenManager,
118
- createMockActivityMonitor(),
119
- mockBroker,
120
- );
121
-
122
- timer.start();
123
- await vi.advanceTimersByTimeAsync(30_000);
124
-
125
- expect(mockBroker.publish).toHaveBeenCalledWith(BROKER_EVENTS.shell.refreshTokenFailed, {});
126
- });
127
-
128
83
  it("should not trigger concurrent refreshes", async () => {
129
84
  let resolveRefresh!: () => void;
130
85
  const tokenManager = createMockTokenManager({
@@ -133,11 +88,7 @@ describe("SessionRefreshTimer", () => {
133
88
  () => new Promise((resolve) => { resolveRefresh = () => resolve("new_token"); }),
134
89
  ),
135
90
  });
136
- const timer = new SessionRefreshTimerImpl(
137
- tokenManager,
138
- createMockActivityMonitor(),
139
- mockBroker,
140
- );
91
+ const timer = new SessionRefreshTimerImpl(tokenManager, createMockActivityMonitor());
141
92
 
142
93
  timer.start();
143
94
  await vi.advanceTimersByTimeAsync(30_000);
@@ -1,7 +1,5 @@
1
1
  import { jwtDecode } from "jwt-decode";
2
2
  import { ActivityMonitor } from "../activity-monitor/activity-monitor";
3
- import { BROKER_EVENTS } from "../broker/broker-events";
4
- import { PrimariaBroker } from "../broker/primaria-broker";
5
3
  import { TokenManager } from "../token-manager/token-manager";
6
4
 
7
5
  const CHECK_INTERVAL_MS = 30_000;
@@ -20,7 +18,6 @@ export class SessionRefreshTimerImpl implements SessionRefreshTimer {
20
18
  constructor(
21
19
  private readonly tokenManager: TokenManager,
22
20
  private readonly activityMonitor: ActivityMonitor,
23
- private readonly broker: PrimariaBroker,
24
21
  ) {}
25
22
 
26
23
  start = () => {
@@ -64,7 +61,7 @@ export class SessionRefreshTimerImpl implements SessionRefreshTimer {
64
61
  try {
65
62
  await this.tokenManager.refreshToken();
66
63
  } catch {
67
- this.broker.publish(BROKER_EVENTS.shell.refreshTokenFailed, {});
64
+ // tokenManager.refreshToken() publishes refreshTokenFailed via broker on failure
68
65
  } finally {
69
66
  this.isRefreshing = false;
70
67
  }
@@ -75,5 +72,4 @@ export class SessionRefreshTimerImpl implements SessionRefreshTimer {
75
72
  export const createSessionRefreshTimer = (
76
73
  tokenManager: TokenManager,
77
74
  activityMonitor: ActivityMonitor,
78
- broker: PrimariaBroker,
79
- ): SessionRefreshTimer => new SessionRefreshTimerImpl(tokenManager, activityMonitor, broker);
75
+ ): SessionRefreshTimer => new SessionRefreshTimerImpl(tokenManager, activityMonitor);
@@ -1,5 +1,6 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
2
  import axios from "axios";
3
+ import { BROKER_EVENTS } from "../broker/broker-events";
3
4
 
4
5
  vi.mock("axios");
5
6
 
@@ -8,10 +9,19 @@ const refresh_token = "test_refresh_token";
8
9
  const new_access_token = "new_access_token";
9
10
  const new_refresh_token = "new_refresh_token";
10
11
 
12
+ const createMockBroker = () => ({
13
+ publish: vi.fn().mockResolvedValue(undefined),
14
+ subscribe: vi.fn(),
15
+ send: vi.fn(),
16
+ registerRequest: vi.fn(),
17
+ events: BROKER_EVENTS,
18
+ });
19
+
11
20
  describe("TokenManager", () => {
12
21
  let mockLocation: Partial<Location>;
13
22
  let TokenManagerImpl: any;
14
23
  let createTokenManager: any;
24
+ let mockBroker: ReturnType<typeof createMockBroker>;
15
25
 
16
26
  beforeEach(async () => {
17
27
  vi.resetModules();
@@ -19,6 +29,7 @@ describe("TokenManager", () => {
19
29
  search: `?access_token=${access_token}&refresh_token=${refresh_token}`,
20
30
  };
21
31
  vi.spyOn(window, "location", "get").mockReturnValue(mockLocation as Location);
32
+ mockBroker = createMockBroker();
22
33
 
23
34
  const module = await import("./token-manager");
24
35
  TokenManagerImpl = module.TokenManagerImpl;
@@ -31,31 +42,31 @@ describe("TokenManager", () => {
31
42
 
32
43
  describe("getToken", () => {
33
44
  it("should initialize token from URL params on first call", () => {
34
- const tokenManager = new TokenManagerImpl();
45
+ const tokenManager = new TokenManagerImpl(mockBroker);
35
46
  expect(tokenManager.getToken()).toBe(access_token);
36
47
  });
37
48
 
38
49
  it("should return empty string if URL params are missing", () => {
39
50
  mockLocation.search = "";
40
- const tokenManager = new TokenManagerImpl();
51
+ const tokenManager = new TokenManagerImpl(mockBroker);
41
52
  expect(tokenManager.getToken()).toBe("");
42
53
  });
43
54
 
44
55
  it("should return the same token on subsequent calls", () => {
45
- const tokenManager = new TokenManagerImpl();
56
+ const tokenManager = new TokenManagerImpl(mockBroker);
46
57
  expect(tokenManager.getToken()).toBe(tokenManager.getToken());
47
58
  });
48
59
  });
49
60
 
50
61
  describe("setInitialTokens", () => {
51
62
  it("should set tokens manually", () => {
52
- const tokenManager = new TokenManagerImpl();
63
+ const tokenManager = new TokenManagerImpl(mockBroker);
53
64
  tokenManager.setInitialTokens("manual_access", "manual_refresh");
54
65
  expect(tokenManager.getToken()).toBe("manual_access");
55
66
  });
56
67
 
57
68
  it("should throw error if tokens are already initialized", () => {
58
- const tokenManager = new TokenManagerImpl();
69
+ const tokenManager = new TokenManagerImpl(mockBroker);
59
70
  tokenManager.setInitialTokens("manual_access", "manual_refresh");
60
71
  expect(() => tokenManager.setInitialTokens("another_access", "another_refresh")).toThrow(
61
72
  "Token already initialized",
@@ -63,7 +74,7 @@ describe("TokenManager", () => {
63
74
  });
64
75
 
65
76
  it("should throw error if tokens were already initialized from URL", () => {
66
- const tokenManager = new TokenManagerImpl();
77
+ const tokenManager = new TokenManagerImpl(mockBroker);
67
78
  tokenManager.getToken();
68
79
  expect(() => tokenManager.setInitialTokens("manual_access", "manual_refresh")).toThrow(
69
80
  "Token already initialized",
@@ -77,7 +88,7 @@ describe("TokenManager", () => {
77
88
  data: { access_token: new_access_token, refresh_token: new_refresh_token },
78
89
  });
79
90
 
80
- const tokenManager = new TokenManagerImpl();
91
+ const tokenManager = new TokenManagerImpl(mockBroker);
81
92
  tokenManager.setInitialTokens(access_token, refresh_token);
82
93
 
83
94
  const refreshed = await tokenManager.refreshToken();
@@ -92,7 +103,7 @@ describe("TokenManager", () => {
92
103
  data: { access_token: new_access_token, refresh_token: new_refresh_token },
93
104
  });
94
105
 
95
- const tokenManager = new TokenManagerImpl();
106
+ const tokenManager = new TokenManagerImpl(mockBroker);
96
107
  await tokenManager.refreshToken();
97
108
 
98
109
  expect(axios.post).toHaveBeenCalledWith("/api/token/refresh", { token: refresh_token });
@@ -101,25 +112,38 @@ describe("TokenManager", () => {
101
112
  it("should throw error if refresh response doesn't contain access_token", async () => {
102
113
  vi.mocked(axios.post).mockResolvedValue({ data: {} });
103
114
 
104
- const tokenManager = new TokenManagerImpl();
115
+ const tokenManager = new TokenManagerImpl(mockBroker);
105
116
  tokenManager.setInitialTokens(access_token, refresh_token);
106
117
 
107
118
  await expect(tokenManager.refreshToken()).rejects.toThrow("Invalid refresh token response");
108
119
  });
109
120
 
110
- it("should handle axios errors", async () => {
121
+ it("should publish refreshTokenFailed and rethrow when refresh fails", async () => {
111
122
  vi.mocked(axios.post).mockRejectedValue(new Error("Network error"));
112
123
 
113
- const tokenManager = new TokenManagerImpl();
124
+ const tokenManager = new TokenManagerImpl(mockBroker);
114
125
  tokenManager.setInitialTokens(access_token, refresh_token);
115
126
 
116
127
  await expect(tokenManager.refreshToken()).rejects.toThrow("Network error");
128
+ expect(mockBroker.publish).toHaveBeenCalledWith(BROKER_EVENTS.shell.refreshTokenFailed, {});
129
+ });
130
+
131
+ it("should not publish refreshTokenFailed when refresh succeeds", async () => {
132
+ vi.mocked(axios.post).mockResolvedValue({
133
+ data: { access_token: new_access_token, refresh_token: new_refresh_token },
134
+ });
135
+
136
+ const tokenManager = new TokenManagerImpl(mockBroker);
137
+ tokenManager.setInitialTokens(access_token, refresh_token);
138
+
139
+ await tokenManager.refreshToken();
140
+ expect(mockBroker.publish).not.toHaveBeenCalled();
117
141
  });
118
142
  });
119
143
 
120
144
  describe("createTokenManager", () => {
121
145
  it("should return a singleton instance", () => {
122
- expect(createTokenManager()).toBe(createTokenManager());
146
+ expect(createTokenManager(mockBroker)).toBe(createTokenManager(mockBroker));
123
147
  });
124
148
  });
125
149
  });
@@ -1,4 +1,6 @@
1
1
  import axios from "axios";
2
+ import { BROKER_EVENTS } from "../broker/broker-events";
3
+ import { PrimariaBroker } from "../broker/primaria-broker";
2
4
 
3
5
  export interface TokenManager {
4
6
  setInitialTokens: (access_token: string, refresh_token: string) => void;
@@ -11,6 +13,8 @@ let refreshToken: string;
11
13
  let tokenInitialized = false;
12
14
 
13
15
  export class TokenManagerImpl implements TokenManager {
16
+ constructor(private readonly broker: PrimariaBroker) {}
17
+
14
18
  getUrlParams = (): URLSearchParams => {
15
19
  return new URLSearchParams(window.location.search);
16
20
  };
@@ -38,19 +42,24 @@ export class TokenManagerImpl implements TokenManager {
38
42
 
39
43
  refreshToken = async () => {
40
44
  if (!tokenInitialized) this.initToken();
41
- const response = await axios.post("/api/token/refresh", { token: refreshToken });
42
- const { access_token, refresh_token } = response.data;
43
- if (!access_token) throw new Error("Invalid refresh token response");
44
- token = access_token;
45
- refreshToken = refresh_token;
46
- return token;
45
+ try {
46
+ const response = await axios.post("/api/token/refresh", { token: refreshToken });
47
+ const { access_token, refresh_token } = response.data;
48
+ if (!access_token) throw new Error("Invalid refresh token response");
49
+ token = access_token;
50
+ refreshToken = refresh_token;
51
+ return token;
52
+ } catch (error) {
53
+ this.broker.publish(BROKER_EVENTS.shell.refreshTokenFailed, {});
54
+ throw error;
55
+ }
47
56
  };
48
57
  }
49
58
 
50
59
  let tokenManager: TokenManagerImpl;
51
60
 
52
- export const createTokenManager = () => {
61
+ export const createTokenManager = (broker: PrimariaBroker) => {
53
62
  if (tokenManager) return tokenManager;
54
- tokenManager = new TokenManagerImpl();
63
+ tokenManager = new TokenManagerImpl(broker);
55
64
  return tokenManager;
56
65
  };