@equinor/fusion-framework-module-http 0.0.0-next-20230915111233
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/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +427 -0
- package/LICENSE +21 -0
- package/README.md +57 -0
- package/dist/esm/configurator.js +22 -0
- package/dist/esm/configurator.js.map +1 -0
- package/dist/esm/errors.js +8 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/lib/client/client-msal.js +15 -0
- package/dist/esm/lib/client/client-msal.js.map +1 -0
- package/dist/esm/lib/client/client.js +108 -0
- package/dist/esm/lib/client/client.js.map +1 -0
- package/dist/esm/lib/client/index.js +4 -0
- package/dist/esm/lib/client/index.js.map +1 -0
- package/dist/esm/lib/client/types.js +2 -0
- package/dist/esm/lib/client/types.js.map +1 -0
- package/dist/esm/lib/index.js +4 -0
- package/dist/esm/lib/index.js.map +1 -0
- package/dist/esm/lib/operators/http-request-handler.js +10 -0
- package/dist/esm/lib/operators/http-request-handler.js.map +1 -0
- package/dist/esm/lib/operators/http-response-handler.js +5 -0
- package/dist/esm/lib/operators/http-response-handler.js.map +1 -0
- package/dist/esm/lib/operators/index.js +5 -0
- package/dist/esm/lib/operators/index.js.map +1 -0
- package/dist/esm/lib/operators/process-operators.js +27 -0
- package/dist/esm/lib/operators/process-operators.js.map +1 -0
- package/dist/esm/lib/operators/request-operator-header.js +7 -0
- package/dist/esm/lib/operators/request-operator-header.js.map +1 -0
- package/dist/esm/lib/operators/types.js +2 -0
- package/dist/esm/lib/operators/types.js.map +1 -0
- package/dist/esm/lib/selectors/blob-selector.js +16 -0
- package/dist/esm/lib/selectors/blob-selector.js.map +1 -0
- package/dist/esm/lib/selectors/index.js +3 -0
- package/dist/esm/lib/selectors/index.js.map +1 -0
- package/dist/esm/lib/selectors/json-selector.js +16 -0
- package/dist/esm/lib/selectors/json-selector.js.map +1 -0
- package/dist/esm/module.js +48 -0
- package/dist/esm/module.js.map +1 -0
- package/dist/esm/provider.js +51 -0
- package/dist/esm/provider.js.map +1 -0
- package/dist/esm/version.js +2 -0
- package/dist/esm/version.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/configurator.d.ts +37 -0
- package/dist/types/errors.d.ts +5 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/lib/client/client-msal.d.ts +12 -0
- package/dist/types/lib/client/client.d.ts +34 -0
- package/dist/types/lib/client/index.d.ts +3 -0
- package/dist/types/lib/client/types.d.ts +36 -0
- package/dist/types/lib/index.d.ts +3 -0
- package/dist/types/lib/operators/http-request-handler.d.ts +6 -0
- package/dist/types/lib/operators/http-response-handler.d.ts +4 -0
- package/dist/types/lib/operators/index.d.ts +4 -0
- package/dist/types/lib/operators/process-operators.d.ts +9 -0
- package/dist/types/lib/operators/request-operator-header.d.ts +4 -0
- package/dist/types/lib/operators/types.d.ts +14 -0
- package/dist/types/lib/selectors/blob-selector.d.ts +2 -0
- package/dist/types/lib/selectors/index.d.ts +2 -0
- package/dist/types/lib/selectors/json-selector.d.ts +2 -0
- package/dist/types/module.d.ts +18 -0
- package/dist/types/provider.d.ts +23 -0
- package/dist/types/version.d.ts +1 -0
- package/package.json +51 -0
- package/src/configurator.ts +131 -0
- package/src/errors.ts +9 -0
- package/src/index.ts +14 -0
- package/src/lib/client/client-msal.ts +33 -0
- package/src/lib/client/client.ts +204 -0
- package/src/lib/client/index.ts +4 -0
- package/src/lib/client/types.ts +168 -0
- package/src/lib/index.ts +3 -0
- package/src/lib/operators/http-request-handler.ts +21 -0
- package/src/lib/operators/http-response-handler.ts +5 -0
- package/src/lib/operators/index.ts +5 -0
- package/src/lib/operators/process-operators.ts +43 -0
- package/src/lib/operators/request-operator-header.ts +12 -0
- package/src/lib/operators/types.ts +42 -0
- package/src/lib/selectors/blob-selector.ts +19 -0
- package/src/lib/selectors/index.ts +2 -0
- package/src/lib/selectors/json-selector.ts +19 -0
- package/src/module.ts +80 -0
- package/src/provider.ts +98 -0
- package/src/version.ts +2 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { from, of } from 'rxjs';
|
|
2
|
+
import type { Observable } from 'rxjs';
|
|
3
|
+
import { last, mergeScan } from 'rxjs/operators';
|
|
4
|
+
import { IProcessOperators, ProcessOperator } from './types';
|
|
5
|
+
|
|
6
|
+
export class ProcessOperators<T> implements IProcessOperators<T> {
|
|
7
|
+
protected _operators: Record<string, ProcessOperator<T>> = {};
|
|
8
|
+
|
|
9
|
+
add(key: string, operator: ProcessOperator<T>): ProcessOperators<T> {
|
|
10
|
+
if (Object.keys(this._operators).includes(key))
|
|
11
|
+
throw Error(`Operator [${key}] already defined`);
|
|
12
|
+
return this.set(key, operator);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
set(key: string, operator: ProcessOperator<T>): ProcessOperators<T> {
|
|
16
|
+
this._operators[key] = operator;
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get(key: string): ProcessOperator<T> {
|
|
21
|
+
return this._operators[key];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
process(request: T): Observable<T> {
|
|
25
|
+
const operators = Object.values(this._operators);
|
|
26
|
+
/** if no operators registered, just return the observable value */
|
|
27
|
+
if (!operators.length) {
|
|
28
|
+
return of(request);
|
|
29
|
+
}
|
|
30
|
+
return from(Object.values(this._operators)).pipe(
|
|
31
|
+
mergeScan(
|
|
32
|
+
// resolve current operator and return result or previous if void
|
|
33
|
+
(value, operator) => Promise.resolve(operator(value)).then((x) => x ?? value),
|
|
34
|
+
// initial value
|
|
35
|
+
request,
|
|
36
|
+
// only allow concurrency of one operator
|
|
37
|
+
1,
|
|
38
|
+
),
|
|
39
|
+
// output result of last operator
|
|
40
|
+
last(),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FetchRequest } from '../client';
|
|
2
|
+
import type { ProcessOperator } from './types';
|
|
3
|
+
|
|
4
|
+
export const requestOperatorHeader =
|
|
5
|
+
<T extends FetchRequest = FetchRequest>(key: string, value: string): ProcessOperator<T> =>
|
|
6
|
+
(request) => {
|
|
7
|
+
const headers = new Headers(request.headers);
|
|
8
|
+
headers.append(key, value);
|
|
9
|
+
return { ...request, headers };
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default requestOperatorHeader;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Observable } from 'rxjs';
|
|
2
|
+
import type { FetchRequest } from '../client';
|
|
3
|
+
|
|
4
|
+
export type ProcessOperator<T, R = T> = (request: T) => R | void | Promise<R | void>;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Container for sync/async operators.
|
|
8
|
+
* Pipes each operator sequential
|
|
9
|
+
*/
|
|
10
|
+
export interface IProcessOperators<T> {
|
|
11
|
+
/**
|
|
12
|
+
* Add a new operator (throw error if already defined)
|
|
13
|
+
*/
|
|
14
|
+
add(key: string, operator: ProcessOperator<T>): IProcessOperators<T>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Add or sets a operator
|
|
18
|
+
*/
|
|
19
|
+
set(key: string, operator: ProcessOperator<T>): IProcessOperators<T>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get a operator, will return undefined on invalid key.
|
|
23
|
+
*/
|
|
24
|
+
get(key: string): ProcessOperator<T>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Process registered processors.
|
|
28
|
+
*/
|
|
29
|
+
process(request: T): Observable<T>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface IHttpRequestHandler<T extends FetchRequest = FetchRequest>
|
|
33
|
+
extends IProcessOperators<T> {
|
|
34
|
+
/**
|
|
35
|
+
* Set header that will apply on all requests done by consumer @see {HttpClient}
|
|
36
|
+
* @param key - name of header
|
|
37
|
+
* @param value - header value
|
|
38
|
+
*/
|
|
39
|
+
setHeader(key: string, value: string): IHttpRequestHandler<T>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface IHttpResponseHandler<T = Response> extends IProcessOperators<T> {}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const blobSelector = <TResponse extends Response = Response>(
|
|
2
|
+
response: TResponse,
|
|
3
|
+
): Promise<Blob> => {
|
|
4
|
+
if (!response.ok) {
|
|
5
|
+
throw new Error('network response was not OK');
|
|
6
|
+
}
|
|
7
|
+
//Status code 204 is no content
|
|
8
|
+
if (response.status === 204) {
|
|
9
|
+
throw new Error('no content');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
return response.blob();
|
|
14
|
+
} catch (err) {
|
|
15
|
+
throw Error('failed to parse response');
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default blobSelector;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const jsonSelector = <TType = unknown, TResponse extends Response = Response>(
|
|
2
|
+
response: TResponse,
|
|
3
|
+
): Promise<TType> => {
|
|
4
|
+
if (!response.ok) {
|
|
5
|
+
throw new Error('network response was not OK');
|
|
6
|
+
}
|
|
7
|
+
//Status code 204 is no content
|
|
8
|
+
if (response.status === 204) {
|
|
9
|
+
return Promise.resolve() as Promise<TType>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
return response.json();
|
|
14
|
+
} catch (err) {
|
|
15
|
+
throw Error('failed to parse response');
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default jsonSelector;
|
package/src/module.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { HttpClientMsal } from './lib/client';
|
|
3
|
+
import { IHttpClientConfigurator, HttpClientConfigurator, HttpClientOptions } from './configurator';
|
|
4
|
+
import { IHttpClientProvider, HttpClientProvider } from './provider';
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
Module,
|
|
8
|
+
ModuleConfigType,
|
|
9
|
+
IModuleConfigurator,
|
|
10
|
+
} from '@equinor/fusion-framework-module';
|
|
11
|
+
|
|
12
|
+
import { MsalModule } from '@equinor/fusion-framework-module-msal';
|
|
13
|
+
|
|
14
|
+
export type HttpModule = Module<'http', IHttpClientProvider, IHttpClientConfigurator>;
|
|
15
|
+
|
|
16
|
+
export type HttpMsalModule = Module<
|
|
17
|
+
'http',
|
|
18
|
+
IHttpClientProvider<HttpClientMsal>,
|
|
19
|
+
IHttpClientConfigurator<HttpClientMsal>,
|
|
20
|
+
[MsalModule]
|
|
21
|
+
>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Configure http-client
|
|
25
|
+
*/
|
|
26
|
+
export const module: HttpMsalModule = {
|
|
27
|
+
name: 'http',
|
|
28
|
+
configure: () => new HttpClientConfigurator(HttpClientMsal),
|
|
29
|
+
initialize: async ({
|
|
30
|
+
config,
|
|
31
|
+
hasModule,
|
|
32
|
+
requireInstance,
|
|
33
|
+
}): Promise<HttpClientProvider<HttpClientMsal>> => {
|
|
34
|
+
const httpProvider = new HttpClientProvider(config);
|
|
35
|
+
if (hasModule('auth')) {
|
|
36
|
+
const authProvider = await requireInstance('auth');
|
|
37
|
+
httpProvider.defaultHttpRequestHandler.set('MSAL', async (request) => {
|
|
38
|
+
const { scopes = [] } = request;
|
|
39
|
+
if (scopes.length) {
|
|
40
|
+
/** TODO should be try catch, check caller for handling */
|
|
41
|
+
const token = await authProvider.acquireToken({
|
|
42
|
+
scopes,
|
|
43
|
+
});
|
|
44
|
+
if (token) {
|
|
45
|
+
const headers = new Headers(request.headers);
|
|
46
|
+
headers.set('Authorization', `Bearer ${token.accessToken}`);
|
|
47
|
+
return { ...request, headers };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return httpProvider;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const configureHttp = <TRef = unknown>(
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
+
configure: (config: ModuleConfigType<HttpMsalModule>, ref?: TRef) => void,
|
|
59
|
+
): IModuleConfigurator<HttpMsalModule, TRef> => ({
|
|
60
|
+
module,
|
|
61
|
+
configure,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const configureHttpClient = <TRef = unknown>(
|
|
65
|
+
name: string,
|
|
66
|
+
args: HttpClientOptions<HttpClientMsal>,
|
|
67
|
+
): IModuleConfigurator<HttpMsalModule, TRef> => ({
|
|
68
|
+
module,
|
|
69
|
+
configure: (config: ModuleConfigType<HttpMsalModule>) => {
|
|
70
|
+
config.configureClient(name, args);
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
declare module '@equinor/fusion-framework-module' {
|
|
75
|
+
interface Modules {
|
|
76
|
+
http: HttpMsalModule;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export default module;
|
package/src/provider.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { HttpClient } from './lib/client';
|
|
2
|
+
import {
|
|
3
|
+
HttpClientOptions,
|
|
4
|
+
HttpClientRequestInitType,
|
|
5
|
+
IHttpClientConfigurator,
|
|
6
|
+
} from './configurator';
|
|
7
|
+
|
|
8
|
+
import type { IHttpRequestHandler } from './lib/operators';
|
|
9
|
+
import type { IHttpClient } from './lib/client';
|
|
10
|
+
|
|
11
|
+
export class ClientNotFoundException extends Error {
|
|
12
|
+
constructor(message: string) {
|
|
13
|
+
super(message);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface IHttpClientProvider<TClient extends IHttpClient = IHttpClient> {
|
|
18
|
+
readonly defaultHttpRequestHandler: IHttpRequestHandler<HttpClientRequestInitType<TClient>>;
|
|
19
|
+
/** check if a client is configured */
|
|
20
|
+
hasClient(key: string): boolean;
|
|
21
|
+
/** create a new http client */
|
|
22
|
+
createClient(key: string): TClient;
|
|
23
|
+
createClient(key: HttpClientOptions<TClient>): TClient;
|
|
24
|
+
/**
|
|
25
|
+
* Class cast creation of custom client
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* config.http.configureClient('foobar', (client) => {
|
|
29
|
+
* client.ctor = MyClient;
|
|
30
|
+
* client.uri = 'https://foobar.com';
|
|
31
|
+
* });
|
|
32
|
+
*/
|
|
33
|
+
createCustomClient<T extends HttpClient>(key: string): T;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const isURL = (url: string) => {
|
|
37
|
+
const pattern = new RegExp(
|
|
38
|
+
'^(https?:\\/\\/)?' + // protocol
|
|
39
|
+
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|' + // domain name
|
|
40
|
+
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
|
|
41
|
+
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
|
|
42
|
+
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
|
|
43
|
+
'(\\#[-a-z\\d_]*)?$',
|
|
44
|
+
'i',
|
|
45
|
+
); // fragment locator
|
|
46
|
+
return pattern.test(url);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export class HttpClientProvider<TClient extends IHttpClient = IHttpClient>
|
|
50
|
+
implements IHttpClientProvider<TClient>
|
|
51
|
+
{
|
|
52
|
+
get defaultHttpRequestHandler(): IHttpRequestHandler<HttpClientRequestInitType<TClient>> {
|
|
53
|
+
return this.config.defaultHttpRequestHandler;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
constructor(protected config: IHttpClientConfigurator<TClient>) {}
|
|
57
|
+
|
|
58
|
+
public hasClient(key: string): boolean {
|
|
59
|
+
return Object.keys(this.config.clients).includes(key);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public createClient(keyOrConfig: string | HttpClientOptions<TClient>): TClient {
|
|
63
|
+
const config = this._resolveConfig(keyOrConfig);
|
|
64
|
+
const {
|
|
65
|
+
baseUri,
|
|
66
|
+
defaultScopes = [],
|
|
67
|
+
onCreate,
|
|
68
|
+
ctor = this.config.defaultHttpClientCtor,
|
|
69
|
+
requestHandler = this.defaultHttpRequestHandler,
|
|
70
|
+
} = config as HttpClientOptions<TClient>;
|
|
71
|
+
const options = { requestHandler };
|
|
72
|
+
const instance = new ctor(baseUri || '', options) as TClient;
|
|
73
|
+
Object.assign(instance, { defaultScopes });
|
|
74
|
+
onCreate && onCreate(instance as TClient);
|
|
75
|
+
return instance as TClient;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public createCustomClient<T extends HttpClient>(key: string): T {
|
|
79
|
+
return this.createClient(key) as unknown as T;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
protected _resolveConfig(
|
|
83
|
+
keyOrConfig: string | HttpClientOptions<TClient>,
|
|
84
|
+
): HttpClientOptions<TClient> {
|
|
85
|
+
if (typeof keyOrConfig === 'string') {
|
|
86
|
+
const config = this.config.clients[keyOrConfig];
|
|
87
|
+
if (!config && isURL(keyOrConfig)) {
|
|
88
|
+
return { baseUri: keyOrConfig };
|
|
89
|
+
} else if (!config) {
|
|
90
|
+
throw new ClientNotFoundException(
|
|
91
|
+
`No registered http client for key [${keyOrConfig}]`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return config;
|
|
95
|
+
}
|
|
96
|
+
return keyOrConfig as HttpClientOptions<TClient>;
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/version.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "dist/esm",
|
|
5
|
+
"rootDir": "src",
|
|
6
|
+
"declarationDir": "./dist/types",
|
|
7
|
+
|
|
8
|
+
},
|
|
9
|
+
"references": [
|
|
10
|
+
{
|
|
11
|
+
"path": "../module"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"path": "../msal"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"include": [
|
|
18
|
+
"src/**/*",
|
|
19
|
+
],
|
|
20
|
+
"exclude": [
|
|
21
|
+
"node_modules",
|
|
22
|
+
"lib"
|
|
23
|
+
]
|
|
24
|
+
}
|