@equinor/fusion-framework-module-widget 2.0.10 → 3.0.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +82 -70
  2. package/dist/esm/Widget.js +255 -0
  3. package/dist/esm/Widget.js.map +1 -0
  4. package/dist/esm/WidgetModuleConfigurator.js +39 -73
  5. package/dist/esm/WidgetModuleConfigurator.js.map +1 -1
  6. package/dist/esm/WidgetModuleProvider.js +85 -17
  7. package/dist/esm/WidgetModuleProvider.js.map +1 -1
  8. package/dist/esm/enable-widget-module.js +8 -2
  9. package/dist/esm/enable-widget-module.js.map +1 -1
  10. package/dist/esm/errors.js +22 -4
  11. package/dist/esm/errors.js.map +1 -1
  12. package/dist/esm/events.js +2 -0
  13. package/dist/esm/events.js.map +1 -0
  14. package/dist/esm/index.js +1 -1
  15. package/dist/esm/index.js.map +1 -1
  16. package/dist/esm/module.js +5 -2
  17. package/dist/esm/module.js.map +1 -1
  18. package/dist/esm/state/actions.js +31 -0
  19. package/dist/esm/state/actions.js.map +1 -0
  20. package/dist/esm/state/create-reducer.js +32 -0
  21. package/dist/esm/state/create-reducer.js.map +1 -0
  22. package/dist/esm/state/create-state.js +12 -0
  23. package/dist/esm/state/create-state.js.map +1 -0
  24. package/dist/esm/state/flows.js +21 -0
  25. package/dist/esm/state/flows.js.map +1 -0
  26. package/dist/esm/utils.js +43 -0
  27. package/dist/esm/utils.js.map +1 -0
  28. package/dist/esm/version.js +2 -1
  29. package/dist/esm/version.js.map +1 -1
  30. package/dist/tsconfig.tsbuildinfo +1 -1
  31. package/dist/types/Widget.d.ts +78 -0
  32. package/dist/types/WidgetModuleConfigurator.d.ts +27 -27
  33. package/dist/types/WidgetModuleProvider.d.ts +44 -5
  34. package/dist/types/enable-widget-module.d.ts +5 -1
  35. package/dist/types/errors.d.ts +7 -2
  36. package/dist/types/events.d.ts +54 -0
  37. package/dist/types/index.d.ts +1 -1
  38. package/dist/types/module.d.ts +3 -3
  39. package/dist/types/state/actions.d.ts +92 -0
  40. package/dist/types/state/create-reducer.d.ts +3 -0
  41. package/dist/types/state/create-state.d.ts +5 -0
  42. package/dist/types/state/flows.d.ts +7 -0
  43. package/dist/types/types.d.ts +81 -5
  44. package/dist/types/utils.d.ts +5 -0
  45. package/dist/types/version.d.ts +1 -1
  46. package/package.json +4 -1
  47. package/src/Widget.ts +324 -0
  48. package/src/WidgetModuleConfigurator.ts +48 -118
  49. package/src/WidgetModuleProvider.ts +110 -22
  50. package/src/enable-widget-module.ts +2 -2
  51. package/src/errors.ts +38 -4
  52. package/src/events.ts +75 -0
  53. package/src/index.ts +1 -5
  54. package/src/module.ts +8 -5
  55. package/src/state/actions.ts +70 -0
  56. package/src/state/create-reducer.ts +44 -0
  57. package/src/state/create-state.ts +21 -0
  58. package/src/state/flows.ts +75 -0
  59. package/src/types.ts +89 -9
  60. package/src/utils.ts +51 -0
  61. package/src/version.ts +1 -1
  62. package/tsconfig.json +1 -3
  63. package/dist/esm/WidgetModuleConfigBuilder.js +0 -42
  64. package/dist/esm/WidgetModuleConfigBuilder.js.map +0 -1
  65. package/dist/types/WidgetModuleConfigBuilder.d.ts +0 -19
  66. package/src/WidgetModuleConfigBuilder.ts +0 -70
package/src/Widget.ts ADDED
@@ -0,0 +1,324 @@
1
+ import { ModuleType } from '@equinor/fusion-framework-module';
2
+ import {
3
+ GetWidgetParameters,
4
+ WidgetConfig,
5
+ WidgetManifest,
6
+ WidgetScriptModule,
7
+ WidgetState,
8
+ WidgetStateInitial,
9
+ } from './types';
10
+ import { Actions, actions } from './state/actions';
11
+ import { FlowSubject } from '@equinor/fusion-observable';
12
+
13
+ import { createState } from './state/create-state';
14
+ import { EventModule } from '@equinor/fusion-framework-module-event';
15
+ import { Observable, Subscription, combineLatest, firstValueFrom, lastValueFrom, of } from 'rxjs';
16
+ import WidgetModuleProvider from './WidgetModuleProvider';
17
+ import { WidgetModuleConfig } from './WidgetModuleConfigurator';
18
+
19
+ import './events';
20
+
21
+ // Class representing a fusion widget
22
+ export class Widget {
23
+ #state: FlowSubject<WidgetState, Actions>;
24
+ name: string;
25
+ config?: WidgetModuleConfig;
26
+ widgetPrams?: GetWidgetParameters['args'];
27
+
28
+ #subscription = new Subscription();
29
+
30
+ // Getter for accessing the current state of the widget
31
+ get state(): WidgetState {
32
+ return this.#state.value;
33
+ }
34
+
35
+ /**
36
+ * Constructs a new Widget instance.
37
+ * @param value - Initial state of the widget.
38
+ * @param args - Configuration and event parameters for the widget.
39
+ */
40
+ constructor(
41
+ value: WidgetStateInitial,
42
+ args: {
43
+ provider: WidgetModuleProvider;
44
+ config?: WidgetModuleConfig;
45
+ event?: ModuleType<EventModule>;
46
+ widgetPrams?: GetWidgetParameters['args'];
47
+ },
48
+ ) {
49
+ this.name = value.name;
50
+ this.widgetPrams = args.widgetPrams;
51
+ this.config = args.config;
52
+ this.#state = createState(value, args.provider);
53
+ args.event && this.#registerEvents(args.event);
54
+ }
55
+
56
+ /**
57
+ * Registers event listeners for various actions in the widget's state.
58
+ * @param event - The event module to dispatch events.
59
+ */
60
+ #registerEvents(event: ModuleType<EventModule>): void {
61
+ const { name } = this;
62
+
63
+ this.#state.addEffect(actions.fetchManifest.type, () => {
64
+ event.dispatchEvent('onWidgetManifestLoad', {
65
+ detail: { name },
66
+ source: this,
67
+ });
68
+ });
69
+ this.#state.addEffect(actions.fetchManifest.success.type, (action) => {
70
+ event.dispatchEvent('onWidgetManifestLoaded', {
71
+ detail: { name, manifest: action.payload },
72
+ source: this,
73
+ });
74
+ });
75
+ this.#state.addEffect(actions.fetchManifest.failure.type, (action) => {
76
+ event.dispatchEvent('onWidgetManifestFailure', {
77
+ detail: { name, error: action.payload },
78
+ source: this,
79
+ });
80
+ });
81
+
82
+ this.#state.addEffect(actions.importWidget.type, () => {
83
+ event.dispatchEvent('onWidgetScriptLoad', {
84
+ detail: { name },
85
+ source: this,
86
+ });
87
+ });
88
+ this.#state.addEffect(actions.importWidget.success.type, (action) => {
89
+ event.dispatchEvent('onWidgetScriptLoaded', {
90
+ detail: { name, script: action.payload },
91
+ source: this,
92
+ });
93
+ });
94
+ this.#state.addEffect(actions.importWidget.failure.type, (action) => {
95
+ event.dispatchEvent('onWidgetScriptFailure', {
96
+ detail: { name, error: action.payload },
97
+ source: this,
98
+ });
99
+ });
100
+
101
+ this.#state.addEffect(actions.initialize.type, () => {
102
+ event.dispatchEvent('onWidgetInitialize', {
103
+ detail: { name },
104
+ source: this,
105
+ });
106
+ });
107
+
108
+ this.#state.addEffect(actions.initialize.success.type, () => {
109
+ event.dispatchEvent('onWidgetInitialized', {
110
+ detail: { name },
111
+ source: this,
112
+ });
113
+ });
114
+
115
+ this.#state.addEffect(actions.initialize.failure.type, ({ payload }) => {
116
+ event.dispatchEvent('onWidgetInitializeFailure', {
117
+ detail: { name, error: payload },
118
+ source: this,
119
+ });
120
+ });
121
+ }
122
+
123
+ /**
124
+ * Retrieves the manifest of the widget as an observable stream.
125
+ * @param force_refresh - Flag to force refresh the manifest.
126
+ * @returns An observable stream of the widget manifest.
127
+ */
128
+ public getManifest(force_refresh = false): Observable<WidgetManifest> {
129
+ return new Observable((subscriber) => {
130
+ if (this.#state.value.manifest) {
131
+ subscriber.next(this.#state.value.manifest);
132
+ if (!force_refresh) {
133
+ return subscriber.complete();
134
+ }
135
+ }
136
+ subscriber.add(
137
+ this.#state.addEffect('set_manifest', ({ payload }) => {
138
+ subscriber.next(payload);
139
+ }),
140
+ );
141
+ subscriber.add(
142
+ this.#state.addEffect('fetch_manifest::success', ({ payload }) => {
143
+ subscriber.next(payload);
144
+ subscriber.complete();
145
+ }),
146
+ );
147
+ subscriber.add(
148
+ this.#state.addEffect('fetch_manifest::failure', ({ payload }) => {
149
+ subscriber.error(
150
+ Error('failed to load widget manifest', {
151
+ cause: payload,
152
+ }),
153
+ );
154
+ }),
155
+ );
156
+
157
+ this.loadManifest();
158
+ });
159
+ }
160
+
161
+ /**
162
+ * Retrieves the configuration of the widget as an observable stream.
163
+ * @param force_refresh - Flag to force refresh the configuration.
164
+ * @returns An observable stream of the widget configuration.
165
+ */
166
+ public getConfig(force_refresh = false): Observable<WidgetConfig> {
167
+ return new Observable((subscriber) => {
168
+ if (this.#state.value.manifest) {
169
+ subscriber.next(this.#state.value.config);
170
+ if (!force_refresh) {
171
+ return subscriber.complete();
172
+ }
173
+ }
174
+ subscriber.add(
175
+ this.#state.addEffect('set_config', ({ payload }) => {
176
+ subscriber.next(payload);
177
+ }),
178
+ );
179
+ subscriber.add(
180
+ this.#state.addEffect('fetch_config::success', ({ payload }) => {
181
+ subscriber.next(payload);
182
+ subscriber.complete();
183
+ }),
184
+ );
185
+ subscriber.add(
186
+ this.#state.addEffect('fetch_config::failure', ({ payload }) => {
187
+ subscriber.error(
188
+ Error('failed to load widget manifest', {
189
+ cause: payload,
190
+ }),
191
+ );
192
+ }),
193
+ );
194
+
195
+ this.loadConfig();
196
+ });
197
+ }
198
+
199
+ /**
200
+ * Loads the configuration for the widget.
201
+ * @param update - Flag to force an update of the configuration.
202
+ */
203
+ public loadConfig(update?: boolean) {
204
+ this.#state.next(actions.fetchConfig({ key: this.name, ...this.widgetPrams }, update));
205
+ }
206
+
207
+ /**
208
+ * Loads the manifest for the widget.
209
+ * @param update - Flag to force an update of the manifest.
210
+ */
211
+ public loadManifest(update?: boolean) {
212
+ this.#state.next(actions.fetchManifest({ key: this.name, ...this.widgetPrams }, update));
213
+ }
214
+
215
+ /**
216
+ * Retrieves the widget module as an observable stream.
217
+ * @param force_refresh - Flag to force refresh the widget module.
218
+ * @returns An observable stream of the widget module.
219
+ */
220
+ public getWidgetModule(force_refresh = false): Observable<WidgetScriptModule> {
221
+ return new Observable((subscriber) => {
222
+ if (this.#state.value.modules) {
223
+ subscriber.next(this.#state.value.modules);
224
+ if (!force_refresh) {
225
+ return subscriber.complete();
226
+ }
227
+ }
228
+ subscriber.add(
229
+ this.#state.addEffect('set_module', ({ payload }) => {
230
+ subscriber.next(payload);
231
+ }),
232
+ );
233
+ subscriber.add(
234
+ this.#state.addEffect('import_widget::success', ({ payload }) => {
235
+ subscriber.next(payload);
236
+ subscriber.complete();
237
+ }),
238
+ );
239
+ subscriber.add(
240
+ this.#state.addEffect('import_widget::failure', ({ payload }) => {
241
+ subscriber.error(
242
+ Error('failed to load widget modules from script', {
243
+ cause: payload,
244
+ }),
245
+ );
246
+ }),
247
+ );
248
+
249
+ subscriber.add(
250
+ this.getManifest().subscribe((manifest) => {
251
+ const path = manifest.assetPath
252
+ ? `${manifest.assetPath}/${manifest.entryPoint}?api-version=${this.config?.client.apiVersion}`
253
+ : `${manifest.entryPoint}?api-version=${this.config?.client.apiVersion}`;
254
+
255
+ const url = new URL(path, this.config?.client.baseImportUrl);
256
+
257
+ return of(this.#state.next(actions.importWidget(url.href)));
258
+ }),
259
+ );
260
+ });
261
+ }
262
+ /**
263
+ * Initializes the widget and returns an observable stream with the combined results.
264
+ * @returns An observable stream with the manifest, script, and configuration.
265
+ */
266
+ public initialize(): Observable<{
267
+ manifest: WidgetManifest;
268
+ script: WidgetScriptModule;
269
+ config?: WidgetConfig;
270
+ }> {
271
+ return new Observable((observer) => {
272
+ this.#state.next(actions.initialize());
273
+ observer.add(
274
+ combineLatest([
275
+ this.getManifest(),
276
+ this.getWidgetModule(),
277
+ // this.getConfig(),
278
+ ]).subscribe({
279
+ next: ([manifest, script]) =>
280
+ observer.next({
281
+ manifest,
282
+ script,
283
+ //Todo: uncomment the getConfig on line #273 and replace config when backend support widget config.
284
+ config: {
285
+ environment: {},
286
+ endpoints: {},
287
+ },
288
+ }),
289
+ error: (err) => {
290
+ observer.error(err), this.#state.next(actions.initialize.failure(err));
291
+ },
292
+ complete: () => {
293
+ this.#state.next(actions.initialize.success());
294
+ observer.complete();
295
+ },
296
+ }),
297
+ );
298
+ });
299
+ }
300
+ /**
301
+ * Retrieves the widget module asynchronously as a Promise.
302
+ * @param allow_cache - Flag to allow caching of the widget module.
303
+ * @returns A Promise containing the widget module.
304
+ */
305
+ public getWidgetModuleAsync(allow_cache = true): Promise<WidgetScriptModule> {
306
+ const operator = allow_cache ? firstValueFrom : lastValueFrom;
307
+ return operator(this.getWidgetModule(!allow_cache));
308
+ }
309
+ /**
310
+ * Updates the manifest of the widget.
311
+ * @param manifest - The new manifest for the widget.
312
+ * @param replace - Flag to replace the existing manifest.
313
+ */
314
+ public updateManifest(manifest: WidgetManifest, replace?: false) {
315
+ this.#state.next(actions.setManifest(manifest, !replace));
316
+ }
317
+
318
+ /**
319
+ * Disposes of the widget by unsubscribing from any active subscriptions.
320
+ */
321
+ public dispose() {
322
+ this.#subscription.unsubscribe();
323
+ }
324
+ }
@@ -1,137 +1,67 @@
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
- }
1
+ import { BaseConfigBuilder, type ConfigBuilderCallback } from '@equinor/fusion-framework-module';
2
+ import { ConfigBuilderCallbackArgs } from '@equinor/fusion-framework-module';
3
+ import { createDefaultClient } from './utils';
4
+ import type { IClient } from './types';
5
+
6
+ // Define the configuration type for the WidgetModule
7
+ export type WidgetModuleConfig = {
8
+ client: IClient;
38
9
  };
39
10
 
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
- };
11
+ // Define a callback type for configuring the WidgetModule
12
+ export type WidgetModuleConfigBuilderCallback = (
13
+ builder: WidgetModuleConfigurator,
14
+ ) => void | Promise<void>;
56
15
 
57
- export class WidgetModuleConfigurator implements IWidgetModuleConfigurator {
16
+ // Class responsible for configuring the WidgetModule
17
+ export class WidgetModuleConfigurator extends BaseConfigBuilder<WidgetModuleConfig> {
18
+ // Default expiration time for configurations (1 minute)
58
19
  defaultExpireTime = 1 * 60 * 1000;
59
20
 
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
- }
21
+ /**
22
+ * Set the client for the WidgetModule configuration.
23
+ * @param cb - Callback function to configure the client.
24
+ */
25
+ public setClient(cb: ConfigBuilderCallback<IClient>) {
26
+ this._set('client', cb);
71
27
  }
72
28
 
73
29
  /**
74
- * WARNING: this function will be remove in future
30
+ * Create an HTTP client based on the provided parameters.
31
+ * @param clientId - Identifier for the client.
32
+ * @param init - Configuration builder callback arguments.
33
+ * @returns An instance of the HTTP client.
75
34
  */
76
- protected async _createHttpClient(
77
- init: ModuleInitializerArgs<
78
- IWidgetModuleConfigurator,
79
- [HttpModule, ServiceDiscoveryModule]
80
- >,
81
- ): Promise<IHttpClient> {
35
+ private async _createHttpClient(clientId: string, init: ConfigBuilderCallbackArgs) {
82
36
  const http = await init.requireInstance('http');
83
- /** check if the http provider has configure a client */
84
- if (http.hasClient(moduleKey)) {
85
- return http.createClient(moduleKey);
37
+
38
+ if (http.hasClient(clientId)) {
39
+ return http.createClient(clientId);
86
40
  } else {
87
41
  /** load service discovery module */
88
42
  const serviceDiscovery = await init.requireInstance('serviceDiscovery');
89
-
90
- const discoClient = await serviceDiscovery.createClient('apps');
91
-
92
- return discoClient;
43
+ return await serviceDiscovery.createClient(clientId);
93
44
  }
94
45
  }
95
46
 
96
- public async createConfig(
97
- init: ModuleInitializerArgs<
98
- IWidgetModuleConfigurator,
99
- [HttpModule, ServiceDiscoveryModule]
100
- >,
101
- ): Promise<WidgetModuleConfig> {
102
- const config = await this.#configBuilders.reduce(
103
- async (cur, cb) => {
104
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
- const builder = new WidgetModuleConfigBuilder(init, await cur);
106
- await Promise.resolve(cb(builder));
107
- return Object.assign(cur, builder.config);
108
- },
109
- Promise.resolve({} as Partial<WidgetModuleConfig>),
110
- );
111
-
112
- const { endpointBuilder = defaultEndpointBuilder } = config;
113
-
114
- // TODO - make less lazy
115
- config.client ??= await (async (): Promise<WidgetModuleConfig['client']> => {
116
- const httpClient = await this._createHttpClient(init);
117
- httpClient.requestHandler.setHeader('api-version', this.#apiVersion);
118
- return {
119
- getWidget: {
120
- client: {
121
- fn: (args) =>
122
- httpClient.json$(endpointBuilder(args), {
123
- selector: widgetSelector({
124
- apiVersion: this.#apiVersion,
125
- uri: httpClient.uri,
126
- }),
127
- }),
128
- },
129
- key: (args) => JSON.stringify(args),
130
- expire: this.defaultExpireTime,
131
- },
132
- };
133
- })();
134
-
47
+ /**
48
+ * Process the WidgetModule configuration and create an HTTP client if needed.
49
+ * @param config - Partial configuration for the WidgetModule.
50
+ * @param _init - Configuration builder callback arguments.
51
+ * @returns The processed WidgetModule configuration.
52
+ */
53
+ protected async _processConfig(
54
+ config: Partial<WidgetModuleConfig>,
55
+ _init: ConfigBuilderCallbackArgs,
56
+ ) {
57
+ // Create an HTTP client using the specified client ID and initialization parameters
58
+ const httpClient = await this._createHttpClient('apps', _init);
59
+
60
+ // If the configuration does not have a client, use the default client
61
+ if (!config.client) {
62
+ config.client = createDefaultClient(httpClient);
63
+ }
64
+ // Return the processed configuration as a WidgetModuleConfig object
135
65
  return config as WidgetModuleConfig;
136
66
  }
137
67
  }