@uxland/primary-shell 7.8.0 → 7.9.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.
@@ -1,10 +1,12 @@
1
1
  export interface TokenManager {
2
+ setInitialTokens: (access_token: string, refresh_token: string) => void;
2
3
  getToken: () => string;
3
4
  refreshToken: () => Promise<string>;
4
5
  }
5
6
  export declare class TokenManagerImpl implements TokenManager {
6
7
  getUrlParams: () => URLSearchParams;
7
- initToken: () => string;
8
+ private initToken;
9
+ setInitialTokens: (access_token: string, refresh_token: string) => void;
8
10
  getToken: () => string;
9
11
  refreshToken: () => Promise<string>;
10
12
  }
@@ -1,6 +1,17 @@
1
- import { PluginDefinition, Plugin as PluginType } from '@uxland/harmonix';
1
+ import { PluginDefinition, Plugin as PluginType, BootstrappedPlugin } from '@uxland/harmonix';
2
2
  import { PrimariaApi } from './api/api';
3
3
  export type { PluginDefinition, PluginInfo } from '@uxland/harmonix';
4
- export declare const bootstrapPlugins: (plugins: PluginDefinition[]) => Promise<void>;
4
+ export interface PrimariaPlugin extends Plugin {
5
+ startup: (api: PrimariaApi, startupParams: any) => Promise<void>;
6
+ }
7
+ export interface PrimariaStartupPlugin {
8
+ pluginId: string;
9
+ params: any;
10
+ }
11
+ interface PrimariaBootstrappedPlugin extends BootstrappedPlugin<PrimariaApi> {
12
+ importedPlugin: PrimariaPlugin;
13
+ }
14
+ export declare const bootstrapPlugins: (plugins: PluginDefinition[], startup?: PrimariaStartupPlugin) => Promise<void>;
15
+ export declare const handleStartupPlugin: (plugins: PrimariaBootstrappedPlugin[], startupPlugin?: PrimariaStartupPlugin) => void;
5
16
  export declare const disposePlugins: () => Promise<void[]>;
6
17
  export type Plugin = PluginType<PrimariaApi>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uxland/primary-shell",
3
- "version": "7.8.0",
3
+ "version": "7.9.0",
4
4
  "description": "Primaria Shell",
5
5
  "author": "UXLand <dev@uxland.es>",
6
6
  "homepage": "https://github.com/uxland/harmonix/tree/app#readme",
@@ -1,17 +1,143 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
- import { createTokenManager } from "./token-manager";
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import axios from "axios";
3
3
 
4
- const access_token = 'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJidFZxNnRMWGpmcXdzbm5MR2FXMXdhdU9McDNiTmY4bWZ3Rm1SZ0lBS2VJIn0.eyJleHAiOjE3Mzg2NjcxODIsImlhdCI6MTczODY2NjI4MiwianRpIjoiY2I0M2M2ZmItY2MyMS00M2Y2LTk5M2UtNDM3ZTJjNDU3YWMzIiwiaXNzIjoiaHR0cHM6Ly9wcmVwcm9kdWNjaW8ucGRzLmhlcy5jYXRzYWx1dC5nZW5jYXQuY2F0L2F1dGgvcmVhbG1zL0hFUyIsInN1YiI6Ijk1OGUyZWQ5LTJkNmUtNDZjOC1hOTE5LTU5MDQ0ZTYwMzVjMiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImV0Yy1jY2YtcHJlIiwic2Vzc2lvbl9zdGF0ZSI6ImU3MzIzYWIyLTU3MDktNGY1ZS05ZGU4LWU1MzM3ZTBhOTMxNiIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLWhlcyIsIlJPTEVfSEVTX0VUQyIsIlJPTEVfSEVTX0xBTkRJTkciXX0sInNjb3BlIjoiIiwic2lkIjoiZTczMjNhYjItNTcwOS00ZjVlLTlkZTgtZTUzMzdlMGE5MzE2IiwiY2xpZW50SG9zdCI6IjEwLjUzLjI1NC4xNTAiLCJhY2Nlc3NfcnVsZXMiOnt9LCJhY2Nlc3NfaW5mbyI6eyJtb2R1bGVfY29kZSI6IkEwMDEiLCJyb2xlX3R5cGUiOiJOT1JNIiwidHJhY2VfdXNlcl9pZCI6IlVzZXJfSUQiLCJjZW50ZXJfdHlwZSI6IkUiLCJ1cF9jb2RlIjoiMDc3MzMiLCJ0cmFjZV91c2VyX2dpdmVuX25hbWUiOiJHaXZlbiBOYW1lIiwidHJhY2VfdXNlcl9mYW1pbHlfbmFtZSI6IkZhbWlseSBOYW1lIiwidXNlcl90eXBlIjoiQURNIiwiY2VudGVyX2NvZGUiOiJFMDg1ODY5NjMiLCJwcm9mZXNzaW9uYWxfY2F0ZWdvcnkiOiIzMDkzNDMwMDYiLCJzZXJ2aWNlX2NvZGUiOiI1UzA4OSIsImVwX2NvZGUiOiIwMjA4IiwiaWRlbnRpZmllciI6W3sidHlwZSI6IkROSSIsInZhbHVlIjoiNzMyODgyMTlBIn0seyJ0eXBlIjoiTlVNQ09MIiwidmFsdWUiOiIyIn1dLCJhbHRfaWRlbnRpZmllciI6W3sidHlwZSI6Ik1QSSIsInZhbHVlIjoiMDYyMWNmN2QtN2Q2My00OWVjLTgwMDYtOGMwNTY5MmVkMzc3In1dfSwiY2xpZW50QWRkcmVzcyI6IjEwLjUzLjI1NC4xNTAiLCJjbGllbnRfaWQiOiJldGMtY2NmLXByZSJ9.WYF6VvIVqzW71MdOcvRJnKNBEe8BoD43Ql_hyGyqBC1wbNDNcbqucX-2wPbSOpHaHv5iogYNTVp1eEkHvrnryQ4koHXf4U3inTxoZR94qAzIt8XGgBVxpYlArc-XrFX1FAwkdfsNmYE_L6G7q5wfzaP0y1YrFH6u9TPTBl1w2us8O8xKPB6B652NUphNHFWmPWv6t6Zq97sjHPIHMZsDeBdolK5RlG5J3u8K-quJeKLByZhA2kYAJMGyCzR6jLLrup5w-WdYNJRyGopeFSDVp-lECmaiYIXmhQOJzzJQqhARrwYN8ZxqTOyD5u24HOB7Q1ZCMnAp4vAL6OphWyRZuw';
5
- const refresh_token = 'eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIwMjUzZGM1MC1hY2FmLTQ5ZDctYTYzNi0xN2NkMTlmOWEwOTAifQ.eyJleHAiOjE3Mzg2NjgwODIsImlhdCI6MTczODY2NjI4MiwianRpIjoiZWM5OGE2OWMtMDJkNS00Yjc0LWIwZWYtNzcwMGU4OTc3YWY1IiwiaXNzIjoiaHR0cHM6Ly9wcmVwcm9kdWNjaW8ucGRzLmhlcy5jYXRzYWx1dC5nZW5jYXQuY2F0L2F1dGgvcmVhbG1zL0hFUyIsImF1ZCI6Imh0dHBzOi8vcHJlcHJvZHVjY2lvLnBkcy5oZXMuY2F0c2FsdXQuZ2VuY2F0LmNhdC9hdXRoL3JlYWxtcy9IRVMiLCJzdWIiOiI5NThlMmVkOS0yZDZlLTQ2YzgtYTkxOS01OTA0NGU2MDM1YzIiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiZXRjLWNjZi1wcmUiLCJzZXNzaW9uX3N0YXRlIjoiZTczMjNhYjItNTcwOS00ZjVlLTlkZTgtZTUzMzdlMGE5MzE2Iiwic2NvcGUiOiIiLCJzaWQiOiJlNzMyM2FiMi01NzA5LTRmNWUtOWRlOC1lNTMzN2UwYTkzMTYifQ.KOO0Ulbed4640-obEr3Xg8qavGeXhU25o6ZUfsq0SoQpzMjhcx8GTgOH-PiIR88ksrjtTnpbmzw2D29xuugv4A';
4
+ vi.mock("axios");
6
5
 
7
- describe("Token Manager test", () => {
8
- beforeEach(() => {
9
- vi.spyOn(window, 'location', 'get').mockReturnValue({
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
+
11
+ describe("TokenManager", () => {
12
+ let mockLocation: Partial<Location>;
13
+ let TokenManagerImpl: any;
14
+ let createTokenManager: any;
15
+
16
+ beforeEach(async () => {
17
+ // Reset module state by requiring a fresh instance
18
+ vi.resetModules();
19
+ mockLocation = {
10
20
  search: `?access_token=${access_token}&refresh_token=${refresh_token}`
11
- } as unknown as Location);
21
+ };
22
+ vi.spyOn(window, 'location', 'get').mockReturnValue(mockLocation as Location);
23
+
24
+ // Import fresh module
25
+ const module = await import("./token-manager");
26
+ TokenManagerImpl = module.TokenManagerImpl;
27
+ createTokenManager = module.createTokenManager;
28
+ });
29
+
30
+ afterEach(() => {
31
+ vi.clearAllMocks();
32
+ });
33
+
34
+ describe("getToken", () => {
35
+ it("should initialize token from URL params on first call", () => {
36
+ const tokenManager = new TokenManagerImpl();
37
+ const token = tokenManager.getToken();
38
+ expect(token).toBe(access_token);
39
+ });
40
+
41
+ it("should return empty string if URL params are missing", () => {
42
+ mockLocation.search = "";
43
+ const tokenManager = new TokenManagerImpl();
44
+ const token = tokenManager.getToken();
45
+ expect(token).toBe("");
46
+ });
47
+
48
+ it("should return the same token on subsequent calls", () => {
49
+ 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);
54
+ });
55
+ });
56
+
57
+ describe("setInitialTokens", () => {
58
+ it("should set tokens manually", () => {
59
+ const tokenManager = new TokenManagerImpl();
60
+ tokenManager.setInitialTokens("manual_access", "manual_refresh");
61
+ expect(tokenManager.getToken()).toBe("manual_access");
62
+ });
63
+
64
+ it("should throw error if tokens are already initialized", () => {
65
+ const tokenManager = new TokenManagerImpl();
66
+ tokenManager.setInitialTokens("manual_access", "manual_refresh");
67
+ expect(() => tokenManager.setInitialTokens("another_access", "another_refresh"))
68
+ .toThrow("Token already initialized");
69
+ });
70
+
71
+ it("should throw error if tokens were already initialized from URL", () => {
72
+ const tokenManager = new TokenManagerImpl();
73
+ tokenManager.getToken(); // Initialize from URL
74
+ expect(() => tokenManager.setInitialTokens("manual_access", "manual_refresh"))
75
+ .toThrow("Token already initialized");
76
+ });
77
+ });
78
+
79
+ describe("refreshToken", () => {
80
+ it("should refresh token successfully", async () => {
81
+ vi.mocked(axios.post).mockResolvedValue({
82
+ data: {
83
+ access_token: new_access_token,
84
+ refresh_token: new_refresh_token
85
+ }
86
+ });
87
+
88
+ const tokenManager = new TokenManagerImpl();
89
+ tokenManager.setInitialTokens(access_token, refresh_token);
90
+
91
+ const refreshedToken = await tokenManager.refreshToken();
92
+
93
+ expect(axios.post).toHaveBeenCalledWith('/api/token/refresh', { token: refresh_token });
94
+ expect(refreshedToken).toBe(new_access_token);
95
+ expect(tokenManager.getToken()).toBe(new_access_token);
96
+ });
97
+
98
+ it("should initialize tokens from URL before refreshing if not initialized", async () => {
99
+ vi.mocked(axios.post).mockResolvedValue({
100
+ data: {
101
+ access_token: new_access_token,
102
+ refresh_token: new_refresh_token
103
+ }
104
+ });
105
+
106
+ const tokenManager = new TokenManagerImpl();
107
+ await tokenManager.refreshToken();
108
+
109
+ expect(axios.post).toHaveBeenCalledWith('/api/token/refresh', { token: refresh_token });
110
+ expect(tokenManager.getToken()).toBe(new_access_token);
111
+ });
112
+
113
+ it("should throw error if refresh response doesn't contain access_token", async () => {
114
+ vi.mocked(axios.post).mockResolvedValue({
115
+ data: {}
116
+ });
117
+
118
+ const tokenManager = new TokenManagerImpl();
119
+ tokenManager.setInitialTokens(access_token, refresh_token);
120
+
121
+ await expect(tokenManager.refreshToken())
122
+ .rejects.toThrow("Invalid refresh token response");
123
+ });
124
+
125
+ it("should handle axios errors", async () => {
126
+ const error = new Error("Network error");
127
+ vi.mocked(axios.post).mockRejectedValue(error);
128
+
129
+ const tokenManager = new TokenManagerImpl();
130
+ tokenManager.setInitialTokens(access_token, refresh_token);
131
+
132
+ await expect(tokenManager.refreshToken()).rejects.toThrow("Network error");
133
+ });
12
134
  });
13
- it("should return initial token", () => {
14
- const tokenManager = createTokenManager();
15
- expect(tokenManager.getToken()).toBe(access_token);
135
+
136
+ describe("createTokenManager", () => {
137
+ it("should return a singleton instance", () => {
138
+ const tokenManager1 = createTokenManager();
139
+ const tokenManager2 = createTokenManager();
140
+ expect(tokenManager1).toBe(tokenManager2);
141
+ });
16
142
  });
17
143
  });
@@ -1,43 +1,61 @@
1
1
  import axios from "axios";
2
2
 
3
3
  export interface TokenManager {
4
+ setInitialTokens: (access_token: string, refresh_token: string) => void;
4
5
  getToken: () => string;
5
6
  refreshToken: () => Promise<string>;
6
7
  }
7
8
 
8
9
  let token: string;
9
10
  let refreshToken: string;
11
+ let tokenInitialized = false;
10
12
  export class TokenManagerImpl implements TokenManager {
11
13
  getUrlParams = (): URLSearchParams => {
12
14
  return new URLSearchParams(window.location.search);
13
15
  };
14
16
 
15
- initToken = () => {
17
+ private initToken = () => {
18
+ if (tokenInitialized)
19
+ throw new Error("Token already initialized");
20
+ tokenInitialized = true;
16
21
  const searchString = this.getUrlParams();
17
22
  token = searchString.get("access_token") || "";
18
23
  refreshToken = searchString.get("refresh_token") || "";
19
24
  return token;
20
25
  };
21
26
 
27
+ setInitialTokens = (access_token: string, refresh_token: string) => {
28
+ if (tokenInitialized)
29
+ throw new Error("Token already initialized");
30
+ token = access_token;
31
+ refreshToken = refresh_token;
32
+ tokenInitialized = true;
33
+ };
34
+
22
35
  getToken = () => {
36
+ if (!tokenInitialized)
37
+ this.initToken();
23
38
  return token;
24
39
  };
25
40
 
26
41
  refreshToken = async () => {
27
- const response = await axios.post('/api/token/refresh', {token: refreshToken});
28
- const {access_token, refresh_token} = response.data;
29
- if(!access_token){
30
- throw new Error("Invalid refresh token response");
31
- }
32
- token = access_token;
33
- refreshToken = refresh_token;
34
- return token;
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
+ }
50
+ token = access_token;
51
+ refreshToken = refresh_token;
52
+ return token;
35
53
  };
36
54
  }
37
55
  let tokenManager;
38
56
  export const createTokenManager = () => {
39
57
  if(tokenManager) return tokenManager;
40
58
  tokenManager = new TokenManagerImpl();
41
- tokenManager.initToken();
59
+ //tokenManager.initToken();
42
60
  return tokenManager;
43
61
  }
@@ -15,7 +15,20 @@ import {
15
15
  } from "../../../plugins/admin-clinical-monitoring/src/plugin";
16
16
  import { PrimariaApi, primariaApiFactory, shellApi } from "./api/api";
17
17
 
18
- let bootstrappedPlugins = [] as BootstrappedPlugin[];
18
+ let bootstrappedPlugins = [] as BootstrappedPlugin<PrimariaApi>[];
19
+
20
+ export interface PrimariaPlugin extends Plugin{
21
+ startup: (api: PrimariaApi, startupParams: any) => Promise<void>;
22
+ }
23
+
24
+ export interface PrimariaStartupPlugin{
25
+ pluginId: string;
26
+ params: any;
27
+ }
28
+
29
+ interface PrimariaBootstrappedPlugin extends BootstrappedPlugin<PrimariaApi>{
30
+ importedPlugin: PrimariaPlugin;
31
+ }
19
32
 
20
33
  const commonPlugins: PluginDefinition[] = [
21
34
  {
@@ -57,15 +70,24 @@ const getPluginsByUserRole = (isUserRoleAdministrative: boolean) => {
57
70
  return commonPlugins.concat(doctorInternalPlugins);
58
71
  };
59
72
 
60
- export const bootstrapPlugins = async (plugins: PluginDefinition[]) => {
73
+ export const bootstrapPlugins = async (plugins: PluginDefinition[], startup?: PrimariaStartupPlugin) => {
61
74
  const isUserRoleAdministrative = shellApi.userManager.isUserRoleAdministrative();
62
75
  const internalPlugins = getPluginsByUserRole(isUserRoleAdministrative);
63
76
  const finalPlugins = internalPlugins.concat(plugins || []);
64
- bootstrappedPlugins = await pluginBootstrapper(finalPlugins, primariaApiFactory);
77
+ bootstrappedPlugins = await pluginBootstrapper(finalPlugins, primariaApiFactory) as BootstrappedPlugin<PrimariaApi>[] ;
78
+ handleStartupPlugin(bootstrappedPlugins as PrimariaBootstrappedPlugin[], startup);
79
+ };
80
+
81
+ export const handleStartupPlugin = (plugins: PrimariaBootstrappedPlugin[], startupPlugin?: PrimariaStartupPlugin) => {
82
+ if (plugins && plugins.length > 0 && startupPlugin?.pluginId) {
83
+ const plugin = plugins.find((plugin: PrimariaBootstrappedPlugin) => plugin?.apiInstance.pluginInfo.pluginId === startupPlugin.pluginId);
84
+ if(plugin?.importedPlugin?.startup)
85
+ plugin?.importedPlugin?.startup?.(plugin.apiInstance, startupPlugin.params);
86
+ }
65
87
  };
66
88
 
67
89
  export const disposePlugins = async () => {
68
- return Promise.all(bootstrappedPlugins.map((plugin: BootstrappedPlugin) => plugin?.dispose()));
90
+ return Promise.all(bootstrappedPlugins.map((plugin: PrimariaBootstrappedPlugin) => plugin?.dispose()));
69
91
  };
70
92
 
71
93
  export type Plugin = PluginType<PrimariaApi>;