@intellegens/cornerstone-client 0.0.2 → 0.0.4
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.
- package/README.md +46 -16
- package/package.json +1 -1
- package/services/api/index.d.ts +66 -0
- package/services/api/index.js +139 -0
- package/services/auth/index.d.ts +21 -18
- package/services/auth/index.js +34 -26
- package/services/auth/types/user-info.d.ts +0 -4
- package/services/auth/types/user.d.ts +0 -4
- package/services/config/index.d.ts +27 -17
- package/services/config/index.js +98 -47
- package/services/index.d.ts +1 -1
- package/services/index.js +1 -1
package/README.md
CHANGED
|
@@ -28,46 +28,76 @@ http://localhost:3000
|
|
|
28
28
|
|
|
29
29
|
## API reference
|
|
30
30
|
|
|
31
|
-
###
|
|
31
|
+
### API Service
|
|
32
32
|
|
|
33
|
-
This service provides way to load configuration for a Cornerstone client application.
|
|
33
|
+
This service provides way to load API configuration for a Cornerstone client application.
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
|
-
import {
|
|
37
|
-
|
|
36
|
+
import { ApiService } from '@intellegens/cornerstone-client';
|
|
37
|
+
const apiService = new ApiService();
|
|
38
|
+
await apiService.initialize();
|
|
38
39
|
```
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
or use the existing singleton instance
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { apiService } from '@intellegens/cornerstone-client';
|
|
45
|
+
await apiService.initialize();
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
#### API base URL detection
|
|
41
49
|
|
|
42
50
|
Detected by checking the `CORNERSTONE-API-BASEURL` response header and the `websettings.json` file.
|
|
43
51
|
|
|
44
|
-
- First, it checks the response headers of the current URL for the
|
|
52
|
+
- First, it checks the response headers of the current URL for the `CORNERSTONE-API-BASEURL` header.
|
|
45
53
|
- If the header is not found, it attempts to load the `websettings.json` file and looks for an `api` field.
|
|
46
54
|
- If no API base URL is found, it defaults to `undefined`.
|
|
47
55
|
|
|
56
|
+
```ts
|
|
57
|
+
const fetchUrl = `${await apiService.getApiUrl('/my/endpoint')}`;
|
|
58
|
+
```
|
|
59
|
+
|
|
48
60
|
### Auth Service
|
|
49
61
|
|
|
50
62
|
This service provides authentication methods for managing user sessions in a Cornerstone client application.
|
|
51
63
|
|
|
52
|
-
|
|
64
|
+
```ts
|
|
65
|
+
import { AuthService } from './index.js';
|
|
66
|
+
const authService = new AuthService();
|
|
67
|
+
await authService.initialize();
|
|
68
|
+
const user = authService.user;
|
|
69
|
+
```
|
|
53
70
|
|
|
54
|
-
|
|
71
|
+
or use the existing singleton instance
|
|
55
72
|
|
|
56
73
|
```ts
|
|
57
|
-
import {
|
|
58
|
-
|
|
74
|
+
import { authService } from '@intellegens/cornerstone-client';
|
|
75
|
+
await authService.initialize();
|
|
76
|
+
const user = authService.user;
|
|
59
77
|
```
|
|
60
78
|
|
|
61
79
|
#### Methods
|
|
62
80
|
|
|
63
|
-
|
|
81
|
+
##### Who Am I
|
|
64
82
|
|
|
65
|
-
|
|
83
|
+
```ts
|
|
84
|
+
authService.whoAmI(): Promise<User | undefined>
|
|
85
|
+
```
|
|
66
86
|
|
|
67
|
-
|
|
87
|
+
This method checks the current session and returns the User object for the logged-in user. If no user is logged in, it returns undefined.
|
|
68
88
|
|
|
69
|
-
|
|
89
|
+
##### Sign in
|
|
70
90
|
|
|
71
|
-
|
|
91
|
+
```ts
|
|
92
|
+
authService.signin(username: string, password: string): Promise<User | undefined>
|
|
93
|
+
```
|
|
72
94
|
|
|
73
|
-
This method
|
|
95
|
+
This method logs the user in by sending the provided username and password to the API. If the login is successful, it returns the authenticated User object.
|
|
96
|
+
|
|
97
|
+
##### Sign out
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
authService.logout(): Promise<User | undefined>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This method logs the user out by sending a request to the API to end the session. It returns undefined if the logout is successful
|
package/package.json
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Central API configuration service, fetches and exposes Cornerstone API configuration
|
|
3
|
+
*
|
|
4
|
+
* @export
|
|
5
|
+
* @class ApiService
|
|
6
|
+
*/
|
|
7
|
+
export declare class ApiService {
|
|
8
|
+
/**
|
|
9
|
+
* Initializes the API configuration
|
|
10
|
+
*
|
|
11
|
+
* This method calls all methods that need to be called during the API configuration phase:
|
|
12
|
+
* - Starts detection of API base URL.
|
|
13
|
+
*
|
|
14
|
+
* @async
|
|
15
|
+
* @return {Promise<void>} Resolves when initialization is complete.
|
|
16
|
+
*/
|
|
17
|
+
initialize(): Promise<void>;
|
|
18
|
+
private _apiBaseUrlPromise;
|
|
19
|
+
/**
|
|
20
|
+
* If API base URL detection was completed
|
|
21
|
+
* IMPORTANT: This property is not meant to be used in normal cases - when ever possible use the .getApiUrl(relativeEndpointPath: string) method instead.
|
|
22
|
+
*/
|
|
23
|
+
_apiBaseUrlDetected: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* If API base URL detection was completed, this property will hold the method by which detection was performed
|
|
26
|
+
* IMPORTANT: This property is not meant to be used in normal cases - when ever possible use the .getApiUrl(relativeEndpointPath: string) method instead.
|
|
27
|
+
*/
|
|
28
|
+
_apiBaseUrlDetectionMethod: string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* If API base URL detection was completed, this property will hold the detected API base URL
|
|
31
|
+
* IMPORTANT: This property is not meant to be used in normal cases - when ever possible use the .getApiUrl(relativeEndpointPath: string) method instead.
|
|
32
|
+
*/
|
|
33
|
+
_apiBaseUrl: string | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* If API base URL detection has failed, this property will hold the error with which it has failed
|
|
36
|
+
* IMPORTANT: This property is not meant to be used in normal cases - when ever possible use the .getApiUrl(relativeEndpointPath: string) method instead.
|
|
37
|
+
*/
|
|
38
|
+
_apiBaseError: Error | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Tries detecting API base URL by multiple methods
|
|
41
|
+
*
|
|
42
|
+
* - Checks the response headers of the current URL for the `CORNERSTONE-API-BASEURL` header.
|
|
43
|
+
* - Attempts to load the `websettings.json` file and looks for an `api` field.
|
|
44
|
+
* - Defaults to `undefined`.
|
|
45
|
+
*
|
|
46
|
+
* IMPORTANT: This method is not meant to be used in normal cases - when ever possible use the .getApiUrl(relativeEndpointPath: string) method instead.
|
|
47
|
+
*
|
|
48
|
+
* @async
|
|
49
|
+
* @return {Promise<string | undefined>} Returns detected API base URL
|
|
50
|
+
* @throws {Error} Throws error if either of the detection methods fails
|
|
51
|
+
*/
|
|
52
|
+
_getApiBaseUrl(): Promise<string | undefined>;
|
|
53
|
+
/**
|
|
54
|
+
* Composes a full API URL for a provided endpoint path.
|
|
55
|
+
*
|
|
56
|
+
* @async
|
|
57
|
+
* @param relativeEndpointPath Relative endpoint path
|
|
58
|
+
* @return {Promise<string | undefined>} Returns the API endpoint's URL
|
|
59
|
+
* @throws {Error} Throws error if API base URL detection fails
|
|
60
|
+
*/
|
|
61
|
+
getApiUrl(relativeEndpointPath: string): Promise<string>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Singleton instance of ApiService
|
|
65
|
+
*/
|
|
66
|
+
export declare const apiService: ApiService;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Central API configuration service, fetches and exposes Cornerstone API configuration
|
|
3
|
+
*
|
|
4
|
+
* @export
|
|
5
|
+
* @class ApiService
|
|
6
|
+
*/
|
|
7
|
+
export class ApiService {
|
|
8
|
+
/**
|
|
9
|
+
* Initializes the API configuration
|
|
10
|
+
*
|
|
11
|
+
* This method calls all methods that need to be called during the API configuration phase:
|
|
12
|
+
* - Starts detection of API base URL.
|
|
13
|
+
*
|
|
14
|
+
* @async
|
|
15
|
+
* @return {Promise<void>} Resolves when initialization is complete.
|
|
16
|
+
*/
|
|
17
|
+
async initialize() {
|
|
18
|
+
// (Pre)detect API base URL
|
|
19
|
+
await this._getApiBaseUrl();
|
|
20
|
+
}
|
|
21
|
+
// #region API Base URL detection
|
|
22
|
+
_apiBaseUrlPromise = undefined;
|
|
23
|
+
/**
|
|
24
|
+
* If API base URL detection was completed
|
|
25
|
+
* IMPORTANT: This property is not meant to be used in normal cases - when ever possible use the .getApiUrl(relativeEndpointPath: string) method instead.
|
|
26
|
+
*/
|
|
27
|
+
_apiBaseUrlDetected = false;
|
|
28
|
+
/**
|
|
29
|
+
* If API base URL detection was completed, this property will hold the method by which detection was performed
|
|
30
|
+
* IMPORTANT: This property is not meant to be used in normal cases - when ever possible use the .getApiUrl(relativeEndpointPath: string) method instead.
|
|
31
|
+
*/
|
|
32
|
+
_apiBaseUrlDetectionMethod = undefined;
|
|
33
|
+
/**
|
|
34
|
+
* If API base URL detection was completed, this property will hold the detected API base URL
|
|
35
|
+
* IMPORTANT: This property is not meant to be used in normal cases - when ever possible use the .getApiUrl(relativeEndpointPath: string) method instead.
|
|
36
|
+
*/
|
|
37
|
+
_apiBaseUrl = undefined;
|
|
38
|
+
/**
|
|
39
|
+
* If API base URL detection has failed, this property will hold the error with which it has failed
|
|
40
|
+
* IMPORTANT: This property is not meant to be used in normal cases - when ever possible use the .getApiUrl(relativeEndpointPath: string) method instead.
|
|
41
|
+
*/
|
|
42
|
+
_apiBaseError = undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Tries detecting API base URL by multiple methods
|
|
45
|
+
*
|
|
46
|
+
* - Checks the response headers of the current URL for the `CORNERSTONE-API-BASEURL` header.
|
|
47
|
+
* - Attempts to load the `websettings.json` file and looks for an `api` field.
|
|
48
|
+
* - Defaults to `undefined`.
|
|
49
|
+
*
|
|
50
|
+
* IMPORTANT: This method is not meant to be used in normal cases - when ever possible use the .getApiUrl(relativeEndpointPath: string) method instead.
|
|
51
|
+
*
|
|
52
|
+
* @async
|
|
53
|
+
* @return {Promise<string | undefined>} Returns detected API base URL
|
|
54
|
+
* @throws {Error} Throws error if either of the detection methods fails
|
|
55
|
+
*/
|
|
56
|
+
async _getApiBaseUrl() {
|
|
57
|
+
// Check if API already detected; don't reattempt detection
|
|
58
|
+
if (this._apiBaseUrlDetected)
|
|
59
|
+
return this._apiBaseUrl;
|
|
60
|
+
// Check if detection already in progress; don't allow multiple detections in parallel
|
|
61
|
+
if (this._apiBaseUrlPromise)
|
|
62
|
+
return this._apiBaseUrlPromise;
|
|
63
|
+
// Detect API base URL
|
|
64
|
+
return (this._apiBaseUrlPromise = new Promise((resolve, reject) => (async () => {
|
|
65
|
+
// Reset
|
|
66
|
+
this._apiBaseUrlDetected = false;
|
|
67
|
+
this._apiBaseUrlDetectionMethod = undefined;
|
|
68
|
+
this._apiBaseUrl = undefined;
|
|
69
|
+
this._apiBaseError = undefined;
|
|
70
|
+
// Check CORNERSTONE-API-BASEURL header for API base URL
|
|
71
|
+
try {
|
|
72
|
+
const currentUrlResponse = await fetch(window.location.href, { method: 'GET' });
|
|
73
|
+
const apiHeader = currentUrlResponse.headers.get('CORNERSTONE-API-BASEURL');
|
|
74
|
+
if (apiHeader) {
|
|
75
|
+
this._apiBaseUrlDetected = true;
|
|
76
|
+
this._apiBaseUrlDetectionMethod = 'GET / Header:CORNERSTONE-API-BASEURL';
|
|
77
|
+
this._apiBaseUrl = apiHeader;
|
|
78
|
+
this._apiBaseError = undefined;
|
|
79
|
+
this._apiBaseUrlPromise = undefined;
|
|
80
|
+
return resolve(this._apiBaseUrl);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
this._apiBaseError = err instanceof Error ? err : new Error('Failed loading API base URL from response header!');
|
|
85
|
+
}
|
|
86
|
+
// Check ./websettings.json header for API base URL
|
|
87
|
+
try {
|
|
88
|
+
const webSettingsResponse = await fetch('./websettings.json', { method: 'GET' });
|
|
89
|
+
if (webSettingsResponse.status === 404)
|
|
90
|
+
return (this._apiBaseUrl = undefined);
|
|
91
|
+
const webSettings = await webSettingsResponse.json();
|
|
92
|
+
if (webSettings?.api) {
|
|
93
|
+
this._apiBaseUrlDetected = true;
|
|
94
|
+
this._apiBaseUrlDetectionMethod = 'GET websettings.json';
|
|
95
|
+
this._apiBaseUrl = webSettings.api;
|
|
96
|
+
this._apiBaseError = undefined;
|
|
97
|
+
this._apiBaseUrlPromise = undefined;
|
|
98
|
+
return resolve(this._apiBaseUrl);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
this._apiBaseError = err instanceof Error ? err : new Error('Failed loading API base URL from settings file!');
|
|
103
|
+
}
|
|
104
|
+
// Check if error caught during any of the detection methods
|
|
105
|
+
if (this._apiBaseError !== undefined) {
|
|
106
|
+
this._apiBaseUrlDetected = false;
|
|
107
|
+
this._apiBaseUrlDetectionMethod = undefined;
|
|
108
|
+
this._apiBaseUrl = undefined;
|
|
109
|
+
this._apiBaseUrlPromise = undefined;
|
|
110
|
+
return reject(this._apiBaseError);
|
|
111
|
+
}
|
|
112
|
+
// Default to no API found
|
|
113
|
+
this._apiBaseUrlDetected = true;
|
|
114
|
+
this._apiBaseUrlDetectionMethod = undefined;
|
|
115
|
+
this._apiBaseUrl = undefined;
|
|
116
|
+
this._apiBaseError = undefined;
|
|
117
|
+
this._apiBaseUrlPromise = undefined;
|
|
118
|
+
return resolve(this._apiBaseUrl);
|
|
119
|
+
})()));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Composes a full API URL for a provided endpoint path.
|
|
123
|
+
*
|
|
124
|
+
* @async
|
|
125
|
+
* @param relativeEndpointPath Relative endpoint path
|
|
126
|
+
* @return {Promise<string | undefined>} Returns the API endpoint's URL
|
|
127
|
+
* @throws {Error} Throws error if API base URL detection fails
|
|
128
|
+
*/
|
|
129
|
+
async getApiUrl(relativeEndpointPath) {
|
|
130
|
+
const apiBaseUrl = await this._getApiBaseUrl();
|
|
131
|
+
const domain = !apiBaseUrl ? `` : apiBaseUrl?.endsWith('/') ? apiBaseUrl : `${apiBaseUrl}/`;
|
|
132
|
+
const path = !relativeEndpointPath.startsWith('/') ? relativeEndpointPath : relativeEndpointPath.substring(1);
|
|
133
|
+
return `${domain}${path}`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Singleton instance of ApiService
|
|
138
|
+
*/
|
|
139
|
+
export const apiService = new ApiService();
|
package/services/auth/index.d.ts
CHANGED
|
@@ -1,46 +1,49 @@
|
|
|
1
1
|
import { User, UserInfo } from './types';
|
|
2
2
|
export { User, UserInfo };
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* AuthService class is responsible for managing user authentication operations.
|
|
5
5
|
* It supports login, logout, and checking the current user's details.
|
|
6
6
|
*
|
|
7
7
|
* @export
|
|
8
|
-
* @class
|
|
8
|
+
* @class AuthService
|
|
9
9
|
*/
|
|
10
|
-
export declare class
|
|
11
|
-
|
|
10
|
+
export declare class AuthService<TId, TUser = User<TId>> {
|
|
11
|
+
/**
|
|
12
|
+
* Holds currently authenticated user's details
|
|
13
|
+
*/
|
|
12
14
|
user: TUser | undefined;
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
16
|
+
* Initializes the Authentication status by checking with the API if the current user is authenticated or not
|
|
15
17
|
*
|
|
16
|
-
* @
|
|
17
|
-
* @
|
|
18
|
+
* @async
|
|
19
|
+
* @return {Promise<void>} Resolves when initialization is complete.
|
|
18
20
|
*/
|
|
19
|
-
|
|
21
|
+
initialize(): Promise<void>;
|
|
20
22
|
/**
|
|
21
|
-
*
|
|
23
|
+
* Checks if user is authenticated and retrieves the current user's details if they are
|
|
22
24
|
*
|
|
23
|
-
* @return {Promise<TUser>} Currently logged in user
|
|
25
|
+
* @return {Promise<TUser>} Currently logged in user's details
|
|
24
26
|
* @throws {Error} Error if fetch fails
|
|
25
|
-
* @memberof Auth
|
|
26
27
|
*/
|
|
27
28
|
whoAmI(): Promise<TUser>;
|
|
28
29
|
/**
|
|
29
|
-
*
|
|
30
|
+
* Authenticates a user using their username and password, and retrieves user's details
|
|
30
31
|
*
|
|
31
32
|
* @param {string} username Login credentials: username
|
|
32
33
|
* @param {string} password Login credentials: password
|
|
33
|
-
* @return {Promise<TUser>} Currently logged in user
|
|
34
|
+
* @return {Promise<TUser>} Currently logged in user's details
|
|
34
35
|
* @throws {Error} Error if fetch fails
|
|
35
|
-
* @memberof Auth
|
|
36
36
|
*/
|
|
37
|
-
|
|
37
|
+
signin(username: string, password: string): Promise<TUser>;
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
39
|
+
* Deauthenticates current user
|
|
40
40
|
*
|
|
41
41
|
* @return {Promise<void>}
|
|
42
42
|
* @throws {Error} Error if fetch fails
|
|
43
|
-
* @memberof Auth
|
|
44
43
|
*/
|
|
45
|
-
|
|
44
|
+
signout(): Promise<void>;
|
|
46
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Singleton instance of AuthService
|
|
48
|
+
*/
|
|
49
|
+
export declare const authService: AuthService<unknown, User<unknown>>;
|
package/services/auth/index.js
CHANGED
|
@@ -1,32 +1,36 @@
|
|
|
1
|
+
import { apiService } from '../api';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
+
* AuthService class is responsible for managing user authentication operations.
|
|
3
4
|
* It supports login, logout, and checking the current user's details.
|
|
4
5
|
*
|
|
5
6
|
* @export
|
|
6
|
-
* @class
|
|
7
|
+
* @class AuthService
|
|
7
8
|
*/
|
|
8
|
-
export class
|
|
9
|
-
|
|
9
|
+
export class AuthService {
|
|
10
|
+
/**
|
|
11
|
+
* Holds currently authenticated user's details
|
|
12
|
+
*/
|
|
10
13
|
user = undefined;
|
|
11
14
|
/**
|
|
12
|
-
*
|
|
15
|
+
* Initializes the Authentication status by checking with the API if the current user is authenticated or not
|
|
13
16
|
*
|
|
14
|
-
* @
|
|
15
|
-
* @
|
|
17
|
+
* @async
|
|
18
|
+
* @return {Promise<void>} Resolves when initialization is complete.
|
|
16
19
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
async initialize() {
|
|
21
|
+
// Check user's authentication status
|
|
22
|
+
await this.whoAmI();
|
|
19
23
|
}
|
|
20
24
|
/**
|
|
21
|
-
*
|
|
25
|
+
* Checks if user is authenticated and retrieves the current user's details if they are
|
|
22
26
|
*
|
|
23
|
-
* @return {Promise<TUser>} Currently logged in user
|
|
27
|
+
* @return {Promise<TUser>} Currently logged in user's details
|
|
24
28
|
* @throws {Error} Error if fetch fails
|
|
25
|
-
* @memberof Auth
|
|
26
29
|
*/
|
|
27
30
|
async whoAmI() {
|
|
28
31
|
try {
|
|
29
|
-
const
|
|
32
|
+
const url = await apiService.getApiUrl('/auth/whoami');
|
|
33
|
+
const response = await fetch(url, { credentials: 'include' });
|
|
30
34
|
if (response.ok) {
|
|
31
35
|
const info = await response.json();
|
|
32
36
|
return (this.user = info.user);
|
|
@@ -40,17 +44,17 @@ export class Auth {
|
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
46
|
/**
|
|
43
|
-
*
|
|
47
|
+
* Authenticates a user using their username and password, and retrieves user's details
|
|
44
48
|
*
|
|
45
49
|
* @param {string} username Login credentials: username
|
|
46
50
|
* @param {string} password Login credentials: password
|
|
47
|
-
* @return {Promise<TUser>} Currently logged in user
|
|
51
|
+
* @return {Promise<TUser>} Currently logged in user's details
|
|
48
52
|
* @throws {Error} Error if fetch fails
|
|
49
|
-
* @memberof Auth
|
|
50
53
|
*/
|
|
51
|
-
async
|
|
54
|
+
async signin(username, password) {
|
|
52
55
|
try {
|
|
53
|
-
const
|
|
56
|
+
const url = await apiService.getApiUrl('/auth/signin');
|
|
57
|
+
const response = await fetch(url, {
|
|
54
58
|
method: 'POST',
|
|
55
59
|
headers: { 'Content-Type': 'application/json' },
|
|
56
60
|
body: JSON.stringify({ username, password }),
|
|
@@ -60,33 +64,37 @@ export class Auth {
|
|
|
60
64
|
return (this.user = await this.whoAmI());
|
|
61
65
|
}
|
|
62
66
|
else {
|
|
63
|
-
throw new Error('Failed
|
|
67
|
+
throw new Error('Failed signing in');
|
|
64
68
|
}
|
|
65
69
|
}
|
|
66
70
|
catch (error) {
|
|
67
|
-
throw error instanceof Error ? error : new Error('Failed
|
|
71
|
+
throw error instanceof Error ? error : new Error('Failed signing in');
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
/**
|
|
71
|
-
*
|
|
75
|
+
* Deauthenticates current user
|
|
72
76
|
*
|
|
73
77
|
* @return {Promise<void>}
|
|
74
78
|
* @throws {Error} Error if fetch fails
|
|
75
|
-
* @memberof Auth
|
|
76
79
|
*/
|
|
77
|
-
async
|
|
80
|
+
async signout() {
|
|
78
81
|
try {
|
|
79
|
-
const
|
|
82
|
+
const url = await apiService.getApiUrl('/auth/signout');
|
|
83
|
+
const response = await fetch(url, { credentials: 'include' });
|
|
80
84
|
if (response.ok) {
|
|
81
85
|
this.user = undefined;
|
|
82
86
|
return;
|
|
83
87
|
}
|
|
84
88
|
else {
|
|
85
|
-
throw new Error('Failed
|
|
89
|
+
throw new Error('Failed signing out');
|
|
86
90
|
}
|
|
87
91
|
}
|
|
88
92
|
catch (error) {
|
|
89
|
-
throw error instanceof Error ? error : new Error('Failed
|
|
93
|
+
throw error instanceof Error ? error : new Error('Failed signing out');
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Singleton instance of AuthService
|
|
99
|
+
*/
|
|
100
|
+
export const authService = new AuthService();
|
|
@@ -7,10 +7,6 @@ import { User } from './user';
|
|
|
7
7
|
*
|
|
8
8
|
* @param {TUser} user The user object containing user details.
|
|
9
9
|
* @param {Date} accessTokenExpiry The date and time when the access token will expire.
|
|
10
|
-
*
|
|
11
|
-
* @typedef {Object} UserInfo
|
|
12
|
-
* @property {TUser} user The user object.
|
|
13
|
-
* @property {Date} accessTokenExpiry The expiry date of the access token.
|
|
14
10
|
*/
|
|
15
11
|
export type UserInfo<TId, TUser = User<TId>> = {
|
|
16
12
|
user: TUser;
|
|
@@ -5,10 +5,6 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @param {TId} id The unique identifier for the user.
|
|
7
7
|
* @param {string} email The email address associated with the user.
|
|
8
|
-
*
|
|
9
|
-
* @typedef {Object} User
|
|
10
|
-
* @property {TId} id The unique identifier of the user.
|
|
11
|
-
* @property {string} email The email address of the user.
|
|
12
8
|
*/
|
|
13
9
|
export type User<TId> = {
|
|
14
10
|
id: TId;
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Central configuration service, fetches and exposes Cornerstone configuration
|
|
3
3
|
*
|
|
4
4
|
* @export
|
|
5
|
-
* @class
|
|
5
|
+
* @class ConfigService
|
|
6
6
|
*/
|
|
7
|
-
export declare class
|
|
8
|
-
/**
|
|
9
|
-
* The base URL of the API. It is undefined by default and gets populated through the initialization process.
|
|
10
|
-
*
|
|
11
|
-
* @type {(string | undefined)}
|
|
12
|
-
* @memberof Config
|
|
13
|
-
*/
|
|
14
|
-
apiBaseUrl: string | undefined;
|
|
7
|
+
export declare class ConfigService {
|
|
15
8
|
/**
|
|
16
9
|
* Initializes the configuration by detecting the API base URL.
|
|
17
10
|
*
|
|
@@ -19,19 +12,36 @@ export declare class Config {
|
|
|
19
12
|
*
|
|
20
13
|
* @async
|
|
21
14
|
* @return {Promise<void>} Resolves when the API base URL is detected.
|
|
22
|
-
* @memberof Config
|
|
23
15
|
*/
|
|
24
16
|
initialize(): Promise<void>;
|
|
17
|
+
private _apiBaseUrlDetected;
|
|
18
|
+
private _apiBaseUrlDetectionMethod;
|
|
19
|
+
private _apiBaseUrl;
|
|
20
|
+
private _apiBaseError;
|
|
21
|
+
private _apiBaseUrlPromise;
|
|
25
22
|
/**
|
|
26
23
|
* Detects the API base URL by checking the `CORNERSTONE-API-BASEURL` header and the `websettings.json` file.
|
|
27
24
|
*
|
|
28
|
-
* First, it checks the response headers of the current URL for the
|
|
29
|
-
* If the header is not found, it attempts to load the `websettings.json` file and looks for an `api` field.
|
|
30
|
-
* If no API base URL is found, it defaults to `undefined`.
|
|
25
|
+
* - First, it checks the response headers of the current URL for the `CORNERSTONE-API-BASEURL` header.
|
|
26
|
+
* - If the header is not found, it attempts to load the `websettings.json` file and looks for an `api` field.
|
|
27
|
+
* - If no API base URL is found, it defaults to `undefined`.
|
|
31
28
|
*
|
|
29
|
+
* @async
|
|
32
30
|
* @return {Promise<string | undefined>} Returns the API base URL
|
|
33
|
-
* @throws {Error} Throws error if
|
|
34
|
-
|
|
31
|
+
* @throws {Error} Throws error if either of the detection methods fails
|
|
32
|
+
*/
|
|
33
|
+
_getApiBaseUrl(): Promise<string | undefined>;
|
|
34
|
+
/**
|
|
35
|
+
* Returns a full URL for a provided endpoint path.
|
|
36
|
+
*
|
|
37
|
+
* @async
|
|
38
|
+
* @param relativeEndpointPath Relative endpoint path
|
|
39
|
+
* @return {Promise<string | undefined>} Returns the API endpoint's URL
|
|
40
|
+
* @throws {Error} Throws error if API base URL detection fails
|
|
35
41
|
*/
|
|
36
|
-
|
|
42
|
+
getApiUrl(relativeEndpointPath: string): Promise<string>;
|
|
37
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Singleton instance of ConfigService
|
|
46
|
+
*/
|
|
47
|
+
export declare const configService: ConfigService;
|
package/services/config/index.js
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Central configuration service, fetches and exposes Cornerstone configuration
|
|
3
3
|
*
|
|
4
4
|
* @export
|
|
5
|
-
* @class
|
|
5
|
+
* @class ConfigService
|
|
6
6
|
*/
|
|
7
|
-
export class
|
|
8
|
-
/**
|
|
9
|
-
* The base URL of the API. It is undefined by default and gets populated through the initialization process.
|
|
10
|
-
*
|
|
11
|
-
* @type {(string | undefined)}
|
|
12
|
-
* @memberof Config
|
|
13
|
-
*/
|
|
14
|
-
apiBaseUrl = undefined;
|
|
7
|
+
export class ConfigService {
|
|
15
8
|
/**
|
|
16
9
|
* Initializes the configuration by detecting the API base URL.
|
|
17
10
|
*
|
|
@@ -19,51 +12,109 @@ export class Config {
|
|
|
19
12
|
*
|
|
20
13
|
* @async
|
|
21
14
|
* @return {Promise<void>} Resolves when the API base URL is detected.
|
|
22
|
-
* @memberof Config
|
|
23
15
|
*/
|
|
24
16
|
async initialize() {
|
|
25
|
-
|
|
17
|
+
// (Pre)detect API base URL
|
|
18
|
+
await this._getApiBaseUrl();
|
|
26
19
|
}
|
|
20
|
+
// #region API Base URL detection
|
|
21
|
+
_apiBaseUrlDetected = false;
|
|
22
|
+
_apiBaseUrlDetectionMethod = undefined;
|
|
23
|
+
_apiBaseUrl = undefined;
|
|
24
|
+
_apiBaseError = undefined;
|
|
25
|
+
_apiBaseUrlPromise = undefined;
|
|
27
26
|
/**
|
|
28
27
|
* Detects the API base URL by checking the `CORNERSTONE-API-BASEURL` header and the `websettings.json` file.
|
|
29
28
|
*
|
|
30
|
-
* First, it checks the response headers of the current URL for the
|
|
31
|
-
* If the header is not found, it attempts to load the `websettings.json` file and looks for an `api` field.
|
|
32
|
-
* If no API base URL is found, it defaults to `undefined`.
|
|
29
|
+
* - First, it checks the response headers of the current URL for the `CORNERSTONE-API-BASEURL` header.
|
|
30
|
+
* - If the header is not found, it attempts to load the `websettings.json` file and looks for an `api` field.
|
|
31
|
+
* - If no API base URL is found, it defaults to `undefined`.
|
|
33
32
|
*
|
|
33
|
+
* @async
|
|
34
34
|
* @return {Promise<string | undefined>} Returns the API base URL
|
|
35
|
-
* @throws {Error} Throws error if
|
|
36
|
-
* @memberof Config
|
|
35
|
+
* @throws {Error} Throws error if either of the detection methods fails
|
|
37
36
|
*/
|
|
38
|
-
async
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
37
|
+
async _getApiBaseUrl() {
|
|
38
|
+
// Check if API already detected; don't reattempt detection
|
|
39
|
+
if (this._apiBaseUrlDetected)
|
|
40
|
+
return this._apiBaseUrl;
|
|
41
|
+
// Check if detection already in progress; don't allow multiple detections in parallel
|
|
42
|
+
if (this._apiBaseUrlPromise)
|
|
43
|
+
return this._apiBaseUrlPromise;
|
|
44
|
+
// Detect API base URL
|
|
45
|
+
return (this._apiBaseUrlPromise = new Promise((resolve, reject) => (async () => {
|
|
46
|
+
// Reset
|
|
47
|
+
this._apiBaseUrlDetected = false;
|
|
48
|
+
this._apiBaseUrlDetectionMethod = undefined;
|
|
49
|
+
this._apiBaseUrl = undefined;
|
|
50
|
+
this._apiBaseError = undefined;
|
|
51
|
+
// Check CORNERSTONE-API-BASEURL header for API base URL
|
|
52
|
+
try {
|
|
53
|
+
const currentUrlResponse = await fetch(window.location.href, { method: 'GET' });
|
|
54
|
+
const apiHeader = currentUrlResponse.headers.get('CORNERSTONE-API-BASEURL');
|
|
55
|
+
if (apiHeader) {
|
|
56
|
+
this._apiBaseUrlDetected = true;
|
|
57
|
+
this._apiBaseUrlDetectionMethod = 'GET / Header:CORNERSTONE-API-BASEURL';
|
|
58
|
+
this._apiBaseUrl = apiHeader;
|
|
59
|
+
this._apiBaseError = undefined;
|
|
60
|
+
this._apiBaseUrlPromise = undefined;
|
|
61
|
+
return resolve(this._apiBaseUrl);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
this._apiBaseError = err instanceof Error ? err : new Error('Failed loading configuration from response header!');
|
|
66
|
+
}
|
|
67
|
+
// Check ./websettings.json header for API base URL
|
|
68
|
+
try {
|
|
69
|
+
const webSettingsResponse = await fetch('./websettings.json', { method: 'GET' });
|
|
70
|
+
if (webSettingsResponse.status === 404)
|
|
71
|
+
return (this._apiBaseUrl = undefined);
|
|
72
|
+
const webSettings = await webSettingsResponse.json();
|
|
73
|
+
if (webSettings?.api) {
|
|
74
|
+
this._apiBaseUrlDetected = true;
|
|
75
|
+
this._apiBaseUrlDetectionMethod = 'GET websettings.json';
|
|
76
|
+
this._apiBaseUrl = webSettings.api;
|
|
77
|
+
this._apiBaseError = undefined;
|
|
78
|
+
this._apiBaseUrlPromise = undefined;
|
|
79
|
+
return resolve(this._apiBaseUrl);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
this._apiBaseError = err instanceof Error ? err : new Error('Failed loading configuration from settings file!');
|
|
84
|
+
}
|
|
85
|
+
// Check if error caught during any of the detection methods
|
|
86
|
+
if (this._apiBaseError !== undefined) {
|
|
87
|
+
this._apiBaseUrlDetected = false;
|
|
88
|
+
this._apiBaseUrlDetectionMethod = undefined;
|
|
89
|
+
this._apiBaseUrl = undefined;
|
|
90
|
+
this._apiBaseUrlPromise = undefined;
|
|
91
|
+
return reject(this._apiBaseError);
|
|
92
|
+
}
|
|
93
|
+
// Default to no API found
|
|
94
|
+
this._apiBaseUrlDetected = true;
|
|
95
|
+
this._apiBaseUrlDetectionMethod = undefined;
|
|
96
|
+
this._apiBaseUrl = undefined;
|
|
97
|
+
this._apiBaseError = undefined;
|
|
98
|
+
this._apiBaseUrlPromise = undefined;
|
|
99
|
+
return resolve(this._apiBaseUrl);
|
|
100
|
+
})()));
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Returns a full URL for a provided endpoint path.
|
|
104
|
+
*
|
|
105
|
+
* @async
|
|
106
|
+
* @param relativeEndpointPath Relative endpoint path
|
|
107
|
+
* @return {Promise<string | undefined>} Returns the API endpoint's URL
|
|
108
|
+
* @throws {Error} Throws error if API base URL detection fails
|
|
109
|
+
*/
|
|
110
|
+
async getApiUrl(relativeEndpointPath) {
|
|
111
|
+
const apiBaseUrl = await this._getApiBaseUrl();
|
|
112
|
+
const domain = !apiBaseUrl ? `` : apiBaseUrl?.endsWith('/') ? apiBaseUrl : `${apiBaseUrl}/`;
|
|
113
|
+
const path = !relativeEndpointPath.startsWith('/') ? relativeEndpointPath : relativeEndpointPath.substring(1);
|
|
114
|
+
return `${domain}${path}`;
|
|
68
115
|
}
|
|
69
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Singleton instance of ConfigService
|
|
119
|
+
*/
|
|
120
|
+
export const configService = new ConfigService();
|
package/services/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './api';
|
|
2
2
|
export * from './auth';
|
package/services/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './api';
|
|
2
2
|
export * from './auth';
|