@equinor/fusion-framework-module-widget 0.0.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +21 -0
  3. package/dist/esm/WidgetModuleConfigBuilder.js +42 -0
  4. package/dist/esm/WidgetModuleConfigBuilder.js.map +1 -0
  5. package/dist/esm/WidgetModuleConfigurator.js +98 -0
  6. package/dist/esm/WidgetModuleConfigurator.js.map +1 -0
  7. package/dist/esm/WidgetModuleProvider.js +52 -0
  8. package/dist/esm/WidgetModuleProvider.js.map +1 -0
  9. package/dist/esm/enable-widget-module.js +10 -0
  10. package/dist/esm/enable-widget-module.js.map +1 -0
  11. package/dist/esm/errors.js +22 -0
  12. package/dist/esm/errors.js.map +1 -0
  13. package/dist/esm/index.js +6 -0
  14. package/dist/esm/index.js.map +1 -0
  15. package/dist/esm/module.js +26 -0
  16. package/dist/esm/module.js.map +1 -0
  17. package/dist/esm/types.js +2 -0
  18. package/dist/esm/types.js.map +1 -0
  19. package/dist/tsconfig.tsbuildinfo +1 -0
  20. package/dist/types/WidgetModuleConfigBuilder.d.ts +19 -0
  21. package/dist/types/WidgetModuleConfigurator.d.ts +23 -0
  22. package/dist/types/WidgetModuleProvider.d.ts +21 -0
  23. package/dist/types/enable-widget-module.d.ts +3 -0
  24. package/dist/types/errors.d.ts +11 -0
  25. package/dist/types/index.d.ts +6 -0
  26. package/dist/types/module.d.ts +13 -0
  27. package/dist/types/types.d.ts +40 -0
  28. package/package.json +44 -0
  29. package/src/WidgetModuleConfigBuilder.ts +67 -0
  30. package/src/WidgetModuleConfigurator.ts +128 -0
  31. package/src/WidgetModuleProvider.ts +76 -0
  32. package/src/enable-widget-module.ts +21 -0
  33. package/src/errors.ts +30 -0
  34. package/src/index.ts +15 -0
  35. package/src/module.ts +35 -0
  36. package/src/types.ts +51 -0
  37. package/tsconfig.json +33 -0
@@ -0,0 +1,128 @@
1
+ import { ModuleInitializerArgs } from '@equinor/fusion-framework-module';
2
+ import { HttpModule, IHttpClient } from '@equinor/fusion-framework-module-http';
3
+ import { ServiceDiscoveryModule } from '@equinor/fusion-framework-module-service-discovery';
4
+ import { QueryCtorOptions } from '@equinor/fusion-query';
5
+
6
+ import {
7
+ WidgetModuleConfigBuilder,
8
+ WidgetModuleConfigBuilderCallback,
9
+ WidgetEndpointBuilder,
10
+ } from './WidgetModuleConfigBuilder';
11
+
12
+ import { moduleKey } from './module';
13
+
14
+ import type { GetWidgetParameters, WidgetManifest } from './types';
15
+
16
+ export interface WidgetModuleConfig {
17
+ client: {
18
+ // getWidgetManifest: QueryCtorOptions<WidgetManifest, { widgetKey: string }>;
19
+ getWidget: QueryCtorOptions<WidgetManifest, GetWidgetParameters>;
20
+ };
21
+ endpointBuilder: WidgetEndpointBuilder;
22
+ }
23
+
24
+ export interface IWidgetModuleConfigurator {
25
+ addConfigBuilder: (init: WidgetModuleConfigBuilderCallback) => void;
26
+ }
27
+
28
+ const defaultEndpointBuilder: WidgetEndpointBuilder = (params) => {
29
+ const { widgetKey, args } = params;
30
+ const { type, value } = args ?? {};
31
+ switch (type) {
32
+ case 'tag':
33
+ case 'version':
34
+ return `/widgets/${widgetKey}/versions/${value}`;
35
+ default:
36
+ return `/widgets/${widgetKey}`;
37
+ }
38
+ };
39
+
40
+ const widgetSelector =
41
+ (args: { apiVersion: string; uri: string }) =>
42
+ async (response: Response): Promise<WidgetManifest> => {
43
+ const data = await response.json();
44
+
45
+ return {
46
+ ...data,
47
+ importBundle: async () =>
48
+ import(
49
+ new URL(
50
+ `${data.assetPath}/${data.entryPoint}?api-version=${args.apiVersion}`,
51
+ args.uri
52
+ ).toString()
53
+ ),
54
+ } as WidgetManifest;
55
+ };
56
+
57
+ export class WidgetModuleConfigurator implements IWidgetModuleConfigurator {
58
+ defaultExpireTime = 1 * 60 * 1000;
59
+
60
+ #configBuilders: Array<WidgetModuleConfigBuilderCallback> = [];
61
+ #apiVersion = '1.0';
62
+
63
+ addConfigBuilder(init: WidgetModuleConfigBuilderCallback): void {
64
+ this.#configBuilders.push(init);
65
+ }
66
+
67
+ constructor(apiVersion?: string) {
68
+ if (apiVersion) {
69
+ this.#apiVersion = apiVersion;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * WARNING: this function will be remove in future
75
+ */
76
+ protected async _createHttpClient(
77
+ init: ModuleInitializerArgs<IWidgetModuleConfigurator, [HttpModule, ServiceDiscoveryModule]>
78
+ ): Promise<IHttpClient> {
79
+ const http = await init.requireInstance('http');
80
+ /** check if the http provider has configure a client */
81
+ if (http.hasClient(moduleKey)) {
82
+ return http.createClient(moduleKey);
83
+ } else {
84
+ /** load service discovery module */
85
+ const serviceDiscovery = await init.requireInstance('serviceDiscovery');
86
+
87
+ const discoClient = await serviceDiscovery.createClient('apps');
88
+
89
+ return discoClient;
90
+ }
91
+ }
92
+
93
+ public async createConfig(
94
+ init: ModuleInitializerArgs<IWidgetModuleConfigurator, [HttpModule, ServiceDiscoveryModule]>
95
+ ): Promise<WidgetModuleConfig> {
96
+ const config = await this.#configBuilders.reduce(async (cur, cb) => {
97
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
+ const builder = new WidgetModuleConfigBuilder(init, await cur);
99
+ await Promise.resolve(cb(builder));
100
+ return Object.assign(cur, builder.config);
101
+ }, Promise.resolve({} as Partial<WidgetModuleConfig>));
102
+
103
+ const { endpointBuilder = defaultEndpointBuilder } = config;
104
+
105
+ // TODO - make less lazy
106
+ config.client ??= await (async (): Promise<WidgetModuleConfig['client']> => {
107
+ const httpClient = await this._createHttpClient(init);
108
+ httpClient.requestHandler.setHeader('api-version', this.#apiVersion);
109
+ return {
110
+ getWidget: {
111
+ client: {
112
+ fn: (args) =>
113
+ httpClient.json$(endpointBuilder(args), {
114
+ selector: widgetSelector({
115
+ apiVersion: this.#apiVersion,
116
+ uri: httpClient.uri,
117
+ }),
118
+ }),
119
+ },
120
+ key: (args) => JSON.stringify(args),
121
+ expire: this.defaultExpireTime,
122
+ },
123
+ };
124
+ })();
125
+
126
+ return config as WidgetModuleConfig;
127
+ }
128
+ }
@@ -0,0 +1,76 @@
1
+ import { catchError, Observable, Subscription } from 'rxjs';
2
+
3
+ import { ModuleType } from '@equinor/fusion-framework-module';
4
+ import { HttpResponseError } from '@equinor/fusion-framework-module-http';
5
+ import { EventModule } from '@equinor/fusion-framework-module-event';
6
+
7
+ import { Query } from '@equinor/fusion-query';
8
+
9
+ import type { GetWidgetParameters, WidgetManifest } from './types';
10
+
11
+ import { WidgetModuleConfig } from './WidgetModuleConfigurator';
12
+ import { GetWidgetError } from './errors';
13
+
14
+ export interface IWidgetModuleProvider {
15
+ getWidget(
16
+ widgetKey: GetWidgetParameters['widgetKey'],
17
+ args: GetWidgetParameters['args']
18
+ ): Observable<WidgetManifest>;
19
+ }
20
+
21
+ export class WidgetModuleProvider implements IWidgetModuleProvider {
22
+ #widgetClient: Query<WidgetManifest, GetWidgetParameters>;
23
+ #subscription = new Subscription();
24
+
25
+ constructor(args: { config: WidgetModuleConfig; event?: ModuleType<EventModule> }) {
26
+ const { config } = args;
27
+
28
+ this.#widgetClient = new Query(config.client.getWidget);
29
+
30
+ this.#subscription.add(() => this.#widgetClient.complete());
31
+ }
32
+
33
+ public getWidget(name: string): Observable<WidgetManifest> {
34
+ return this._getWidget(name);
35
+ }
36
+
37
+ public getWidgetByVersion(name: string, version: string): Observable<WidgetManifest> {
38
+ return this._getWidget(name, { type: 'version', value: version });
39
+ }
40
+
41
+ public getWidgetByTag(name: string, tag: string): Observable<WidgetManifest> {
42
+ return this._getWidget(name, { type: 'tag', value: tag });
43
+ }
44
+
45
+ /**
46
+ * fetch configuration for a widget
47
+ * @param widgetKey - widget key
48
+ * @param version - version of widget to use
49
+ */
50
+ protected _getWidget(
51
+ widgetKey: GetWidgetParameters['widgetKey'],
52
+ args?: GetWidgetParameters['args']
53
+ ): Observable<WidgetManifest> {
54
+ return Query.extractQueryValue(
55
+ this.#widgetClient.query({ widgetKey, args }).pipe(
56
+ catchError((err) => {
57
+ /** extract cause, since error will be a `QueryError` */
58
+ const { cause } = err;
59
+ if (cause instanceof GetWidgetError) {
60
+ throw cause;
61
+ }
62
+ if (cause instanceof HttpResponseError) {
63
+ throw GetWidgetError.fromHttpResponse(cause.response, { cause });
64
+ }
65
+ throw new GetWidgetError('unknown', 'failed to load config', { cause });
66
+ })
67
+ )
68
+ );
69
+ }
70
+
71
+ public dispose() {
72
+ this.#subscription.unsubscribe();
73
+ }
74
+ }
75
+
76
+ export default WidgetModuleProvider;
@@ -0,0 +1,21 @@
1
+ import type { IModulesConfigurator } from '@equinor/fusion-framework-module';
2
+ import type { WidgetModuleConfigBuilderCallback } from './WidgetModuleConfigBuilder';
3
+
4
+ import { module } from './module';
5
+
6
+ /**
7
+ * Method for enabling the widget module
8
+ * @param configurator - configuration object
9
+ */
10
+ export const enableWidgetModule = (
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ configurator: IModulesConfigurator<any, any>,
13
+ builder?: WidgetModuleConfigBuilderCallback
14
+ ): void => {
15
+ configurator.addConfig({
16
+ module,
17
+ configure: (widgetConfigurator) => {
18
+ builder && widgetConfigurator.addConfigBuilder(builder);
19
+ },
20
+ });
21
+ };
package/src/errors.ts ADDED
@@ -0,0 +1,30 @@
1
+ type WidgetErrorType = 'not_found' | 'unauthorized' | 'unknown';
2
+
3
+ export class GetWidgetError extends Error {
4
+ static fromHttpResponse(response: Response, options?: ErrorOptions) {
5
+ switch (response.status) {
6
+ case 401:
7
+ return new GetWidgetError(
8
+ 'unauthorized',
9
+ 'failed to load widget manifest, request not authorized',
10
+ options
11
+ );
12
+ case 404:
13
+ return new GetWidgetError('not_found', 'widget manifest not found', options);
14
+ }
15
+ return new GetWidgetError(
16
+ 'unknown',
17
+ `failed to load widget manifest, status code ${response.status}`,
18
+ options
19
+ );
20
+ }
21
+ constructor(public readonly type: WidgetErrorType, message?: string, options?: ErrorOptions) {
22
+ super(message, options);
23
+ }
24
+ }
25
+
26
+ export class WidgetScriptModuleError extends Error {
27
+ constructor(public readonly type: WidgetErrorType, message?: string, options?: ErrorOptions) {
28
+ super(message, options);
29
+ }
30
+ }
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ export {
2
+ WidgetModuleConfigurator,
3
+ IWidgetModuleConfigurator,
4
+ WidgetModuleConfig,
5
+ } from './WidgetModuleConfigurator';
6
+
7
+ export { WidgetModuleProvider } from './WidgetModuleProvider';
8
+
9
+ export type { IWidgetModuleProvider } from './WidgetModuleProvider';
10
+
11
+ export * from './types';
12
+
13
+ export { enableWidgetModule } from './enable-widget-module';
14
+
15
+ export { default, WidgetModule, module, moduleKey } from './module';
package/src/module.ts ADDED
@@ -0,0 +1,35 @@
1
+ import { Module } from '@equinor/fusion-framework-module';
2
+ import { ModuleDeps } from './types';
3
+
4
+ import { IWidgetModuleConfigurator, WidgetModuleConfigurator } from './WidgetModuleConfigurator';
5
+ import { IWidgetModuleProvider, WidgetModuleProvider } from './WidgetModuleProvider';
6
+
7
+ export const moduleKey = 'widget';
8
+
9
+ export type WidgetModule = Module<
10
+ typeof moduleKey,
11
+ IWidgetModuleProvider,
12
+ IWidgetModuleConfigurator,
13
+ ModuleDeps
14
+ >;
15
+
16
+ export const module: WidgetModule = {
17
+ name: moduleKey,
18
+ configure: () => new WidgetModuleConfigurator('1.0-preview'),
19
+ initialize: async (args) => {
20
+ const config = await (args.config as WidgetModuleConfigurator).createConfig(args);
21
+ const event = await args.requireInstance('event').catch(() => undefined);
22
+ return new WidgetModuleProvider({ config, event });
23
+ },
24
+ dispose: (args) => {
25
+ (args.instance as unknown as WidgetModuleProvider).dispose();
26
+ },
27
+ };
28
+
29
+ export default module;
30
+
31
+ declare module '@equinor/fusion-framework-module' {
32
+ interface Modules {
33
+ widget: WidgetModule;
34
+ }
35
+ }
package/src/types.ts ADDED
@@ -0,0 +1,51 @@
1
+ import type { AnyModule, CombinedModules, ModulesInstance } from '@equinor/fusion-framework-module';
2
+ import type { EventModule } from '@equinor/fusion-framework-module-event';
3
+ import type { HttpModule } from '@equinor/fusion-framework-module-http';
4
+ import type { ServiceDiscoveryModule } from '@equinor/fusion-framework-module-service-discovery';
5
+
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ type Fusion = any;
8
+
9
+ export type WidgetEnv = {
10
+ config?: WidgetManifest;
11
+ };
12
+
13
+ // TODO: change to module-services when new app service is created
14
+ export type ModuleDeps = [HttpModule, ServiceDiscoveryModule, EventModule];
15
+
16
+ export type GetWidgetParameters = {
17
+ widgetKey: string;
18
+ args?: { type: 'version' | 'tag'; value: string };
19
+ };
20
+
21
+ export type WidgetManifest = {
22
+ id: string;
23
+ name: string;
24
+ version: string;
25
+ description: string;
26
+ maintainers?: string[];
27
+ entryPoint: string;
28
+ assetPath: string;
29
+ // TODO move to @equinor/fusion-widget
30
+ importBundle: () => Promise<WidgetScriptModule>;
31
+ };
32
+
33
+ export type WidgetModules<TModules extends Array<AnyModule> | unknown = unknown> = CombinedModules<
34
+ TModules,
35
+ [EventModule, ServiceDiscoveryModule]
36
+ >;
37
+
38
+ export type WidgetRenderArgs<TFusion extends Fusion = Fusion, TEnv = WidgetEnv> = {
39
+ fusion: TFusion;
40
+ env: TEnv;
41
+ };
42
+
43
+ export type WidgetScriptModule<
44
+ TProps extends Record<PropertyKey, unknown> = Record<PropertyKey, unknown>
45
+ > = {
46
+ default: (el: HTMLElement, args: WidgetRenderArgs, props?: TProps) => VoidFunction;
47
+ renderWidget: (el: HTMLElement, args: WidgetRenderArgs, props?: TProps) => VoidFunction;
48
+ };
49
+
50
+ export type WidgetModulesInstance<TModules extends Array<AnyModule> | unknown = unknown> =
51
+ ModulesInstance<WidgetModules<TModules>>;
package/tsconfig.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "extends": "../../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist/esm",
5
+ "rootDir": "src",
6
+ "declarationDir": "./dist/types",
7
+ "baseUrl": "src",
8
+ },
9
+ "references": [
10
+ {
11
+ "path": "../../utils/query"
12
+ },
13
+ {
14
+ "path": "../module"
15
+ },
16
+ {
17
+ "path": "../http"
18
+ },
19
+ {
20
+ "path": "../event"
21
+ },
22
+ {
23
+ "path": "../service-discovery"
24
+ }
25
+ ],
26
+ "include": [
27
+ "src/**/*",
28
+ ],
29
+ "exclude": [
30
+ "node_modules",
31
+ "lib"
32
+ ]
33
+ }