@uxland/primary-shell 7.42.0 → 7.43.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ export interface ActivityMonitor {
2
+ start: () => void;
3
+ stop: () => void;
4
+ getLastActivityTimestamp: () => number;
5
+ }
6
+ export declare class ActivityMonitorImpl implements ActivityMonitor {
7
+ private lastActivityTimestamp;
8
+ private readonly activityHandler;
9
+ start: () => void;
10
+ stop: () => void;
11
+ getLastActivityTimestamp: () => number;
12
+ }
13
+ export declare const createActivityMonitor: () => ActivityMonitor;
@@ -32,6 +32,8 @@ export interface PrimariaApi extends HarmonixApi {
32
32
  importDataManager: PrimariaImportDataManager;
33
33
  }
34
34
  export declare const PrimariaRegionHost: any;
35
+ export declare const activityMonitor: import('./activity-monitor/activity-monitor').ActivityMonitor;
36
+ export declare const sessionRefreshTimer: import('./session-refresh-timer/session-refresh-timer').SessionRefreshTimer;
35
37
  /**
36
38
  * Factory function that creates a Primaria API instance.
37
39
  *
@@ -0,0 +1,20 @@
1
+ import { ActivityMonitor } from '../activity-monitor/activity-monitor';
2
+ import { PrimariaBroker } from '../broker/primaria-broker';
3
+ import { TokenManager } from '../token-manager/token-manager';
4
+ export interface SessionRefreshTimer {
5
+ start: () => void;
6
+ stop: () => void;
7
+ }
8
+ export declare class SessionRefreshTimerImpl implements SessionRefreshTimer {
9
+ private readonly tokenManager;
10
+ private readonly activityMonitor;
11
+ private readonly broker;
12
+ private intervalId;
13
+ private isRefreshing;
14
+ constructor(tokenManager: TokenManager, activityMonitor: ActivityMonitor, broker: PrimariaBroker);
15
+ start: () => void;
16
+ stop: () => void;
17
+ private getTokenExpiry;
18
+ private checkAndRefresh;
19
+ }
20
+ export declare const createSessionRefreshTimer: (tokenManager: TokenManager, activityMonitor: ActivityMonitor, broker: PrimariaBroker) => SessionRefreshTimer;
@@ -10,4 +10,4 @@ export declare class TokenManagerImpl implements TokenManager {
10
10
  getToken: () => string;
11
11
  refreshToken: () => Promise<string>;
12
12
  }
13
- export declare const createTokenManager: () => any;
13
+ export declare const createTokenManager: () => TokenManagerImpl;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uxland/primary-shell",
3
- "version": "7.42.0",
3
+ "version": "7.43.1",
4
4
  "description": "Primaria Shell",
5
5
  "author": "UXLand <dev@uxland.es>",
6
6
  "homepage": "https://github.com/uxland/harmonix/tree/app#readme",
@@ -0,0 +1,67 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { ActivityMonitorImpl } from "./activity-monitor";
3
+
4
+ describe("ActivityMonitor", () => {
5
+ let monitor: ActivityMonitorImpl;
6
+
7
+ beforeEach(() => {
8
+ vi.useFakeTimers();
9
+ monitor = new ActivityMonitorImpl();
10
+ });
11
+
12
+ afterEach(() => {
13
+ monitor.stop();
14
+ vi.useRealTimers();
15
+ });
16
+
17
+ it("should return 0 before any activity", () => {
18
+ expect(monitor.getLastActivityTimestamp()).toBe(0);
19
+ });
20
+
21
+ it("should update timestamp on keydown", () => {
22
+ monitor.start();
23
+ const before = Date.now();
24
+ window.dispatchEvent(new Event("keydown"));
25
+ expect(monitor.getLastActivityTimestamp()).toBeGreaterThanOrEqual(before);
26
+ });
27
+
28
+ it("should update timestamp on click", () => {
29
+ monitor.start();
30
+ const before = Date.now();
31
+ window.dispatchEvent(new Event("click"));
32
+ expect(monitor.getLastActivityTimestamp()).toBeGreaterThanOrEqual(before);
33
+ });
34
+
35
+ it("should update timestamp on scroll", () => {
36
+ monitor.start();
37
+ const before = Date.now();
38
+ window.dispatchEvent(new Event("scroll"));
39
+ expect(monitor.getLastActivityTimestamp()).toBeGreaterThanOrEqual(before);
40
+ });
41
+
42
+ it("should reflect the most recent activity time", () => {
43
+ monitor.start();
44
+
45
+ window.dispatchEvent(new Event("keydown"));
46
+ const first = monitor.getLastActivityTimestamp();
47
+
48
+ vi.advanceTimersByTime(5000);
49
+ window.dispatchEvent(new Event("click"));
50
+ const second = monitor.getLastActivityTimestamp();
51
+
52
+ expect(second).toBeGreaterThan(first);
53
+ });
54
+
55
+ it("should not update timestamp after stop", () => {
56
+ monitor.start();
57
+ window.dispatchEvent(new Event("keydown"));
58
+ const timestampBeforeStop = monitor.getLastActivityTimestamp();
59
+
60
+ monitor.stop();
61
+ vi.advanceTimersByTime(5000);
62
+ window.dispatchEvent(new Event("click"));
63
+
64
+ expect(monitor.getLastActivityTimestamp()).toBe(timestampBeforeStop);
65
+ });
66
+
67
+ });
@@ -0,0 +1,26 @@
1
+ const ACTIVITY_EVENTS = ["keydown", "click", "scroll"] as const;
2
+
3
+ export interface ActivityMonitor {
4
+ start: () => void;
5
+ stop: () => void;
6
+ getLastActivityTimestamp: () => number;
7
+ }
8
+
9
+ export class ActivityMonitorImpl implements ActivityMonitor {
10
+ private lastActivityTimestamp = 0;
11
+ private readonly activityHandler = () => {
12
+ this.lastActivityTimestamp = Date.now();
13
+ };
14
+
15
+ start = () => {
16
+ for (const e of ACTIVITY_EVENTS) window.addEventListener(e, this.activityHandler);
17
+ };
18
+
19
+ stop = () => {
20
+ for (const e of ACTIVITY_EVENTS) window.removeEventListener(e, this.activityHandler);
21
+ };
22
+
23
+ getLastActivityTimestamp = () => this.lastActivityTimestamp;
24
+ }
25
+
26
+ export const createActivityMonitor = (): ActivityMonitor => new ActivityMonitorImpl();
package/src/api/api.ts CHANGED
@@ -5,8 +5,10 @@ import { PrimariaBroker } from "./broker/primaria-broker";
5
5
  import { EcapEventManager, createEcapEventManager } from "./ecap-event-manager/ecap-event-manager";
6
6
  import { ExitGuardManager, ExitGuardManagerImpl } from "./exit-guard-manager/exit-guard-manager";
7
7
  import { PrimariaGlobalStateManager, createGlobalStateManager } from "./global-state/global-state";
8
+ import { createActivityMonitor } from "./activity-monitor/activity-monitor";
8
9
  import { HttpClient, createHttpClient } from "./http-client/http-client";
9
10
  import { PrimariaInteractionService } from "./interaction-service";
11
+ import { createSessionRefreshTimer } from "./session-refresh-timer/session-refresh-timer";
10
12
  import { ParimariaInteractionServiceImpl } from "./interaction-service/interaction-service-impl";
11
13
  import { createLocaleManager } from "./localization/localization";
12
14
  import { PrimariaNotificationService } from "./notification-service/notification-service";
@@ -44,6 +46,8 @@ const regionManager: RegionManager = createRegionManager("primaria");
44
46
  export const PrimariaRegionHost: any = createRegionHost(regionManager as any);
45
47
  const tokenManager = createTokenManager();
46
48
  const userManager = createUserManager(tokenManager);
49
+ export const activityMonitor = createActivityMonitor();
50
+ export const sessionRefreshTimer = createSessionRefreshTimer(tokenManager, activityMonitor, broker);
47
51
  const globalStateManager: PrimariaGlobalStateManager = createGlobalStateManager(broker);
48
52
  const contextManager = createContextManager();
49
53
  const pluginBusyManager = new PluginBusyManagerImpl();
@@ -0,0 +1,151 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { BROKER_EVENTS } from "../broker/broker-events";
3
+ import { SessionRefreshTimerImpl } from "./session-refresh-timer";
4
+
5
+ const createJwt = (exp: number): string => {
6
+ const payload = btoa(JSON.stringify({ exp }))
7
+ .replace(/=/g, "")
8
+ .replace(/\+/g, "-")
9
+ .replace(/\//g, "_");
10
+ return `header.${payload}.sig`;
11
+ };
12
+
13
+ const createMockTokenManager = (overrides: Record<string, any> = {}) => ({
14
+ getToken: vi.fn().mockReturnValue(createJwt(Math.floor(Date.now() / 1000) + 3600)),
15
+ setInitialTokens: vi.fn(),
16
+ refreshToken: vi.fn().mockResolvedValue("new_token"),
17
+ ...overrides,
18
+ });
19
+
20
+ const createMockActivityMonitor = (lastActivity = Date.now()) => ({
21
+ start: vi.fn(),
22
+ stop: vi.fn(),
23
+ getLastActivityTimestamp: vi.fn().mockReturnValue(lastActivity),
24
+ });
25
+
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
+ describe("SessionRefreshTimer", () => {
35
+ let mockBroker: ReturnType<typeof createMockBroker>;
36
+
37
+ beforeEach(() => {
38
+ vi.useFakeTimers();
39
+ mockBroker = createMockBroker();
40
+ });
41
+
42
+ afterEach(() => {
43
+ vi.useRealTimers();
44
+ vi.clearAllMocks();
45
+ });
46
+
47
+ it("should skip tick when there is no recent activity", async () => {
48
+ const tokenManager = createMockTokenManager({
49
+ getToken: vi.fn().mockReturnValue(createJwt(Math.floor(Date.now() / 1000) + 60)),
50
+ });
51
+ const timer = new SessionRefreshTimerImpl(
52
+ tokenManager,
53
+ createMockActivityMonitor(0),
54
+ mockBroker,
55
+ );
56
+
57
+ timer.start();
58
+ await vi.advanceTimersByTimeAsync(30_000);
59
+
60
+ expect(tokenManager.refreshToken).not.toHaveBeenCalled();
61
+ });
62
+
63
+ it("should skip tick when token is not near expiry", async () => {
64
+ const tokenManager = createMockTokenManager({
65
+ getToken: vi.fn().mockReturnValue(createJwt(Math.floor(Date.now() / 1000) + 3600)),
66
+ });
67
+ const timer = new SessionRefreshTimerImpl(
68
+ tokenManager,
69
+ createMockActivityMonitor(),
70
+ mockBroker,
71
+ );
72
+
73
+ timer.start();
74
+ await vi.advanceTimersByTimeAsync(30_000);
75
+
76
+ expect(tokenManager.refreshToken).not.toHaveBeenCalled();
77
+ });
78
+
79
+ it("should refresh when token is near expiry and user is active", async () => {
80
+ const tokenManager = createMockTokenManager({
81
+ getToken: vi.fn().mockReturnValue(createJwt(Math.floor(Date.now() / 1000) + 60)),
82
+ });
83
+ const timer = new SessionRefreshTimerImpl(
84
+ tokenManager,
85
+ createMockActivityMonitor(),
86
+ mockBroker,
87
+ );
88
+
89
+ timer.start();
90
+ await vi.advanceTimersByTimeAsync(30_000);
91
+
92
+ expect(tokenManager.refreshToken).toHaveBeenCalledTimes(1);
93
+ });
94
+
95
+ it("should refresh when token is already expired and user is active", async () => {
96
+ const tokenManager = createMockTokenManager({
97
+ getToken: vi.fn().mockReturnValue(createJwt(Math.floor(Date.now() / 1000) - 60)),
98
+ });
99
+ const timer = new SessionRefreshTimerImpl(
100
+ tokenManager,
101
+ createMockActivityMonitor(),
102
+ mockBroker,
103
+ );
104
+
105
+ timer.start();
106
+ await vi.advanceTimersByTimeAsync(30_000);
107
+
108
+ expect(tokenManager.refreshToken).toHaveBeenCalledTimes(1);
109
+ });
110
+
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
+ it("should not trigger concurrent refreshes", async () => {
129
+ let resolveRefresh!: () => void;
130
+ const tokenManager = createMockTokenManager({
131
+ getToken: vi.fn().mockReturnValue(createJwt(Math.floor(Date.now() / 1000) + 60)),
132
+ refreshToken: vi.fn().mockImplementation(
133
+ () => new Promise((resolve) => { resolveRefresh = () => resolve("new_token"); }),
134
+ ),
135
+ });
136
+ const timer = new SessionRefreshTimerImpl(
137
+ tokenManager,
138
+ createMockActivityMonitor(),
139
+ mockBroker,
140
+ );
141
+
142
+ timer.start();
143
+ await vi.advanceTimersByTimeAsync(30_000);
144
+ await vi.advanceTimersByTimeAsync(30_000);
145
+
146
+ resolveRefresh();
147
+ await Promise.resolve();
148
+
149
+ expect(tokenManager.refreshToken).toHaveBeenCalledTimes(1);
150
+ });
151
+ });
@@ -0,0 +1,79 @@
1
+ import { jwtDecode } from "jwt-decode";
2
+ import { ActivityMonitor } from "../activity-monitor/activity-monitor";
3
+ import { BROKER_EVENTS } from "../broker/broker-events";
4
+ import { PrimariaBroker } from "../broker/primaria-broker";
5
+ import { TokenManager } from "../token-manager/token-manager";
6
+
7
+ const CHECK_INTERVAL_MS = 30_000;
8
+ const REFRESH_THRESHOLD_S = 120;
9
+ const ACTIVITY_WINDOW_MS = 15 * 60 * 1000;
10
+
11
+ export interface SessionRefreshTimer {
12
+ start: () => void;
13
+ stop: () => void;
14
+ }
15
+
16
+ export class SessionRefreshTimerImpl implements SessionRefreshTimer {
17
+ private intervalId: ReturnType<typeof setInterval> | null = null;
18
+ private isRefreshing = false;
19
+
20
+ constructor(
21
+ private readonly tokenManager: TokenManager,
22
+ private readonly activityMonitor: ActivityMonitor,
23
+ private readonly broker: PrimariaBroker,
24
+ ) {}
25
+
26
+ start = () => {
27
+ this.intervalId = setInterval(this.checkAndRefresh, CHECK_INTERVAL_MS);
28
+ };
29
+
30
+ stop = () => {
31
+ if (this.intervalId !== null) {
32
+ clearInterval(this.intervalId);
33
+ this.intervalId = null;
34
+ }
35
+ };
36
+
37
+ private getTokenExpiry = (): number => {
38
+ try {
39
+ const { exp } = jwtDecode(this.tokenManager.getToken()) as { exp?: number };
40
+ return exp ?? 0;
41
+ } catch {
42
+ return 0;
43
+ }
44
+ };
45
+
46
+ private checkAndRefresh = async () => {
47
+ const now = Date.now();
48
+
49
+ // Skip if the user hasn't interacted in the last ACTIVITY_WINDOW_MS.
50
+ // Avoids keeping sessions alive for unattended tabs.
51
+ if (now - this.activityMonitor.getLastActivityTimestamp() > ACTIVITY_WINDOW_MS) return;
52
+
53
+ // Skip if a refresh is already in flight (this interval can fire again before the previous awaits).
54
+ if (this.isRefreshing) return;
55
+
56
+ const exp = this.getTokenExpiry();
57
+
58
+ // Skip if the token can't be decoded (e.g. not yet initialized).
59
+ if (exp === 0) return;
60
+
61
+ // exp is in seconds, now is in milliseconds — compare in the same unit.
62
+ if (exp - now / 1000 <= REFRESH_THRESHOLD_S) {
63
+ this.isRefreshing = true;
64
+ try {
65
+ await this.tokenManager.refreshToken();
66
+ } catch {
67
+ this.broker.publish(BROKER_EVENTS.shell.refreshTokenFailed, {});
68
+ } finally {
69
+ this.isRefreshing = false;
70
+ }
71
+ }
72
+ };
73
+ }
74
+
75
+ export const createSessionRefreshTimer = (
76
+ tokenManager: TokenManager,
77
+ activityMonitor: ActivityMonitor,
78
+ broker: PrimariaBroker,
79
+ ): SessionRefreshTimer => new SessionRefreshTimerImpl(tokenManager, activityMonitor, broker);
@@ -3,10 +3,10 @@ import axios from "axios";
3
3
 
4
4
  vi.mock("axios");
5
5
 
6
- const access_token = 'test_access_token';
7
- const refresh_token = 'test_refresh_token';
8
- const new_access_token = 'new_access_token';
9
- const new_refresh_token = 'new_refresh_token';
6
+ const access_token = "test_access_token";
7
+ const refresh_token = "test_refresh_token";
8
+ const new_access_token = "new_access_token";
9
+ const new_refresh_token = "new_refresh_token";
10
10
 
11
11
  describe("TokenManager", () => {
12
12
  let mockLocation: Partial<Location>;
@@ -14,14 +14,12 @@ describe("TokenManager", () => {
14
14
  let createTokenManager: any;
15
15
 
16
16
  beforeEach(async () => {
17
- // Reset module state by requiring a fresh instance
18
17
  vi.resetModules();
19
18
  mockLocation = {
20
- search: `?access_token=${access_token}&refresh_token=${refresh_token}`
19
+ search: `?access_token=${access_token}&refresh_token=${refresh_token}`,
21
20
  };
22
- vi.spyOn(window, 'location', 'get').mockReturnValue(mockLocation as Location);
21
+ vi.spyOn(window, "location", "get").mockReturnValue(mockLocation as Location);
23
22
 
24
- // Import fresh module
25
23
  const module = await import("./token-manager");
26
24
  TokenManagerImpl = module.TokenManagerImpl;
27
25
  createTokenManager = module.createTokenManager;
@@ -34,23 +32,18 @@ describe("TokenManager", () => {
34
32
  describe("getToken", () => {
35
33
  it("should initialize token from URL params on first call", () => {
36
34
  const tokenManager = new TokenManagerImpl();
37
- const token = tokenManager.getToken();
38
- expect(token).toBe(access_token);
35
+ expect(tokenManager.getToken()).toBe(access_token);
39
36
  });
40
37
 
41
38
  it("should return empty string if URL params are missing", () => {
42
39
  mockLocation.search = "";
43
40
  const tokenManager = new TokenManagerImpl();
44
- const token = tokenManager.getToken();
45
- expect(token).toBe("");
41
+ expect(tokenManager.getToken()).toBe("");
46
42
  });
47
43
 
48
44
  it("should return the same token on subsequent calls", () => {
49
45
  const tokenManager = new TokenManagerImpl();
50
- const token1 = tokenManager.getToken();
51
- const token2 = tokenManager.getToken();
52
- expect(token1).toBe(token2);
53
- expect(token1).toBe(access_token);
46
+ expect(tokenManager.getToken()).toBe(tokenManager.getToken());
54
47
  });
55
48
  });
56
49
 
@@ -64,67 +57,58 @@ describe("TokenManager", () => {
64
57
  it("should throw error if tokens are already initialized", () => {
65
58
  const tokenManager = new TokenManagerImpl();
66
59
  tokenManager.setInitialTokens("manual_access", "manual_refresh");
67
- expect(() => tokenManager.setInitialTokens("another_access", "another_refresh"))
68
- .toThrow("Token already initialized");
60
+ expect(() => tokenManager.setInitialTokens("another_access", "another_refresh")).toThrow(
61
+ "Token already initialized",
62
+ );
69
63
  });
70
64
 
71
65
  it("should throw error if tokens were already initialized from URL", () => {
72
66
  const tokenManager = new TokenManagerImpl();
73
- tokenManager.getToken(); // Initialize from URL
74
- expect(() => tokenManager.setInitialTokens("manual_access", "manual_refresh"))
75
- .toThrow("Token already initialized");
67
+ tokenManager.getToken();
68
+ expect(() => tokenManager.setInitialTokens("manual_access", "manual_refresh")).toThrow(
69
+ "Token already initialized",
70
+ );
76
71
  });
77
72
  });
78
73
 
79
74
  describe("refreshToken", () => {
80
75
  it("should refresh token successfully", async () => {
81
76
  vi.mocked(axios.post).mockResolvedValue({
82
- data: {
83
- access_token: new_access_token,
84
- refresh_token: new_refresh_token
85
- }
77
+ data: { access_token: new_access_token, refresh_token: new_refresh_token },
86
78
  });
87
79
 
88
80
  const tokenManager = new TokenManagerImpl();
89
81
  tokenManager.setInitialTokens(access_token, refresh_token);
90
82
 
91
- const refreshedToken = await tokenManager.refreshToken();
83
+ const refreshed = await tokenManager.refreshToken();
92
84
 
93
- expect(axios.post).toHaveBeenCalledWith('/api/token/refresh', { token: refresh_token });
94
- expect(refreshedToken).toBe(new_access_token);
85
+ expect(axios.post).toHaveBeenCalledWith("/api/token/refresh", { token: refresh_token });
86
+ expect(refreshed).toBe(new_access_token);
95
87
  expect(tokenManager.getToken()).toBe(new_access_token);
96
88
  });
97
89
 
98
90
  it("should initialize tokens from URL before refreshing if not initialized", async () => {
99
91
  vi.mocked(axios.post).mockResolvedValue({
100
- data: {
101
- access_token: new_access_token,
102
- refresh_token: new_refresh_token
103
- }
92
+ data: { access_token: new_access_token, refresh_token: new_refresh_token },
104
93
  });
105
94
 
106
95
  const tokenManager = new TokenManagerImpl();
107
96
  await tokenManager.refreshToken();
108
97
 
109
- expect(axios.post).toHaveBeenCalledWith('/api/token/refresh', { token: refresh_token });
110
- expect(tokenManager.getToken()).toBe(new_access_token);
98
+ expect(axios.post).toHaveBeenCalledWith("/api/token/refresh", { token: refresh_token });
111
99
  });
112
100
 
113
101
  it("should throw error if refresh response doesn't contain access_token", async () => {
114
- vi.mocked(axios.post).mockResolvedValue({
115
- data: {}
116
- });
102
+ vi.mocked(axios.post).mockResolvedValue({ data: {} });
117
103
 
118
104
  const tokenManager = new TokenManagerImpl();
119
105
  tokenManager.setInitialTokens(access_token, refresh_token);
120
106
 
121
- await expect(tokenManager.refreshToken())
122
- .rejects.toThrow("Invalid refresh token response");
107
+ await expect(tokenManager.refreshToken()).rejects.toThrow("Invalid refresh token response");
123
108
  });
124
109
 
125
110
  it("should handle axios errors", async () => {
126
- const error = new Error("Network error");
127
- vi.mocked(axios.post).mockRejectedValue(error);
111
+ vi.mocked(axios.post).mockRejectedValue(new Error("Network error"));
128
112
 
129
113
  const tokenManager = new TokenManagerImpl();
130
114
  tokenManager.setInitialTokens(access_token, refresh_token);
@@ -135,9 +119,7 @@ describe("TokenManager", () => {
135
119
 
136
120
  describe("createTokenManager", () => {
137
121
  it("should return a singleton instance", () => {
138
- const tokenManager1 = createTokenManager();
139
- const tokenManager2 = createTokenManager();
140
- expect(tokenManager1).toBe(tokenManager2);
122
+ expect(createTokenManager()).toBe(createTokenManager());
141
123
  });
142
124
  });
143
125
  });
@@ -9,14 +9,14 @@ export interface TokenManager {
9
9
  let token: string;
10
10
  let refreshToken: string;
11
11
  let tokenInitialized = false;
12
+
12
13
  export class TokenManagerImpl implements TokenManager {
13
14
  getUrlParams = (): URLSearchParams => {
14
15
  return new URLSearchParams(window.location.search);
15
16
  };
16
17
 
17
18
  private initToken = () => {
18
- if (tokenInitialized)
19
- throw new Error("Token already initialized");
19
+ if (tokenInitialized) throw new Error("Token already initialized");
20
20
  tokenInitialized = true;
21
21
  const searchString = this.getUrlParams();
22
22
  token = searchString.get("access_token") || "";
@@ -25,37 +25,32 @@ export class TokenManagerImpl implements TokenManager {
25
25
  };
26
26
 
27
27
  setInitialTokens = (access_token: string, refresh_token: string) => {
28
- if (tokenInitialized)
29
- throw new Error("Token already initialized");
28
+ if (tokenInitialized) throw new Error("Token already initialized");
30
29
  token = access_token;
31
30
  refreshToken = refresh_token;
32
31
  tokenInitialized = true;
33
32
  };
34
33
 
35
34
  getToken = () => {
36
- if (!tokenInitialized)
37
- this.initToken();
35
+ if (!tokenInitialized) this.initToken();
38
36
  return token;
39
37
  };
40
38
 
41
39
  refreshToken = async () => {
42
- if (!tokenInitialized) {
43
- this.initToken();
44
- }
45
- const response = await axios.post('/api/token/refresh', {token: refreshToken});
46
- const {access_token, refresh_token} = response.data;
47
- if(!access_token){
48
- throw new Error("Invalid refresh token response");
49
- }
40
+ 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");
50
44
  token = access_token;
51
45
  refreshToken = refresh_token;
52
46
  return token;
53
47
  };
54
48
  }
55
- let tokenManager;
56
- export const createTokenManager = () => {
57
- if(tokenManager) return tokenManager;
49
+
50
+ let tokenManager: TokenManagerImpl;
51
+
52
+ export const createTokenManager = () => {
53
+ if (tokenManager) return tokenManager;
58
54
  tokenManager = new TokenManagerImpl();
59
- //tokenManager.initToken();
60
55
  return tokenManager;
61
- }
56
+ };
@@ -1,16 +1,17 @@
1
1
  import { PrimariaShell } from "./UI/components/primaria-shell/primaria-shell";
2
- import { shellApi } from "./api/api";
2
+ import { activityMonitor, sessionRefreshTimer, shellApi } from "./api/api";
3
3
  import { EcapContext } from "./api/context-manager/context-manager";
4
4
  import { useFeatures } from "./features/bootstrapper";
5
5
  import { useLocalization } from "./locales";
6
6
  import { useUI } from "./UI/bootstrapper";
7
7
 
8
8
  export const initializeShell = (hostAppElement: HTMLElement, ecapContext?: EcapContext) => {
9
- // Initialize the context manager with ECAP parameters
10
9
  if (ecapContext) {
11
10
  (shellApi.contextManager as any).initializeContext(ecapContext);
12
11
  }
13
12
 
13
+ activityMonitor.start();
14
+ sessionRefreshTimer.start();
14
15
  useLocalization(shellApi);
15
16
  useUI();
16
17
  useFeatures(shellApi);
package/src/disposer.ts CHANGED
@@ -1,9 +1,11 @@
1
- import { shellApi } from "./api/api";
1
+ import { activityMonitor, sessionRefreshTimer, shellApi } from "./api/api";
2
2
  import { disposeFeatures } from "./features/bootstrapper";
3
3
  import { ExitShellPayload } from "./features/exit/request";
4
4
  export const disposeShell = () => {
5
5
  disposeFeatures(shellApi);
6
6
  shellApi.interactionService.dispose();
7
+ sessionRefreshTimer.stop();
8
+ activityMonitor.stop();
7
9
  };
8
10
 
9
11
  export const raiseCloseEvent = () => {