@equinor/fusion-framework-app 13.1.0 → 13.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.
- package/dist/esm/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +15 -12
- package/CHANGELOG.md +0 -2034
- package/docs/bookmarks.md +0 -18
- package/docs/http-clients.md +0 -121
- package/docs/testing.md +0 -105
- package/src/AppConfigurator.ts +0 -280
- package/src/AppConfiguratorError.ts +0 -34
- package/src/AppModulesConfiguredEvent.ts +0 -43
- package/src/AppModulesInitializedEvent.ts +0 -40
- package/src/__tests__/AppConfigurator.test.ts +0 -49
- package/src/__tests__/mock/AppMockConfigurator.test.ts +0 -96
- package/src/__tests__/mock/mock-app.test.ts +0 -112
- package/src/__tests__/mock/msal-hoisting.test.ts +0 -54
- package/src/configure-modules.ts +0 -71
- package/src/enable-bookmark.ts +0 -95
- package/src/enable-state.ts +0 -49
- package/src/index.ts +0 -34
- package/src/initialize-app-modules.ts +0 -98
- package/src/mock/AppMockConfigurator.ts +0 -218
- package/src/mock/enable-app-manifest-mock.ts +0 -62
- package/src/mock/index.ts +0 -21
- package/src/mock/mock-app-modules.ts +0 -109
- package/src/types.ts +0 -145
- package/src/utils.ts +0 -50
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -42
- package/vitest.config.ts +0 -10
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { firstValueFrom, take } from 'rxjs';
|
|
3
|
-
import { AppConfigurator } from '../AppConfigurator';
|
|
4
|
-
import type { AnyModule } from '@equinor/fusion-framework-module';
|
|
5
|
-
import { SemanticVersion } from '@equinor/fusion-framework-module';
|
|
6
|
-
|
|
7
|
-
describe('AppConfigurator', () => {
|
|
8
|
-
let configurator: AppConfigurator;
|
|
9
|
-
|
|
10
|
-
// Create a mock module for testing
|
|
11
|
-
const createMockModule = (name: string, version = '1.0.0'): AnyModule => ({
|
|
12
|
-
name,
|
|
13
|
-
version: new SemanticVersion(version),
|
|
14
|
-
initialize: () => ({ mockInstance: true }),
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
// Mock environment object
|
|
18
|
-
const mockEnv = {
|
|
19
|
-
manifest: {
|
|
20
|
-
appKey: 'test-app',
|
|
21
|
-
displayName: 'Test App',
|
|
22
|
-
description: 'A test application',
|
|
23
|
-
type: 'standalone' as const,
|
|
24
|
-
build: {
|
|
25
|
-
version: '1.0.0',
|
|
26
|
-
entryPoint: 'index.js',
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
beforeEach(() => {
|
|
32
|
-
configurator = new AppConfigurator(mockEnv);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('Event Name Prefixing', () => {
|
|
36
|
-
it('should prefix event names with "AppConfigurator::"', async () => {
|
|
37
|
-
// Trigger event by adding a config
|
|
38
|
-
configurator.addConfig({
|
|
39
|
-
module: createMockModule('test', '1.0.0'),
|
|
40
|
-
configure: () => {},
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// Wait for the first event to be emitted
|
|
44
|
-
const event = await firstValueFrom(configurator.event$.pipe(take(1)));
|
|
45
|
-
|
|
46
|
-
expect(event.name).toMatch(/^AppConfigurator::/);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
});
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { AppConfig } from '@equinor/fusion-framework-module-app';
|
|
4
|
-
import { enableTelemetry } from '@equinor/fusion-framework-module-telemetry';
|
|
5
|
-
|
|
6
|
-
import { AppConfigurator } from '../../AppConfigurator.js';
|
|
7
|
-
import { AppMockConfigurator } from '../../mock/AppMockConfigurator.js';
|
|
8
|
-
|
|
9
|
-
const mockEnv = {
|
|
10
|
-
manifest: {
|
|
11
|
-
appKey: 'test-app',
|
|
12
|
-
displayName: 'Test App',
|
|
13
|
-
description: 'A test application',
|
|
14
|
-
type: 'standalone' as const,
|
|
15
|
-
build: {
|
|
16
|
-
version: '1.0.0',
|
|
17
|
-
entryPoint: 'index.js',
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
describe('AppMockConfigurator', () => {
|
|
23
|
-
it('is a real AppConfigurator', () => {
|
|
24
|
-
const configurator = new AppMockConfigurator(mockEnv);
|
|
25
|
-
|
|
26
|
-
expect(configurator).toBeInstanceOf(AppMockConfigurator);
|
|
27
|
-
expect(configurator).toBeInstanceOf(AppConfigurator);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('constructs without throwing when env.config declares endpoints', async () => {
|
|
31
|
-
// the base AppConfigurator constructor auto-registers these via addConfig,
|
|
32
|
-
// before this class's own fields (#pinnedModules) are initialized
|
|
33
|
-
const env = {
|
|
34
|
-
...mockEnv,
|
|
35
|
-
config: new AppConfig({
|
|
36
|
-
endpoints: { status: { url: 'https://status.example.com', scopes: [] } },
|
|
37
|
-
}),
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const configurator = new AppMockConfigurator(env);
|
|
41
|
-
configurator.http.addMiddleware(async (uri, init, next) =>
|
|
42
|
-
uri === 'https://status.example.com/health' ? Response.json({ ok: true }) : next(uri, init),
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
enableTelemetry(configurator);
|
|
46
|
-
const modules = await configurator.initialize();
|
|
47
|
-
|
|
48
|
-
await expect(modules.http.createClient('status').json('/health')).resolves.toEqual({
|
|
49
|
-
ok: true,
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('exposes the same http configurator the http module is built from', async () => {
|
|
54
|
-
const configurator = new AppMockConfigurator(mockEnv);
|
|
55
|
-
|
|
56
|
-
configurator.http.configureClient('catalog', { baseUri: 'https://api.example.com' });
|
|
57
|
-
configurator.http.addMiddleware(async (uri, init, next) =>
|
|
58
|
-
uri === 'https://api.example.com/items' ? Response.json([{ id: 1 }]) : next(uri, init),
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
// msal's config schema requires a telemetry module, normally wired by configureModules
|
|
62
|
-
enableTelemetry(configurator);
|
|
63
|
-
const modules = await configurator.initialize();
|
|
64
|
-
|
|
65
|
-
await expect(modules.http.createClient('catalog').json('/items')).resolves.toEqual([{ id: 1 }]);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('exposes the same msal configurator the auth module is built from', async () => {
|
|
69
|
-
const configurator = new AppMockConfigurator(mockEnv);
|
|
70
|
-
|
|
71
|
-
configurator.msal.setAccount({ name: 'Ada Lovelace' });
|
|
72
|
-
|
|
73
|
-
// msal's config schema requires a telemetry module, normally wired by configureModules
|
|
74
|
-
enableTelemetry(configurator);
|
|
75
|
-
const modules = await configurator.initialize();
|
|
76
|
-
|
|
77
|
-
expect(modules.auth.account?.name).toBe('Ada Lovelace');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('answers a client registered through configureHttpClient via addMiddleware', async () => {
|
|
81
|
-
const configurator = new AppMockConfigurator(mockEnv);
|
|
82
|
-
|
|
83
|
-
configurator.configureHttpClient('status-api', { baseUri: 'https://status.example.com' });
|
|
84
|
-
configurator.http.addMiddleware(async (uri, init, next) =>
|
|
85
|
-
uri === 'https://status.example.com/status' ? Response.json({ ok: true }) : next(uri, init),
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
// msal's config schema requires a telemetry module, normally wired by configureModules
|
|
89
|
-
enableTelemetry(configurator);
|
|
90
|
-
const modules = await configurator.initialize();
|
|
91
|
-
|
|
92
|
-
await expect(modules.http.createClient('status-api').json('/status')).resolves.toEqual({
|
|
93
|
-
ok: true,
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
});
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import type { Fusion } from '@equinor/fusion-framework';
|
|
4
|
-
import { mockFramework } from '@equinor/fusion-framework/mock';
|
|
5
|
-
import { AppConfig, type AppModule } from '@equinor/fusion-framework-module-app';
|
|
6
|
-
|
|
7
|
-
import { AppMockConfigurator } from '../../mock/AppMockConfigurator.js';
|
|
8
|
-
import { mockAppModules } from '../../mock/mock-app-modules.js';
|
|
9
|
-
|
|
10
|
-
const env = {
|
|
11
|
-
manifest: {
|
|
12
|
-
appKey: 'test-app',
|
|
13
|
-
displayName: 'Test App',
|
|
14
|
-
description: 'A test application',
|
|
15
|
-
type: 'standalone' as const,
|
|
16
|
-
build: {
|
|
17
|
-
version: '1.0.0',
|
|
18
|
-
entryPoint: 'index.js',
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
config: new AppConfig({ environment: { foo: 'bar' } }),
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
describe('mockApp', () => {
|
|
25
|
-
it('initializes the app module pipeline with no configuration', async () => {
|
|
26
|
-
const modules = await mockAppModules(undefined, env);
|
|
27
|
-
|
|
28
|
-
expect(modules.event).toBeDefined();
|
|
29
|
-
expect(modules.auth).toBeDefined();
|
|
30
|
-
expect(modules.http).toBeDefined();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('passes a real AppConfigurator to the callback', async () => {
|
|
34
|
-
expect.assertions(1);
|
|
35
|
-
|
|
36
|
-
await mockAppModules((configurator) => {
|
|
37
|
-
expect(configurator).toBeInstanceOf(AppMockConfigurator);
|
|
38
|
-
}, env);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('answers a service-discovery-resolved client from the app’s own mocked http module', async () => {
|
|
42
|
-
const modules = await mockAppModules((configurator) => {
|
|
43
|
-
configurator.useFrameworkServiceClient('portal-api');
|
|
44
|
-
// matches against the full resolved URL, so the host is part of the match to keep this
|
|
45
|
-
// from also answering a different client's request
|
|
46
|
-
configurator.http.addMiddleware(async (uri, init, next) =>
|
|
47
|
-
uri === 'https://portal-api.fusion.test/items'
|
|
48
|
-
? Response.json([{ id: 1 }])
|
|
49
|
-
: next(uri, init),
|
|
50
|
-
);
|
|
51
|
-
}, env);
|
|
52
|
-
|
|
53
|
-
await expect(modules.http.createClient('portal-api').json('/items')).resolves.toEqual([
|
|
54
|
-
{ id: 1 },
|
|
55
|
-
]);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('reuses an already-mocked fusion instance instead of creating a new one', async () => {
|
|
59
|
-
expect.assertions(1);
|
|
60
|
-
|
|
61
|
-
const fusion = await mockFramework((configurator) => {
|
|
62
|
-
configurator.msal.setAccount({ name: 'Ada Lovelace' });
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
await mockAppModules(
|
|
66
|
-
(_configurator, { fusion: parent }) => {
|
|
67
|
-
expect(parent.modules.auth.account?.name).toBe('Ada Lovelace');
|
|
68
|
-
},
|
|
69
|
-
env,
|
|
70
|
-
fusion,
|
|
71
|
-
);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('awaits an async configure callback before initialize resolves', async () => {
|
|
75
|
-
const modules = await mockAppModules(async (configurator) => {
|
|
76
|
-
await Promise.resolve();
|
|
77
|
-
configurator.msal.setAccount({ name: 'Ada Lovelace' });
|
|
78
|
-
}, env);
|
|
79
|
-
|
|
80
|
-
expect(modules.auth.account?.name).toBe('Ada Lovelace');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('serves this app’s own manifest and config through the default parent’s app module', async () => {
|
|
84
|
-
expect.assertions(2);
|
|
85
|
-
|
|
86
|
-
await mockAppModules(async (_configurator, { fusion }) => {
|
|
87
|
-
// the default parent always has `app` enabled; typed as plain `Fusion` since callers
|
|
88
|
-
// may pass in a parent without it, so the module set is narrowed just for this assertion
|
|
89
|
-
const { app } = (fusion as Fusion<[AppModule]>).modules;
|
|
90
|
-
app.setCurrentApp(env.manifest.appKey);
|
|
91
|
-
|
|
92
|
-
await expect(app.current?.getManifestAsync()).resolves.toMatchObject({
|
|
93
|
-
appKey: env.manifest.appKey,
|
|
94
|
-
displayName: env.manifest.displayName,
|
|
95
|
-
});
|
|
96
|
-
await expect(app.current?.getConfigAsync()).resolves.toMatchObject({
|
|
97
|
-
environment: env.config.environment,
|
|
98
|
-
});
|
|
99
|
-
}, env);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('falls through to the real client for a manifest not matching this app’s own', async () => {
|
|
103
|
-
expect.assertions(1);
|
|
104
|
-
|
|
105
|
-
await mockAppModules(async (_configurator, { fusion }) => {
|
|
106
|
-
const { app } = (fusion as Fusion<[AppModule]>).modules;
|
|
107
|
-
app.setCurrentApp('some-other-app');
|
|
108
|
-
|
|
109
|
-
await expect(app.current?.getManifestAsync()).rejects.toThrow();
|
|
110
|
-
}, env);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { mockFramework } from '@equinor/fusion-framework/mock';
|
|
4
|
-
import type { AuthenticationResult } from '@equinor/fusion-framework-module-msal';
|
|
5
|
-
|
|
6
|
-
import { mockAppModules } from '../../mock/mock-app-modules.js';
|
|
7
|
-
|
|
8
|
-
const env = {
|
|
9
|
-
manifest: {
|
|
10
|
-
appKey: 'test-app',
|
|
11
|
-
displayName: 'Test App',
|
|
12
|
-
description: 'A test application',
|
|
13
|
-
type: 'standalone' as const,
|
|
14
|
-
},
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
describe('msal hoisting', () => {
|
|
18
|
-
it('delegates acquireToken to the parent’s auth module instead of building its own client', async () => {
|
|
19
|
-
const fusion = await mockFramework();
|
|
20
|
-
const result = { accessToken: 'parent-issued-token' } as AuthenticationResult;
|
|
21
|
-
vi.spyOn(fusion.modules.auth, 'acquireToken').mockResolvedValue(result);
|
|
22
|
-
|
|
23
|
-
const modules = await mockAppModules(undefined, env, fusion);
|
|
24
|
-
|
|
25
|
-
// the app's own `auth` is a distinct (proxying) object, not the parent's instance itself
|
|
26
|
-
expect(modules.auth).not.toBe(fusion.modules.auth);
|
|
27
|
-
await expect(
|
|
28
|
-
modules.auth.acquireToken({ request: { scopes: ['User.Read'] } }),
|
|
29
|
-
).resolves.toMatchObject({ accessToken: 'parent-issued-token' });
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('surfaces the parent’s acquisition failures instead of falling back to its own client', async () => {
|
|
33
|
-
const fusion = await mockFramework();
|
|
34
|
-
vi.spyOn(fusion.modules.auth, 'acquireToken').mockRejectedValue(
|
|
35
|
-
new Error('acquisition failed'),
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
const modules = await mockAppModules(undefined, env, fusion);
|
|
39
|
-
|
|
40
|
-
await expect(modules.auth.acquireToken({ request: { scopes: ['User.Read'] } })).rejects.toThrow(
|
|
41
|
-
'acquisition failed',
|
|
42
|
-
);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('reflects the parent’s signed-in account rather than signing in its own', async () => {
|
|
46
|
-
const fusion = await mockFramework((configurator) => {
|
|
47
|
-
configurator.msal.setAccount({ name: 'Ada Lovelace' });
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const modules = await mockAppModules(undefined, env, fusion);
|
|
51
|
-
|
|
52
|
-
expect(modules.auth.account?.name).toBe('Ada Lovelace');
|
|
53
|
-
});
|
|
54
|
-
});
|
package/src/configure-modules.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Application module configuration factory
|
|
3
|
-
*
|
|
4
|
-
* Provides the core factory function for configuring and initializing
|
|
5
|
-
* application-specific modules in the Fusion framework.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { Fusion } from '@equinor/fusion-framework';
|
|
9
|
-
import type { AnyModule } from '@equinor/fusion-framework-module';
|
|
10
|
-
|
|
11
|
-
import { AppConfigurator } from './AppConfigurator';
|
|
12
|
-
|
|
13
|
-
import type { AppModulesInstance, AppModuleInitiator, AppEnv } from './types';
|
|
14
|
-
import { initializeAppModules } from './initialize-app-modules';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Create an application module initializer for a Fusion application.
|
|
18
|
-
*
|
|
19
|
-
* `configureModules` is the primary entry point for setting up an application’s
|
|
20
|
-
* module pipeline. It returns an async function that, when called with the Fusion
|
|
21
|
-
* instance and the application environment, will:
|
|
22
|
-
*
|
|
23
|
-
* 1. Create an {@link AppConfigurator} with the provided environment.
|
|
24
|
-
* 2. Wire up telemetry scoped to the application.
|
|
25
|
-
* 3. Invoke the optional user-supplied configuration callback.
|
|
26
|
-
* 4. Initialize all registered modules and dispatch an `onAppModulesLoaded` event.
|
|
27
|
-
*
|
|
28
|
-
* @template TModules - Additional application-specific modules to register.
|
|
29
|
-
* @template TRef - The Fusion instance type, used as a configuration reference.
|
|
30
|
-
* @template TEnv - The application environment descriptor (manifest, config, basename).
|
|
31
|
-
*
|
|
32
|
-
* @param cb - Optional configuration callback invoked before modules are initialized.
|
|
33
|
-
* Use this to register HTTP clients, enable bookmarks, or add custom modules.
|
|
34
|
-
* @returns An async initializer function that accepts `{ fusion, env }` and resolves
|
|
35
|
-
* with the fully initialized application module instance.
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```ts
|
|
39
|
-
* import { configureModules } from '@equinor/fusion-framework-app';
|
|
40
|
-
*
|
|
41
|
-
* const initialize = configureModules((configurator, { fusion, env }) => {
|
|
42
|
-
* configurator.useFrameworkServiceClient('my-service');
|
|
43
|
-
* });
|
|
44
|
-
*
|
|
45
|
-
* // Later, during app bootstrap:
|
|
46
|
-
* const modules = await initialize({ fusion, env });
|
|
47
|
-
* ```
|
|
48
|
-
*/
|
|
49
|
-
export const configureModules =
|
|
50
|
-
<
|
|
51
|
-
TModules extends Array<AnyModule> | never,
|
|
52
|
-
TRef extends Fusion = Fusion,
|
|
53
|
-
TEnv extends AppEnv = AppEnv,
|
|
54
|
-
>(
|
|
55
|
-
cb?: AppModuleInitiator<TModules, TRef, TEnv>,
|
|
56
|
-
): ((args: { fusion: TRef; env: TEnv }) => Promise<AppModulesInstance<TModules>>) =>
|
|
57
|
-
/**
|
|
58
|
-
* Async initializer that bootstraps application modules.
|
|
59
|
-
*
|
|
60
|
-
* @param args - Object containing the Fusion instance and the application environment.
|
|
61
|
-
* @param args.fusion - The active Fusion framework instance.
|
|
62
|
-
* @param args.env - The application environment with manifest, config, and basename.
|
|
63
|
-
* @returns The fully initialized application module instance.
|
|
64
|
-
*/
|
|
65
|
-
async (args: { fusion: TRef; env: TEnv }): Promise<AppModulesInstance<TModules>> => {
|
|
66
|
-
// Create app configurator
|
|
67
|
-
const configurator = new AppConfigurator<TModules, TRef['modules'], TEnv>(args.env);
|
|
68
|
-
return initializeAppModules(configurator, cb, args);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
export default configureModules;
|
package/src/enable-bookmark.ts
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
BookmarkModule,
|
|
3
|
-
BookmarkPayloadGenerator,
|
|
4
|
-
} from '@equinor/fusion-framework-module-bookmark';
|
|
5
|
-
import type { IAppConfigurator } from './AppConfigurator';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Enable the bookmark module for a Fusion application.
|
|
9
|
-
*
|
|
10
|
-
* Adds bookmark support by wiring the portal’s bookmark provider into the
|
|
11
|
-
* application’s module set. Payload generators registered by the application
|
|
12
|
-
* are automatically cleaned up when the module is disposed, preventing memory
|
|
13
|
-
* leaks across application load/unload cycles.
|
|
14
|
-
*
|
|
15
|
-
* Import this function from `@equinor/fusion-framework-app/enable-bookmark` or, for
|
|
16
|
-
* React apps, from `@equinor/fusion-framework-react-app/bookmark`.
|
|
17
|
-
*
|
|
18
|
-
* @remarks
|
|
19
|
-
* - The portal must expose a bookmark provider on `ref.bookmark`; if it is
|
|
20
|
-
* missing, an error is logged and the module initializes as a no-op.
|
|
21
|
-
* - The `@equinor/fusion-framework-module-bookmark` package must be installed,
|
|
22
|
-
* but do **not** call its `enableBookmark` directly in app code — use this
|
|
23
|
-
* app-level enabler instead.
|
|
24
|
-
*
|
|
25
|
-
* @param config - The application configurator to register the bookmark module on.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```ts
|
|
29
|
-
* import { configureModules } from '@equinor/fusion-framework-app';
|
|
30
|
-
* import { enableBookmark } from '@equinor/fusion-framework-app/enable-bookmark';
|
|
31
|
-
*
|
|
32
|
-
* const initialize = configureModules((configurator) => {
|
|
33
|
-
* enableBookmark(configurator);
|
|
34
|
-
* });
|
|
35
|
-
* ```
|
|
36
|
-
*/
|
|
37
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
-
export const enableBookmark = (config: IAppConfigurator): void => {
|
|
39
|
-
// collection of cleanup functions
|
|
40
|
-
const cleanupFunctions = new Set<VoidFunction>();
|
|
41
|
-
config.addConfig({
|
|
42
|
-
module: {
|
|
43
|
-
name: 'bookmark',
|
|
44
|
-
initialize(args) {
|
|
45
|
-
// get the bookmark provider from the ref (portal)
|
|
46
|
-
const provider = args.ref?.bookmark;
|
|
47
|
-
// Bail out early if the portal did not expose a bookmark provider.
|
|
48
|
-
if (!provider) {
|
|
49
|
-
console.error('Bookmark provider not found');
|
|
50
|
-
return {};
|
|
51
|
-
}
|
|
52
|
-
// create a proxy to intercept the addPayloadGenerator method
|
|
53
|
-
return new Proxy(provider, {
|
|
54
|
-
get(target, prop) {
|
|
55
|
-
// Intercept specific property access to wrap bookmark lifecycle management.
|
|
56
|
-
switch (prop) {
|
|
57
|
-
case 'addPayloadGenerator':
|
|
58
|
-
return (generator: BookmarkPayloadGenerator) => {
|
|
59
|
-
// catch the teardown function and add it to the cleanup functions
|
|
60
|
-
const cleanupHandler = target.addPayloadGenerator(generator);
|
|
61
|
-
cleanupFunctions.add(cleanupHandler);
|
|
62
|
-
// wrap the teardown function to remove it from the cleanup functions
|
|
63
|
-
return () => {
|
|
64
|
-
cleanupFunctions.delete(cleanupHandler);
|
|
65
|
-
cleanupHandler();
|
|
66
|
-
};
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* If the property is not addPayloadGenerator, we want to access the property on the provider
|
|
71
|
-
*
|
|
72
|
-
* @remarks we can not use the Reflect API to access the property, as the provider is a proxy
|
|
73
|
-
* and the Reflect API will not work as expected (can not access private properties)
|
|
74
|
-
*/
|
|
75
|
-
if (prop in target) {
|
|
76
|
-
// if the property is a function, bind it to the provider
|
|
77
|
-
if (typeof target[prop] === 'function') {
|
|
78
|
-
return target[prop].bind(target);
|
|
79
|
-
}
|
|
80
|
-
// access the property
|
|
81
|
-
return target[prop];
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
},
|
|
86
|
-
dispose() {
|
|
87
|
-
// Run all registered cleanup functions to prevent memory leaks on module disposal.
|
|
88
|
-
for (const teardown of cleanupFunctions) {
|
|
89
|
-
teardown();
|
|
90
|
-
}
|
|
91
|
-
cleanupFunctions.clear();
|
|
92
|
-
},
|
|
93
|
-
} satisfies BookmarkModule,
|
|
94
|
-
});
|
|
95
|
-
};
|
package/src/enable-state.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { enableStateModule } from '@equinor/fusion-framework-module-state';
|
|
2
|
-
import type { IStateModuleConfigurator } from '@equinor/fusion-framework-module-state';
|
|
3
|
-
import type { IAppConfigurator } from './AppConfigurator';
|
|
4
|
-
import type { AnyModule } from '@equinor/fusion-framework-module';
|
|
5
|
-
import type { FusionModulesInstance } from '@equinor/fusion-framework';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Enables state management for the application with persistent storage.
|
|
9
|
-
*
|
|
10
|
-
* This is a thin, app-scoped convenience wrapper around `enableStateModule` — it registers
|
|
11
|
-
* the state module on the app's configurator so app code can reach it via the app namespace,
|
|
12
|
-
* and scopes the module's default storage to this app's own `manifest.appKey` (so unrelated
|
|
13
|
-
* apps or widgets hosted alongside it never share its state). The state module resolves a
|
|
14
|
-
* local PouchDB database, optionally synced with the Fusion App State backend when service
|
|
15
|
-
* discovery and auth are configured; call `setStorage` on the configurator to override it.
|
|
16
|
-
*
|
|
17
|
-
* @warning Local storage is NOT encrypted. Do not store sensitive data such as passwords,
|
|
18
|
-
* tokens, personal information, or any data that requires security protection.
|
|
19
|
-
*
|
|
20
|
-
* @see {@link https://github.com/equinor/fusion-framework/blob/main/packages/modules/state/README.md | State Module Documentation} for comprehensive usage examples and API reference.
|
|
21
|
-
*
|
|
22
|
-
* @template M - Array of modules to be configured.
|
|
23
|
-
* @template R - The fusion modules instance type.
|
|
24
|
-
* @param configurator - The application configurator to enable state management on.
|
|
25
|
-
* @param configure - Optional config callback, receiving the module's `IStateModuleConfigurator`.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```typescript
|
|
29
|
-
* import { enableState } from '@equinor/fusion-framework-app';
|
|
30
|
-
*
|
|
31
|
-
* export const configure = (configurator) => {
|
|
32
|
-
* enableState(configurator);
|
|
33
|
-
* };
|
|
34
|
-
*
|
|
35
|
-
* // Later in your app, access the state provider
|
|
36
|
-
* const stateProvider = modules.state;
|
|
37
|
-
* await stateProvider.storeItem({ key: 'user-preference', value: { theme: 'dark' } });
|
|
38
|
-
* const item = await stateProvider.getItem('user-preference');
|
|
39
|
-
* ```
|
|
40
|
-
*/
|
|
41
|
-
export function enableState<M extends AnyModule[], R extends FusionModulesInstance>(
|
|
42
|
-
configurator: IAppConfigurator<M, R>,
|
|
43
|
-
configure?: (builder: IStateModuleConfigurator) => void | Promise<void>,
|
|
44
|
-
): void {
|
|
45
|
-
enableStateModule(configurator, async (builder) => {
|
|
46
|
-
builder.setName(configurator.manifest.appKey);
|
|
47
|
-
await configure?.(builder);
|
|
48
|
-
});
|
|
49
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @packageDocumentation
|
|
3
|
-
*
|
|
4
|
-
* `@equinor/fusion-framework-app` provides the configuration and initialization
|
|
5
|
-
* layer for Fusion applications. Use this package to set up application modules,
|
|
6
|
-
* configure HTTP clients, enable bookmarks, and integrate with telemetry and
|
|
7
|
-
* service discovery.
|
|
8
|
-
*
|
|
9
|
-
* The main entry points are:
|
|
10
|
-
*
|
|
11
|
-
* - {@link configureModules} — factory that creates an application initializer
|
|
12
|
-
* - {@link AppConfigurator} / {@link IAppConfigurator} — configurator for registering modules and HTTP clients
|
|
13
|
-
* - Type aliases such as {@link AppModuleInitiator}, {@link AppEnv}, and {@link AppRenderFn}
|
|
14
|
-
*
|
|
15
|
-
* Bookmark support is available via the `@equinor/fusion-framework-app/enable-bookmark`
|
|
16
|
-
* sub-path export.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
export { AppConfigurator, IAppConfigurator, AppConfiguratorConstructor } from './AppConfigurator';
|
|
20
|
-
|
|
21
|
-
export * from './types';
|
|
22
|
-
|
|
23
|
-
export { configureModules, default } from './configure-modules';
|
|
24
|
-
|
|
25
|
-
export { AppConfiguratorError } from './AppConfiguratorError';
|
|
26
|
-
|
|
27
|
-
export { AppModulesConfiguredEvent } from './AppModulesConfiguredEvent';
|
|
28
|
-
|
|
29
|
-
export { AppModulesInitializedEvent } from './AppModulesInitializedEvent';
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @deprecated Use {@link configureModules} instead. This alias will be removed in a future major version.
|
|
33
|
-
*/
|
|
34
|
-
export { configureModules as initAppModules } from './configure-modules';
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import type { Fusion } from '@equinor/fusion-framework';
|
|
2
|
-
import type { AnyModule } from '@equinor/fusion-framework-module';
|
|
3
|
-
import {
|
|
4
|
-
enableTelemetry,
|
|
5
|
-
type MetadataExtractor,
|
|
6
|
-
} from '@equinor/fusion-framework-module-telemetry';
|
|
7
|
-
|
|
8
|
-
import type { AppConfigurator } from './AppConfigurator';
|
|
9
|
-
import type { AppModulesInstance, AppEnv } from './types';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Runs the telemetry wiring, the caller's configuration callback and module
|
|
13
|
-
* initialization against an already constructed configurator.
|
|
14
|
-
*
|
|
15
|
-
* @remarks
|
|
16
|
-
* Extracted so `mockAppModules` (`@equinor/fusion-framework-app/mock`) can drive the
|
|
17
|
-
* exact same pipeline against an `AppMockConfigurator` instead of reimplementing
|
|
18
|
-
* it — the same way `FrameworkConfigurator` and `FrameworkMockConfigurator`
|
|
19
|
-
* share the framework's `init`.
|
|
20
|
-
*
|
|
21
|
-
* @param configurator - The (real or mock) app configurator to run the pipeline on.
|
|
22
|
-
* @param cb - Configuration callback invoked before module initialization, or `undefined` to skip it.
|
|
23
|
-
* @param args - Object containing the Fusion instance and the application environment.
|
|
24
|
-
* @returns The fully initialized application module instance.
|
|
25
|
-
* @template TModules - Application module descriptors beyond the default set.
|
|
26
|
-
* @template TRef - The parent Fusion instance type.
|
|
27
|
-
* @template TEnv - The application environment descriptor.
|
|
28
|
-
* @template TConfigurator - The (real or mock) `AppConfigurator` subclass driving the pipeline.
|
|
29
|
-
*/
|
|
30
|
-
export async function initializeAppModules<
|
|
31
|
-
TModules extends Array<AnyModule> | never,
|
|
32
|
-
TRef extends Fusion = Fusion,
|
|
33
|
-
TEnv extends AppEnv = AppEnv,
|
|
34
|
-
// Widened beyond the plain `AppConfigurator` so callers such as `mockAppModules` can
|
|
35
|
-
// drive the same pipeline against a subclass (e.g. `AppMockConfigurator`) and have `cb`
|
|
36
|
-
// typed against that subclass rather than the base `IAppConfigurator` interface.
|
|
37
|
-
TConfigurator extends AppConfigurator<TModules, TRef['modules'], TEnv> = AppConfigurator<
|
|
38
|
-
TModules,
|
|
39
|
-
TRef['modules'],
|
|
40
|
-
TEnv
|
|
41
|
-
>,
|
|
42
|
-
>(
|
|
43
|
-
configurator: TConfigurator,
|
|
44
|
-
cb:
|
|
45
|
-
| ((configurator: TConfigurator, args: { fusion: TRef; env: TEnv }) => void | Promise<void>)
|
|
46
|
-
| undefined,
|
|
47
|
-
args: { fusion: TRef; env: TEnv },
|
|
48
|
-
): Promise<AppModulesInstance<TModules>> {
|
|
49
|
-
const { fusion } = args;
|
|
50
|
-
|
|
51
|
-
// Extract telemetry metadata from app manifest for tracking and debugging
|
|
52
|
-
const metadataExtractor: MetadataExtractor = () => {
|
|
53
|
-
return {
|
|
54
|
-
fusion: {
|
|
55
|
-
type: 'app-telemetry',
|
|
56
|
-
app: {
|
|
57
|
-
key: args.env.manifest?.appKey || 'unknown-app',
|
|
58
|
-
version: args.env.manifest?.build?.version || 'unknown-version',
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// Enable telemetry collection for module configuration events
|
|
65
|
-
// attachConfiguratorEvents automatically prefixes events with configurator class name
|
|
66
|
-
enableTelemetry(configurator, {
|
|
67
|
-
attachConfiguratorEvents: true,
|
|
68
|
-
configure: (builder) => {
|
|
69
|
-
builder.setMetadata(metadataExtractor);
|
|
70
|
-
builder.setParent(fusion.modules.telemetry);
|
|
71
|
-
// Scope telemetry to 'app' level for app-specific event filtering
|
|
72
|
-
builder.setDefaultScope(['app']);
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// Allow user configuration callback to run before module initialization
|
|
77
|
-
if (cb) {
|
|
78
|
-
await Promise.resolve(cb(configurator, args));
|
|
79
|
-
}
|
|
80
|
-
// Type cast is safe because AppConfigurator.initialize() returns the exact module
|
|
81
|
-
// instance that was registered and configured above. The intermediate 'unknown'
|
|
82
|
-
// cast is necessary due to TypeScript's generic inference limitations with the
|
|
83
|
-
// configurator's initialization chain, but the runtime value is guaranteed to match.
|
|
84
|
-
const modules: AppModulesInstance<TModules> = (await configurator.initialize(
|
|
85
|
-
args.fusion.modules,
|
|
86
|
-
)) as unknown as AppModulesInstance<TModules>;
|
|
87
|
-
|
|
88
|
-
// Dispatch app modules loaded event for app lifecycle tracking
|
|
89
|
-
// TODO(#5061): remove check after fusion-cli is updated (app module is not enabled in fusion-cli)
|
|
90
|
-
if (args.env.manifest?.appKey) {
|
|
91
|
-
modules.event.dispatchEvent('onAppModulesLoaded', {
|
|
92
|
-
detail: { appKey: args.env.manifest.appKey, manifest: args.env.manifest, modules },
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
return modules;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export default initializeAppModules;
|