@equinor/fusion-framework-module-app 8.1.0 → 8.1.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 (52) hide show
  1. package/dist/esm/AppConfigurator.js +3 -1
  2. package/dist/esm/AppConfigurator.js.map +1 -1
  3. package/dist/esm/__tests__/actions.test.js +27 -0
  4. package/dist/esm/__tests__/actions.test.js.map +1 -0
  5. package/dist/esm/app/actions.js +1 -1
  6. package/dist/esm/app/actions.js.map +1 -1
  7. package/dist/esm/version.js +1 -1
  8. package/dist/tsconfig.tsbuildinfo +1 -1
  9. package/dist/types/AppConfigurator.d.ts +7 -1
  10. package/dist/types/__tests__/actions.test.d.ts +1 -0
  11. package/dist/types/version.d.ts +1 -1
  12. package/package.json +12 -9
  13. package/CHANGELOG.md +0 -1469
  14. package/src/AppClient.ts +0 -409
  15. package/src/AppConfig.ts +0 -99
  16. package/src/AppConfigSelector.ts +0 -25
  17. package/src/AppConfigurator.ts +0 -145
  18. package/src/AppModuleProvider.ts +0 -283
  19. package/src/__tests__/AppModuleProvider.test.ts +0 -47
  20. package/src/__tests__/MockAppClient.test.ts +0 -97
  21. package/src/app/App.ts +0 -984
  22. package/src/app/actions.ts +0 -112
  23. package/src/app/create-reducer.ts +0 -62
  24. package/src/app/create-state.ts +0 -52
  25. package/src/app/events.ts +0 -108
  26. package/src/app/filter-empty.ts +0 -11
  27. package/src/app/flows/handle-fetch-config.ts +0 -48
  28. package/src/app/flows/handle-fetch-manifest.ts +0 -53
  29. package/src/app/flows/handle-fetch-settings.ts +0 -50
  30. package/src/app/flows/handle-import-application.ts +0 -37
  31. package/src/app/flows/handle-update-settings.ts +0 -39
  32. package/src/app/flows/index.ts +0 -5
  33. package/src/app/index.ts +0 -10
  34. package/src/app/types.ts +0 -51
  35. package/src/enable-app-module.ts +0 -40
  36. package/src/errors/AppBuildError.ts +0 -47
  37. package/src/errors/AppConfigError.ts +0 -47
  38. package/src/errors/AppManifestError.ts +0 -47
  39. package/src/errors/AppScriptModuleError.ts +0 -20
  40. package/src/errors/AppSettingsError.ts +0 -47
  41. package/src/errors/app-error-type.ts +0 -9
  42. package/src/errors.ts +0 -6
  43. package/src/events.ts +0 -18
  44. package/src/index.ts +0 -40
  45. package/src/mock/MockAppClient.ts +0 -77
  46. package/src/mock/index.ts +0 -13
  47. package/src/module.ts +0 -59
  48. package/src/schemas.ts +0 -181
  49. package/src/types.ts +0 -263
  50. package/src/version.ts +0 -2
  51. package/tsconfig.json +0 -30
  52. package/vitest.config.ts +0 -11
package/src/app/App.ts DELETED
@@ -1,984 +0,0 @@
1
- import type {
2
- AppModulesInstance,
3
- AppScriptModule,
4
- AppManifest,
5
- AppConfig,
6
- AppSettings,
7
- ConfigEnvironment,
8
- } from '../types';
9
- import { type FlowSubject, Observable } from '@equinor/fusion-observable';
10
-
11
- import type { AppModuleProvider } from '../AppModuleProvider';
12
- import { combineLatest, of, Subscription, firstValueFrom, lastValueFrom } from 'rxjs';
13
- import { defaultIfEmpty, last, map, switchMap } from 'rxjs/operators';
14
-
15
- import type { EventModule } from '@equinor/fusion-framework-module-event';
16
- import type { AnyModule, ModuleType } from '@equinor/fusion-framework-module';
17
- import { createState } from './create-state';
18
- import { actions, type Actions } from './actions';
19
- import type { AppBundleState, AppBundleStateInitial } from './types';
20
-
21
- import isEqual from 'fast-deep-equal';
22
-
23
- import { filterEmpty } from './filter-empty';
24
-
25
- import './events';
26
-
27
- export { filterEmpty } from './filter-empty';
28
-
29
- /**
30
- * Public interface for a single loaded Fusion application.
31
- *
32
- * Provides reactive observables and imperative methods for accessing the
33
- * application's manifest, configuration, per-user settings, script module,
34
- * and initialized module instance.
35
- *
36
- * @template TEnv - Shape of the environment configuration record.
37
- * @template TModules - Additional framework modules the app depends on.
38
- */
39
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
- export interface IApp<
41
- TEnv extends ConfigEnvironment = ConfigEnvironment,
42
- TModules extends Array<AnyModule> | unknown = unknown,
43
- > {
44
- /**
45
- * Returns an observable that emits the app manifest.
46
- * @returns An observable of type AppManifest.
47
- */
48
- get manifest$(): Observable<AppManifest>;
49
-
50
- /**
51
- * Observable that emits the configuration of the app.
52
- * @returns An Observable that emits the app configuration.
53
- */
54
- get config$(): Observable<AppConfig<TEnv>>;
55
-
56
- /**
57
- * Returns an observable stream of the loaded app script instance.
58
- * @returns {Observable<AppScriptModule>} The observable stream of app script modules.
59
- */
60
- get modules$(): Observable<AppScriptModule>;
61
-
62
- /**
63
- * Returns an observable that emits the instance of the app modules.
64
- * @returns An observable that emits the instance of the app modules.
65
- */
66
- get instance$(): Observable<AppModulesInstance<TModules>>;
67
-
68
- /**
69
- * Observable that emits the settings of the app.
70
- * @returns An Observable that emits the app settings.
71
- */
72
- get settings$(): Observable<AppSettings>;
73
-
74
- /**
75
- * Observable that emits the status of the app.
76
- * @returns An Observable that emits the app status.
77
- */
78
- get status$(): Observable<AppBundleState['status']>;
79
-
80
- /**
81
- * Gets the current state of the Application.
82
- * @returns The current state of the Application.
83
- */
84
- get state(): AppBundleState;
85
-
86
- /**
87
- * Gets the app key.
88
- * @returns The app key.
89
- */
90
- get appKey(): string;
91
-
92
- /**
93
- * Gets the manifest of the app.
94
- * @returns The manifest of the app, or undefined if it doesn't exist.
95
- */
96
- get manifest(): Readonly<AppManifest> | undefined;
97
-
98
- /**
99
- * Retrieves the manifest asynchronously.
100
- * @returns A promise that resolves to the AppManifest.
101
- */
102
- get manifestAsync(): Promise<Readonly<AppManifest>>;
103
-
104
- /**
105
- * Gets the configuration of the app.
106
- * @returns The configuration object or undefined if no configuration is set.
107
- */
108
- get config(): AppConfig<TEnv> | undefined;
109
-
110
- /**
111
- * Retrieves the configuration asynchronously.
112
- * @returns A promise that resolves to the AppConfig.
113
- */
114
- get instance(): AppModulesInstance<TModules> | undefined;
115
-
116
- /**
117
- * Initializes the application container.
118
- * @returns An observable that emits an object containing the manifest, script, and config.
119
- * @example
120
- * ```typescript
121
- * app.initialize().subscribe({
122
- * next: ({ manifest, script, config }) => {
123
- * // Use the manifest, script, and config to initialize the application
124
- * script.render(el, ...);
125
- * },
126
- * error: (err) => console.error('Failed to load application', err),
127
- * complete: () => setInitializingApp(false)
128
- * });
129
- * ```
130
- */
131
- initialize(): Observable<{
132
- manifest: AppManifest;
133
- script: AppScriptModule;
134
- config: AppConfig;
135
- }>;
136
-
137
- /**
138
- * Loads the app configuration.
139
- */
140
- loadConfig(): void;
141
-
142
- /**
143
- * Loads the app manifest.
144
- */
145
- loadManifest(): void;
146
-
147
- /**
148
- * Loads the app module.
149
- * @param allow_cache Whether to allow loading from cache.
150
- */
151
- loadAppModule(allow_cache?: boolean): void;
152
-
153
- /**
154
- * Gets the app configuration.
155
- * @param force_refresh Whether to force refreshing the configuration.
156
- * @returns An observable that emits the app configuration.
157
- */
158
- getConfig(force_refresh?: boolean): Observable<AppConfig>;
159
-
160
- /**
161
- * Retrieves the app configuration asynchronously.
162
- * @param allow_cache Whether to allow loading from cache.
163
- * @returns A promise that resolves to the AppConfig.
164
- */
165
- getConfigAsync(allow_cache?: boolean): Promise<AppConfig>;
166
-
167
- /**
168
- * Gets the app settings.
169
- * @param force_refresh Whether to force refreshing the settings.
170
- * @returns An observable that emits the app settings.
171
- */
172
- getSettings(force_refresh?: boolean): Observable<AppSettings>;
173
-
174
- /**
175
- * Retrieves the app settings asynchronously.
176
- * @param allow_cache Whether to allow loading from cache.
177
- * @returns A promise that resolves to the AppSettings.
178
- */
179
- getSettingsAsync(allow_cache?: boolean): Promise<AppSettings>;
180
-
181
- /**
182
- * Sets the app settings.
183
- * @param settings The settings object to save.
184
- * @returns An observable that emits the app settings.
185
- */
186
- updateSettings<T extends AppSettings>(settings: T): Observable<T>;
187
-
188
- /**
189
- * Sets the app settings asyncronously.
190
- * @param settings The settings object to save.
191
- * @returns An Promise that resolves the app settings.
192
- */
193
- updateSettingsAsync<T extends AppSettings>(settings: T): Promise<T>;
194
-
195
- /**
196
- * Updates a specific setting of the app.
197
- * @param property The property to update.
198
- * @param value The value to set.
199
- * @returns An observable that emits the app settings.
200
- */
201
- updateSetting<T extends AppSettings, P extends keyof T>(
202
- property: P,
203
- value: T[P],
204
- ): Observable<T[P]>;
205
-
206
- /**
207
- * Updates a specific setting of the app asynchronously.
208
- * @param property The property to update.
209
- * @param value The value to set.
210
- * @returns A promise that resolves to the AppSettings.
211
- */
212
- updateSettingAsync<T extends AppSettings, P extends keyof T>(
213
- property: P,
214
- value: T[P],
215
- ): Promise<T[P]>;
216
-
217
- /**
218
- * Gets the app manifest.
219
- * @param force_refresh Whether to force refreshing the manifest.
220
- * @returns An observable that emits the app manifest.
221
- */
222
- getManifest(force_refresh?: boolean): Observable<AppManifest>;
223
-
224
- /**
225
- * Retrieves the app manifest asynchronously.
226
- * @param allow_cache Whether to allow loading from cache.
227
- * @returns A promise that resolves to the AppManifest.
228
- */
229
- getManifestAsync(allow_cache?: boolean): Promise<AppManifest>;
230
-
231
- /**
232
- * Gets the app module.
233
- * @param force_refresh Whether to force refreshing the app module.
234
- * @returns An observable that emits the app module.
235
- */
236
- getAppModule(force_refresh?: boolean): Observable<AppScriptModule>;
237
-
238
- /**
239
- * Retrieves the app module asynchronously.
240
- * @param allow_cache Whether to allow loading from cache.
241
- * @returns A promise that resolves to the AppScriptModule.
242
- */
243
- getAppModuleAsync(allow_cache?: boolean): Promise<AppScriptModule>;
244
- }
245
-
246
- /** Default empty settings object used when no settings have been fetched. */
247
- const fallbackSettings: AppSettings = {};
248
-
249
- /**
250
- * Result emitted by {@link IApp.initialize}, containing the resolved manifest,
251
- * imported script module, and runtime configuration.
252
- */
253
- export type AppInitializeResult = {
254
- manifest: AppManifest;
255
- script: AppScriptModule;
256
- config: AppConfig;
257
- };
258
-
259
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
260
- /**
261
- * Concrete implementation of {@link IApp}.
262
- *
263
- * Manages an internal reactive state machine ({@link FlowSubject}) that orchestrates
264
- * manifest fetching, config loading, settings management, and script import. Dispatches
265
- * lifecycle events through the {@link EventModule} when available.
266
- *
267
- * TODO(#5130): streams should be made distinct until changed from state.
268
- *
269
- * @template TEnv - Shape of the environment configuration record.
270
- * @template TModules - Additional framework modules the app depends on.
271
- */
272
- export class App<
273
- TEnv extends ConfigEnvironment = ConfigEnvironment,
274
- TModules extends Array<AnyModule> | unknown = unknown,
275
- > implements IApp<TEnv, TModules>
276
- {
277
- #state: FlowSubject<AppBundleState, Actions>;
278
-
279
- //#region === streams ===
280
-
281
- /** @inheritdoc */
282
- get manifest$(): Observable<AppManifest> {
283
- // filter out null/undefined manifest states
284
- return this.#state.select((state) => state.manifest).pipe(filterEmpty());
285
- }
286
-
287
- /** @inheritdoc */
288
- get config$(): Observable<AppConfig<TEnv>> {
289
- return (
290
- this.#state
291
- .select((state) => state.config as AppConfig<TEnv>, isEqual)
292
- // filter out null/undefined config states
293
- .pipe(filterEmpty())
294
- );
295
- }
296
-
297
- /** @inheritdoc */
298
- get modules$(): Observable<AppScriptModule> {
299
- // filter out null/undefined module states
300
- return this.#state.select((state) => state.modules).pipe(filterEmpty());
301
- }
302
-
303
- /** @inheritdoc */
304
- get instance$(): Observable<AppModulesInstance<TModules>> {
305
- return (
306
- this.#state
307
- .select((state) => state.instance as AppModulesInstance<TModules>)
308
- // filter out null/undefined instance states
309
- .pipe(filterEmpty())
310
- );
311
- }
312
-
313
- /** @inheritdoc */
314
- get settings$(): Observable<AppSettings> {
315
- return new Observable<AppSettings>((subscriber) => {
316
- this.#state.next(actions.fetchSettings(this.appKey));
317
- subscriber.add(
318
- this.#state
319
- .select((state) => state.settings, isEqual)
320
- // filter out null/undefined settings and fall back to the default empty settings
321
- .pipe(filterEmpty(), defaultIfEmpty(fallbackSettings))
322
- .subscribe(subscriber),
323
- );
324
- });
325
- }
326
-
327
- /** @inheritdoc */
328
- get status$(): Observable<AppBundleState['status']> {
329
- return this.#state.select((state) => state.status);
330
- }
331
-
332
- //#endregion
333
-
334
- /** @inheritdoc */
335
- get state(): Readonly<AppBundleState> {
336
- // TODO(#5133) deep-freeze
337
- return Object.freeze(this.#state.value) as Readonly<AppBundleState>;
338
- }
339
-
340
- /** @inheritdoc */
341
- get appKey(): string {
342
- return this.#state.value.appKey;
343
- }
344
- /**
345
- * The tag (version) this app instance was loaded with, if any.
346
- * @returns The tag string, or `undefined` if none was specified.
347
- */
348
- get tag(): string | undefined {
349
- return this.#state.value.tag;
350
- }
351
-
352
- /** @inheritdoc */
353
- get manifest(): Readonly<AppManifest> | undefined {
354
- return this.state.manifest;
355
- }
356
-
357
- /** @inheritdoc */
358
- get manifestAsync(): Promise<Readonly<AppManifest>> {
359
- return firstValueFrom(this.manifest$);
360
- }
361
-
362
- /** @inheritdoc */
363
- get config(): AppConfig<TEnv> | undefined {
364
- return this.state.config as AppConfig<TEnv>;
365
- }
366
-
367
- /**
368
- * Retrieves the configuration asynchronously.
369
- * @returns A promise that resolves to the {@link AppConfig}.
370
- */
371
- get configAsync(): Promise<AppConfig<TEnv>> {
372
- return firstValueFrom(this.config$);
373
- }
374
-
375
- /** @inheritdoc */
376
- get instance(): AppModulesInstance<TModules> | undefined {
377
- return this.#state.value.instance as AppModulesInstance<TModules>;
378
- }
379
-
380
- /**
381
- * Creates a new {@link App} instance and starts its internal state machine.
382
- * @param value - Initial app bundle state (appKey, tag, and any pre-loaded data).
383
- * @param args - The owning {@link AppModuleProvider} and an optional event module for lifecycle events.
384
- */
385
- constructor(
386
- value: AppBundleStateInitial,
387
- args: {
388
- provider: AppModuleProvider;
389
- event?: ModuleType<EventModule>;
390
- },
391
- ) {
392
- this.#state = createState(value, args.provider);
393
-
394
- const { appKey } = value;
395
- const { event } = args;
396
-
397
- // register events if event module is provided
398
- event && this.#registerEvents(event);
399
-
400
- // create a tear down handler for the application
401
- const subscriptions = new Subscription();
402
-
403
- // only wire up event listeners when an event module is provided
404
- if (event) {
405
- // when app is disposed, dispatch event to notify listeners
406
- subscriptions.add(() => {
407
- event.dispatchEvent('onAppDispose', { detail: { appKey } });
408
- });
409
-
410
- // when disposed, dispose of monitoring of app modules loaded
411
- subscriptions.add(
412
- event.addEventListener('onAppModulesLoaded', (e) => {
413
- // validate that the event is for the current app
414
- if (e.detail.appKey === appKey) {
415
- // set the instance of the app modules
416
- this.#state.next(actions.setInstance(e.detail.modules));
417
- }
418
- }),
419
- );
420
- }
421
-
422
- this.dispose = () => {
423
- subscriptions?.unsubscribe();
424
- // tear down modules of application, if an instance was loaded
425
- if (this.#state.value.instance) {
426
- // tear down modules of application
427
- this.#state.value.instance.dispose();
428
- }
429
- this.#state.complete();
430
- };
431
- }
432
-
433
- /**
434
- * Registers event listeners for various actions in the app.
435
- * @param event - The event module used for dispatching events.
436
- */
437
- #registerEvents(event: ModuleType<EventModule>): void {
438
- const { appKey } = this;
439
-
440
- // monitor when application manifest is loading
441
- this.#state.addEffect(actions.fetchManifest.type, () => {
442
- // dispatch event to notify listeners that the application manifest is being loaded
443
- event.dispatchEvent('onAppManifestLoad', {
444
- detail: { appKey },
445
- source: this,
446
- });
447
- });
448
-
449
- // monitor when application manifest is loaded
450
- this.#state.addEffect(actions.fetchManifest.success.type, (action) => {
451
- // dispatch event to notify listeners that the application manifest has been loaded
452
- event.dispatchEvent('onAppManifestLoaded', {
453
- detail: { appKey, manifest: action.payload },
454
- source: this,
455
- });
456
- });
457
-
458
- // monitor when application manifest fails to load
459
- this.#state.addEffect(actions.fetchManifest.failure.type, (action) => {
460
- // dispatch event to notify listeners that the application manifest failed to load
461
- event.dispatchEvent('onAppManifestFailure', {
462
- detail: { appKey, error: action.payload },
463
- source: this,
464
- });
465
- });
466
-
467
- // monitor when application configuration is loading
468
- this.#state.addEffect(actions.fetchConfig.type, () => {
469
- // dispatch event to notify listeners that the application configuration is being loaded
470
- event.dispatchEvent('onAppConfigLoad', {
471
- detail: { appKey },
472
- source: this,
473
- });
474
- });
475
-
476
- // monitor when application configuration is loaded
477
- this.#state.addEffect(actions.fetchConfig.success.type, (action) => {
478
- // dispatch event to notify listeners that the application configuration has been loaded
479
- event.dispatchEvent('onAppConfigLoaded', {
480
- detail: { appKey, config: action.payload },
481
- source: this,
482
- });
483
- });
484
-
485
- // monitor when application configuration fails to load
486
- this.#state.addEffect(actions.fetchConfig.failure.type, (action) => {
487
- // dispatch event to notify listeners that the application configuration failed to load
488
- event.dispatchEvent('onAppConfigFailure', {
489
- detail: { appKey, error: action.payload },
490
- source: this,
491
- });
492
- });
493
-
494
- // monitor when application settings is loading
495
- this.#state.addEffect(actions.fetchSettings.type, () => {
496
- // dispatch event to notify listeners that the application settings is being loaded
497
- event.dispatchEvent('onAppSettingsLoad', {
498
- detail: { appKey },
499
- source: this,
500
- });
501
- });
502
-
503
- // monitor when application settings is loaded
504
- this.#state.addEffect(actions.fetchSettings.success.type, (action) => {
505
- // dispatch event to notify listeners that the application settings has been loaded
506
- event.dispatchEvent('onAppSettingsLoaded', {
507
- detail: { appKey, settings: action.payload },
508
- source: this,
509
- });
510
- });
511
-
512
- // monitor when application settings fails to load
513
- this.#state.addEffect(actions.fetchSettings.failure.type, (action) => {
514
- // dispatch event to notify listeners that the application settings failed to load
515
- event.dispatchEvent('onAppSettingsFailure', {
516
- detail: { appKey, error: action.payload },
517
- source: this,
518
- });
519
- });
520
-
521
- // monitor when application settings is updated
522
- this.#state.addEffect(actions.updateSettings.type, (action) => {
523
- // dispatch event to notify listeners that the application settings has been loaded
524
- event.dispatchEvent('onAppSettingsUpdate', {
525
- detail: { appKey, settings: action.payload.settings },
526
- source: this,
527
- });
528
- });
529
-
530
- // monitor when application settings is updated
531
- this.#state.addEffect(actions.updateSettings.success.type, (action) => {
532
- // dispatch event to notify listeners that the application settings has been loaded
533
- event.dispatchEvent('onAppSettingsUpdated', {
534
- detail: { appKey, settings: action.payload.settings },
535
- source: this,
536
- });
537
- });
538
-
539
- // monitor when application settings fails to updated
540
- this.#state.addEffect(actions.updateSettings.failure.type, (action) => {
541
- // dispatch event to notify listeners that the application settings has been loaded
542
- event.dispatchEvent('onAppSettingsUpdateFailure', {
543
- detail: { appKey, settings: action.payload },
544
- source: this,
545
- });
546
- });
547
-
548
- // monitor when application script is loading
549
- this.#state.addEffect(actions.importApp.type, () => {
550
- // dispatch event to notify listeners that the application script is being loaded
551
- event.dispatchEvent('onAppScriptLoad', {
552
- detail: { appKey },
553
- source: this,
554
- });
555
- });
556
-
557
- // monitor when application script is loaded
558
- this.#state.addEffect(actions.importApp.success.type, (action) => {
559
- // dispatch event to notify listeners that the application script has been loaded
560
- event.dispatchEvent('onAppScriptLoaded', {
561
- detail: { appKey, script: action.payload },
562
- source: this,
563
- });
564
- });
565
-
566
- // monitor when application script fails to load
567
- this.#state.addEffect(actions.importApp.failure.type, (action) => {
568
- // dispatch event to notify listeners that the application script failed to load
569
- event.dispatchEvent('onAppScriptFailure', {
570
- detail: { appKey, error: action.payload },
571
- source: this,
572
- });
573
- });
574
-
575
- // monitor when application is initializing
576
- this.#state.addEffect(actions.initialize.type, () => {
577
- // dispatch event to notify listeners that the application is initializing
578
- event.dispatchEvent('onAppInitialize', {
579
- detail: { appKey },
580
- source: this,
581
- });
582
- });
583
-
584
- // monitor when application has been initialized
585
- this.#state.addEffect(actions.initialize.success.type, () => {
586
- // dispatch event to notify listeners that the application has been initialized
587
- event.dispatchEvent('onAppInitialized', {
588
- detail: { appKey },
589
- source: this,
590
- });
591
- });
592
-
593
- // monitor when application fails to initialize
594
- this.#state.addEffect(actions.initialize.failure.type, ({ payload }) => {
595
- // dispatch event to notify listeners that the application failed to initialize
596
- event.dispatchEvent('onAppInitializeFailure', {
597
- detail: { appKey, error: payload },
598
- source: this,
599
- });
600
- });
601
- }
602
-
603
- /** @inheritdoc */
604
- public initialize(): Observable<AppInitializeResult> {
605
- return new Observable((subscriber) => {
606
- // dispatch initialize action to indicate that the application is initializing
607
- this.#state.next(actions.initialize());
608
- subscriber.add(
609
- // request latest manifest, application script, and configuration
610
- combineLatest([this.getManifest(), this.getAppModule(), this.getConfig()]).subscribe({
611
- next: ([manifest, script, config]) =>
612
- // emit the manifest, script, and config to the subscriber
613
- subscriber.next({
614
- manifest,
615
- script,
616
- config,
617
- }),
618
- error: (err) => {
619
- // emit error and complete the stream
620
- subscriber.error(err);
621
- this.#state.next(actions.initialize.failure(err));
622
- },
623
- complete: () => {
624
- // dispatch initialize success action to indicate that the application has been initialized
625
- this.#state.next(actions.initialize.success());
626
- subscriber.complete();
627
- },
628
- }),
629
- );
630
- });
631
- }
632
-
633
- /** @inheritdoc */
634
- public loadConfig() {
635
- // TODO(#5126) - shit fix
636
- (this.manifest ? of(this.manifest) : this.getManifest()).subscribe({
637
- next: (manifest) => {
638
- this.#state.next(actions.fetchConfig(manifest));
639
- },
640
- });
641
- }
642
-
643
- /** @inheritdoc */
644
- public loadManifest(update?: boolean) {
645
- this.#state.next(actions.fetchManifest(this.appKey, this.tag, update));
646
- }
647
-
648
- /**
649
- * Replaces or merges the current manifest with a new one.
650
- * @param manifest - The manifest to apply.
651
- * @param replace - When `false` (default), merges into the existing manifest; when omitted, behaves the same.
652
- */
653
- public updateManifest(manifest: AppManifest, replace?: false) {
654
- this.#state.next(actions.setManifest(manifest, !replace));
655
- }
656
-
657
- /** @inheritdoc */
658
- public async loadAppModule(allow_cache = true) {
659
- const manifest = await this.getManifestAsync(allow_cache);
660
- // only import the app module if the manifest declares an entry point
661
- if (manifest.build?.entryPoint) {
662
- this.#state.next(actions.importApp(manifest.build.entryPoint));
663
- } else {
664
- console.warn(
665
- `The ${manifest.appKey} is missing entryPoint, please upload a build for the app before continuing`,
666
- );
667
- }
668
- }
669
-
670
- /** @inheritdoc */
671
- public getConfig(force_refresh = false): Observable<AppConfig> {
672
- return new Observable((subscriber) => {
673
- // emit the cached config immediately, if one is already loaded
674
- if (this.#state.value.config) {
675
- // emit current config to the subscriber
676
- subscriber.next(this.#state.value.config);
677
- // complete the stream unless a forced refresh was requested
678
- if (!force_refresh) {
679
- // since we have the config and no force refresh, complete the stream
680
- return subscriber.complete();
681
- }
682
- }
683
-
684
- // when stream closes, dispose of subscription to change of state config
685
- subscriber.add(
686
- // monitor changes to state changes of config and emit to subscriber
687
- this.#state.addEffect('set_config', ({ payload }) => {
688
- subscriber.next(payload);
689
- }),
690
- );
691
-
692
- // when stream closes, dispose of subscription to fetch config
693
- subscriber.add(
694
- // monitor success of fetching config and emit to subscriber
695
- this.#state.addEffect('fetch_config::success', ({ payload }) => {
696
- // application config loaded, emit to subscriber and complete the stream
697
- subscriber.next(payload);
698
- subscriber.complete();
699
- }),
700
- );
701
-
702
- // when stream closes, dispose of subscription to fetch config
703
- subscriber.add(
704
- // monitor failure of fetching config and emit error to subscriber
705
- this.#state.addEffect('fetch_config::failure', ({ payload }) => {
706
- // application config failed to load, emit error and complete the stream
707
- subscriber.error(
708
- Error('failed to load application config', {
709
- cause: payload,
710
- }),
711
- );
712
- }),
713
- );
714
-
715
- this.loadConfig();
716
- });
717
- }
718
-
719
- /** @inheritdoc */
720
- public getConfigAsync(allow_cache = true): Promise<AppConfig> {
721
- // when allow_cache is true, use first emitted value, otherwise use last emitted value
722
- const operator = allow_cache ? firstValueFrom : lastValueFrom;
723
- return operator(this.getConfig(!allow_cache));
724
- }
725
-
726
- /** @inheritdoc */
727
- public getSettings<T extends AppSettings>(force_refresh = false): Observable<T> {
728
- return new Observable<T>((subscriber) => {
729
- // emit the cached settings immediately, if already loaded
730
- if (this.#state.value.settings) {
731
- // emit current settings to the subscriber
732
- subscriber.next(this.#state.value.settings as T);
733
- // complete the stream unless a forced refresh was requested
734
- if (!force_refresh) {
735
- // since we have the settings and no force refresh, complete the stream
736
- return subscriber.complete();
737
- }
738
- }
739
-
740
- // when stream closes, dispose of subscription to change of state settings
741
- subscriber.add(
742
- // monitor changes to state changes of settings and emit to subscriber
743
- this.#state.addEffect('set_settings', ({ payload }) => {
744
- subscriber.next(payload as T);
745
- }),
746
- );
747
-
748
- // when stream closes, dispose of subscription to fetch settings
749
- subscriber.add(
750
- // monitor success of fetching settings and emit to subscriber
751
- this.#state.addEffect('fetch_settings::success', ({ payload }) => {
752
- // application settings loaded, emit to subscriber and complete the stream
753
- subscriber.next(payload as T);
754
- subscriber.complete();
755
- }),
756
- );
757
-
758
- // when stream closes, dispose of subscription to fetch settings
759
- subscriber.add(
760
- // monitor failure of fetching settings and emit error to subscriber
761
- this.#state.addEffect('fetch_settings::failure', ({ payload }) => {
762
- // application settings failed to load, emit error and complete the stream
763
- subscriber.error(
764
- Error('failed to load application settings', {
765
- cause: payload,
766
- }),
767
- );
768
- }),
769
- );
770
-
771
- this.#state.next(actions.fetchSettings(this.appKey));
772
- });
773
- }
774
-
775
- /** @inheritdoc */
776
- public getSettingsAsync<T extends AppSettings>(allow_cache = true): Promise<T> {
777
- // when allow_cache is true, use first emitted value, otherwise use last emitted value
778
- const operator = allow_cache ? firstValueFrom : lastValueFrom;
779
- return operator(this.getSettings<T>(!allow_cache));
780
- }
781
-
782
- /** @inheritdoc */
783
- public updateSettings<T extends AppSettings>(settings: T): Observable<T> {
784
- return new Observable((subscriber) => {
785
- subscriber.add(
786
- // monitor failure of updating settings and emit error to subscriber
787
- this.#state.addEffect('update_settings::failure', ({ payload }) => {
788
- // request to reset settings to source state
789
- this.#state.next(actions.fetchSettings(this.appKey));
790
-
791
- // application settings failed to save, emit error and complete the stream
792
- subscriber.error(
793
- Error('failed to update application settings', {
794
- cause: payload,
795
- }),
796
- );
797
- }),
798
- );
799
-
800
- subscriber.add(
801
- // monitor success of updating settings and emit to subscriber
802
- this.#state.addEffect('update_settings::success', ({ payload }) => {
803
- subscriber.next(payload as T);
804
- subscriber.complete();
805
- }),
806
- );
807
-
808
- // optimistic update of settings
809
- this.#state.next(actions.setSettings(settings));
810
-
811
- // request to update settings
812
- this.#state.next(actions.updateSettings(this.appKey, settings));
813
- });
814
- }
815
-
816
- /** @inheritdoc */
817
- public updateSettingsAsync<T extends AppSettings>(settings: T): Promise<T> {
818
- return lastValueFrom(this.updateSettings(settings));
819
- }
820
-
821
- /** @inheritdoc */
822
- public updateSetting<T extends AppSettings, P extends keyof T>(
823
- property: P,
824
- value: T[P],
825
- ): Observable<T[P]> {
826
- const currentSettings$ =
827
- this.#state.value.settings === undefined
828
- ? // if settings are not loaded, fetch settings
829
- this.getSettings()
830
- // take the last emitted value
831
- .pipe(last())
832
- : // if settings are loaded, use current settings
833
- of(this.#state.value.settings);
834
-
835
- // merge the current settings with the new value, then persist and return the updated property
836
- return currentSettings$.pipe(
837
- map((settings) => ({ ...settings, [property]: value })),
838
- // update settings
839
- switchMap((settings) => this.updateSettings<T>(settings as T)),
840
- // return the updated property
841
- map((settings) => settings[property] as T[P]),
842
- );
843
- }
844
-
845
- /** @inheritdoc */
846
- public updateSettingAsync<T extends AppSettings, P extends keyof T>(
847
- property: P,
848
- value: T[P],
849
- ): Promise<T[P]> {
850
- return lastValueFrom(this.updateSetting<T, P>(property, value));
851
- }
852
-
853
- /** @inheritdoc */
854
- public getManifest(force_refresh = false): Observable<AppManifest> {
855
- return new Observable((subscriber) => {
856
- // emit the cached manifest immediately, if already loaded
857
- if (this.#state.value.manifest) {
858
- // emit current manifest to the subscriber
859
- subscriber.next(this.#state.value.manifest);
860
- // complete the stream unless a forced refresh was requested
861
- if (!force_refresh) {
862
- // since we have the manifest and no force refresh, complete the stream
863
- return subscriber.complete();
864
- }
865
- }
866
- // when stream closes, dispose of subscription to change of state manifest
867
- subscriber.add(
868
- // monitor changes to state changes of manifest and emit to subscriber
869
- this.#state.addEffect('set_manifest', ({ payload }) => {
870
- subscriber.next(payload);
871
- }),
872
- );
873
-
874
- // when stream closes, dispose of subscription to fetch manifest
875
- subscriber.add(
876
- // monitor success of fetching manifest and emit to subscriber
877
- this.#state.addEffect('fetch_manifest::success', ({ payload }) => {
878
- subscriber.next(payload);
879
- // application manifest loaded, complete the stream
880
- subscriber.complete();
881
- }),
882
- );
883
- subscriber.add(
884
- // monitor failure of fetching manifest and emit error to subscriber
885
- this.#state.addEffect('fetch_manifest::failure', ({ payload }) => {
886
- // application manifest failed to load, emit error and complete the stream
887
- subscriber.error(
888
- Error('failed to load application manifest', {
889
- cause: payload,
890
- }),
891
- );
892
- }),
893
- );
894
-
895
- // fetch the application manifest
896
- this.loadManifest();
897
- });
898
- }
899
-
900
- /** @inheritdoc */
901
- public getManifestAsync(allow_cache = true): Promise<AppManifest> {
902
- // when allow_cache is true, use first emitted value, otherwise use last emitted value
903
- const operator = allow_cache ? firstValueFrom : lastValueFrom;
904
- return operator(this.getManifest(!allow_cache));
905
- }
906
-
907
- /** @inheritdoc */
908
- public getAppModule(force_refresh = false): Observable<AppScriptModule> {
909
- return new Observable((subscriber) => {
910
- // emit the cached module immediately, if already loaded
911
- if (this.#state.value.modules) {
912
- // emit current value to the subscriber
913
- subscriber.next(this.#state.value.modules);
914
- // complete the stream unless a forced refresh was requested
915
- if (!force_refresh) {
916
- // complete if no force refresh
917
- return subscriber.complete();
918
- }
919
- }
920
-
921
- // when stream closes, dispose of subscription to change of state modules
922
- subscriber.add(
923
- // monitor changes to state changes of modules and emit to subscriber
924
- this.#state.addEffect('set_module', ({ payload }) => {
925
- subscriber.next(payload);
926
- }),
927
- );
928
-
929
- // when stream closes, dispose of subscription script load success
930
- subscriber.add(
931
- // monitor success of loading application script and emit to subscriber
932
- this.#state.addEffect('import_app::success', ({ payload }) => {
933
- subscriber.next(payload);
934
- // application module loaded, complete the stream
935
- subscriber.complete();
936
- }),
937
- );
938
-
939
- // when stream closes, dispose of subscription to script load failure
940
- subscriber.add(
941
- // monitor failure of loading application script and emit error to subscriber
942
- this.#state.addEffect('import_app::failure', ({ payload }) => {
943
- // application module failed to load, emit error and complete the stream
944
- subscriber.error(
945
- Error('failed to load application modules from script', {
946
- cause: payload,
947
- }),
948
- );
949
- }),
950
- );
951
-
952
- // when stream closes, dispose of subscription to fetch manifest
953
- subscriber.add(
954
- // fetch application latest manifest and request loading of the application script
955
- this.getManifest().subscribe((manifest) => {
956
- // only import the app module if the manifest declares an entry point
957
- if (manifest.build?.entryPoint) {
958
- // TODO(#5128) - this should come from backend
959
- const assetPath =
960
- manifest.build.assetPath ?? [manifest.appKey, manifest.build.version].join('@');
961
- // dispatch import_app action to load the application script
962
- this.#state.next(actions.importApp([assetPath, manifest.build.entryPoint].join('/')));
963
- } else {
964
- console.error(
965
- `The ${manifest.appKey} app is missing a entry in the manifest, upload a build for your app before continuing`,
966
- );
967
- }
968
- }),
969
- );
970
- });
971
- }
972
-
973
- /** @inheritdoc */
974
- public getAppModuleAsync(allow_cache = true): Promise<AppScriptModule> {
975
- // when allow_cache is true, use first emitted value, otherwise use last emitted value
976
- const operator = allow_cache ? firstValueFrom : lastValueFrom;
977
- return operator(this.getAppModule(!allow_cache));
978
- }
979
-
980
- /** Releases resources held by this app instance. */
981
- public dispose: VoidFunction;
982
- }
983
-
984
- export default App;