@equinor/fusion-framework-module-msal 5.1.2 → 6.0.0-next.1

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 (144) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/README.md +237 -40
  3. package/dist/esm/MsalClient.interface.js +2 -0
  4. package/dist/esm/MsalClient.interface.js.map +1 -0
  5. package/dist/esm/MsalClient.js +215 -0
  6. package/dist/esm/MsalClient.js.map +1 -0
  7. package/dist/esm/MsalConfigurator.js +248 -0
  8. package/dist/esm/MsalConfigurator.js.map +1 -0
  9. package/dist/esm/MsalProvider.interface.js +2 -0
  10. package/dist/esm/MsalProvider.interface.js.map +1 -0
  11. package/dist/esm/MsalProvider.js +525 -0
  12. package/dist/esm/MsalProvider.js.map +1 -0
  13. package/dist/esm/MsalProxyProvider.interface.js +2 -0
  14. package/dist/esm/MsalProxyProvider.interface.js.map +1 -0
  15. package/dist/esm/__tests__/versioning/resolve-version.test.js +29 -38
  16. package/dist/esm/__tests__/versioning/resolve-version.test.js.map +1 -1
  17. package/dist/esm/create-client-log-callback.js +87 -0
  18. package/dist/esm/create-client-log-callback.js.map +1 -0
  19. package/dist/esm/create-proxy-provider.js +84 -0
  20. package/dist/esm/create-proxy-provider.js.map +1 -0
  21. package/dist/esm/index.js +1 -1
  22. package/dist/esm/index.js.map +1 -1
  23. package/dist/esm/module.js +64 -16
  24. package/dist/esm/module.js.map +1 -1
  25. package/dist/esm/static.js +32 -2
  26. package/dist/esm/static.js.map +1 -1
  27. package/dist/esm/types.js +9 -0
  28. package/dist/esm/types.js.map +1 -1
  29. package/dist/esm/util/compare-origin.js +11 -0
  30. package/dist/esm/util/compare-origin.js.map +1 -0
  31. package/dist/esm/{v2/client/util/url.js → util/normalize-uri.js} +1 -10
  32. package/dist/esm/util/normalize-uri.js.map +1 -0
  33. package/dist/esm/{v2/client/util/browser.js → util/redirect.js} +1 -1
  34. package/dist/esm/util/redirect.js.map +1 -0
  35. package/dist/esm/v2/IAuthClient.interface.js +2 -0
  36. package/dist/esm/v2/IAuthClient.interface.js.map +1 -0
  37. package/dist/esm/v2/IPublicClientApplication.interface.js +2 -0
  38. package/dist/esm/v2/IPublicClientApplication.interface.js.map +1 -0
  39. package/dist/esm/v2/MsalProvider.interface.js +2 -0
  40. package/dist/esm/v2/MsalProvider.interface.js.map +1 -0
  41. package/dist/esm/v2/create-proxy-client.js +155 -0
  42. package/dist/esm/v2/create-proxy-client.js.map +1 -0
  43. package/dist/esm/v2/create-proxy-provider.js +140 -0
  44. package/dist/esm/v2/create-proxy-provider.js.map +1 -0
  45. package/dist/esm/v2/map-account-info.js +18 -0
  46. package/dist/esm/v2/map-account-info.js.map +1 -0
  47. package/dist/esm/v2/map-authentication-result.js +22 -0
  48. package/dist/esm/v2/map-authentication-result.js.map +1 -0
  49. package/dist/esm/version.js +1 -1
  50. package/dist/esm/version.js.map +1 -1
  51. package/dist/esm/versioning/resolve-version.js +28 -16
  52. package/dist/esm/versioning/resolve-version.js.map +1 -1
  53. package/dist/tsconfig.tsbuildinfo +1 -1
  54. package/dist/types/MsalClient.d.ts +141 -0
  55. package/dist/types/MsalClient.interface.d.ts +103 -0
  56. package/dist/types/MsalConfigurator.d.ts +147 -0
  57. package/dist/types/MsalProvider.d.ts +291 -0
  58. package/dist/types/MsalProvider.interface.d.ts +159 -0
  59. package/dist/types/MsalProxyProvider.interface.d.ts +52 -0
  60. package/dist/types/create-client-log-callback.d.ts +38 -0
  61. package/dist/types/create-proxy-provider.d.ts +19 -0
  62. package/dist/types/index.d.ts +5 -4
  63. package/dist/types/module.d.ts +70 -4
  64. package/dist/types/static.d.ts +32 -1
  65. package/dist/types/types.d.ts +14 -6
  66. package/dist/types/util/redirect.d.ts +1 -0
  67. package/dist/types/v2/IAuthClient.interface.d.ts +68 -0
  68. package/dist/types/v2/IPublicClientApplication.interface.d.ts +68 -0
  69. package/dist/types/v2/MsalProvider.interface.d.ts +85 -0
  70. package/dist/types/v2/create-proxy-client.d.ts +22 -0
  71. package/dist/types/v2/create-proxy-provider.d.ts +24 -0
  72. package/dist/types/v2/map-account-info.d.ts +9 -0
  73. package/dist/types/v2/map-authentication-result.d.ts +9 -0
  74. package/dist/types/v2/types.d.ts +12 -0
  75. package/dist/types/version.d.ts +1 -1
  76. package/dist/types/versioning/resolve-version.d.ts +1 -1
  77. package/package.json +11 -6
  78. package/src/MsalClient.interface.ts +121 -0
  79. package/src/MsalClient.ts +274 -0
  80. package/src/MsalConfigurator.ts +289 -0
  81. package/src/MsalProvider.interface.ts +175 -0
  82. package/src/MsalProvider.ts +597 -0
  83. package/src/MsalProxyProvider.interface.ts +71 -0
  84. package/src/__tests__/versioning/resolve-version.test.ts +29 -42
  85. package/src/create-client-log-callback.ts +101 -0
  86. package/src/create-proxy-provider.ts +89 -0
  87. package/src/index.ts +6 -7
  88. package/src/module.ts +88 -20
  89. package/src/static.ts +32 -3
  90. package/src/types.ts +15 -7
  91. package/src/util/compare-origin.ts +11 -0
  92. package/src/{v2/client/util/url.ts → util/normalize-uri.ts} +0 -10
  93. package/src/v2/IAuthClient.interface.ts +91 -0
  94. package/src/v2/IPublicClientApplication.interface.ts +71 -0
  95. package/src/v2/MsalProvider.interface.ts +92 -0
  96. package/src/v2/create-proxy-client.ts +186 -0
  97. package/src/v2/create-proxy-provider.ts +156 -0
  98. package/src/v2/map-account-info.ts +20 -0
  99. package/src/v2/map-authentication-result.ts +24 -0
  100. package/src/v2/types.ts +12 -0
  101. package/src/version.ts +1 -1
  102. package/src/versioning/resolve-version.ts +35 -28
  103. package/tsconfig.json +3 -0
  104. package/dist/esm/v2/client/behavior.js +0 -5
  105. package/dist/esm/v2/client/behavior.js.map +0 -1
  106. package/dist/esm/v2/client/client.js +0 -142
  107. package/dist/esm/v2/client/client.js.map +0 -1
  108. package/dist/esm/v2/client/create-auth-client.js +0 -36
  109. package/dist/esm/v2/client/create-auth-client.js.map +0 -1
  110. package/dist/esm/v2/client/index.js +0 -5
  111. package/dist/esm/v2/client/index.js.map +0 -1
  112. package/dist/esm/v2/client/log/console.js +0 -45
  113. package/dist/esm/v2/client/log/console.js.map +0 -1
  114. package/dist/esm/v2/client/request.js +0 -2
  115. package/dist/esm/v2/client/request.js.map +0 -1
  116. package/dist/esm/v2/client/util/browser.js.map +0 -1
  117. package/dist/esm/v2/client/util/url.js.map +0 -1
  118. package/dist/esm/v2/configurator.js +0 -42
  119. package/dist/esm/v2/configurator.js.map +0 -1
  120. package/dist/esm/v2/index.js +0 -3
  121. package/dist/esm/v2/index.js.map +0 -1
  122. package/dist/esm/v2/provider.js +0 -115
  123. package/dist/esm/v2/provider.js.map +0 -1
  124. package/dist/types/v2/client/behavior.d.ts +0 -13
  125. package/dist/types/v2/client/client.d.ts +0 -89
  126. package/dist/types/v2/client/create-auth-client.d.ts +0 -27
  127. package/dist/types/v2/client/index.d.ts +0 -5
  128. package/dist/types/v2/client/log/console.d.ts +0 -28
  129. package/dist/types/v2/client/request.d.ts +0 -65
  130. package/dist/types/v2/configurator.d.ts +0 -32
  131. package/dist/types/v2/index.d.ts +0 -2
  132. package/dist/types/v2/provider.d.ts +0 -59
  133. package/src/v2/client/behavior.ts +0 -14
  134. package/src/v2/client/client.ts +0 -180
  135. package/src/v2/client/create-auth-client.ts +0 -48
  136. package/src/v2/client/index.ts +0 -8
  137. package/src/v2/client/log/console.ts +0 -58
  138. package/src/v2/client/request.ts +0 -66
  139. package/src/v2/configurator.ts +0 -58
  140. package/src/v2/index.ts +0 -2
  141. package/src/v2/provider.ts +0 -178
  142. /package/dist/types/{v2/client/util/browser.d.ts → util/compare-origin.d.ts} +0 -0
  143. /package/dist/types/{v2/client/util/url.d.ts → util/normalize-uri.d.ts} +0 -0
  144. /package/src/{v2/client/util/browser.ts → util/redirect.ts} +0 -0
@@ -1,17 +1,12 @@
1
1
  import { describe, it, expect, vi } from 'vitest';
2
2
  import { SemVer } from 'semver';
3
- import { resolveVersion } from '../../versioning/resolve-version';
4
3
 
5
- // Mock the static module to avoid test failures when package version changes
6
- // This ensures tests remain stable regardless of package.json version bumps
7
- vi.mock('../../static', () => ({
8
- MsalModuleVersion: {
9
- V2: 'v2',
10
- Latest: '4.0.9', // Fixed version for consistent testing
11
- },
4
+ // Mock the version first (hoisted)
5
+ vi.mock('../../version', () => ({
6
+ version: '6.1.2-next.0+1493919587',
12
7
  }));
13
8
 
14
- // Import the mocked version
9
+ import { resolveVersion } from '../../versioning/resolve-version';
15
10
  import { MsalModuleVersion } from '../../static';
16
11
 
17
12
  describe('resolveVersion', () => {
@@ -23,12 +18,12 @@ describe('resolveVersion', () => {
23
18
  wantedVersion: expect.any(SemVer),
24
19
  latestVersion: expect.any(SemVer),
25
20
  isLatest: false,
26
- satisfiesLatest: false, // 2.x is not compatible with 4.x
21
+ satisfiesLatest: false, // 2.x is not compatible with 5.x
27
22
  enumVersion: MsalModuleVersion.V2,
28
23
  });
29
24
 
30
25
  expect(result.wantedVersion.version).toBe('2.1.0');
31
- expect(result.latestVersion.version).toBe('4.0.9');
26
+ expect(result.latestVersion.version).toBe('6.1.2');
32
27
  });
33
28
 
34
29
  it('should resolve with SemVer object', () => {
@@ -36,14 +31,14 @@ describe('resolveVersion', () => {
36
31
  const result = resolveVersion(semver);
37
32
 
38
33
  expect(result.wantedVersion).toBe(semver);
39
- expect(result.satisfiesLatest).toBe(false); // 2.x is not compatible with 4.x
34
+ expect(result.satisfiesLatest).toBe(false); // 2.x is not compatible with 5.x
40
35
  });
41
36
 
42
37
  it('should resolve latest version when no version provided', () => {
43
38
  const result = resolveVersion();
44
39
 
45
- expect(result.wantedVersion.version).toBe('4.0.9');
46
- expect(result.latestVersion.version).toBe('4.0.9');
40
+ expect(result.wantedVersion.version).toBe('6.1.2');
41
+ expect(result.latestVersion.version).toBe('6.1.2');
47
42
  expect(result.isLatest).toBe(true);
48
43
  expect(result.satisfiesLatest).toBe(true);
49
44
  });
@@ -51,7 +46,7 @@ describe('resolveVersion', () => {
51
46
  it('should resolve latest version when empty string provided', () => {
52
47
  const result = resolveVersion('');
53
48
 
54
- expect(result.wantedVersion.version).toBe('4.0.9');
49
+ expect(result.wantedVersion.version).toBe('6.1.2');
55
50
  expect(result.isLatest).toBe(true);
56
51
  });
57
52
 
@@ -59,54 +54,46 @@ describe('resolveVersion', () => {
59
54
  const result = resolveVersion('2.5.0');
60
55
 
61
56
  expect(result.enumVersion).toBe(MsalModuleVersion.V2);
62
- expect(result.satisfiesLatest).toBe(false); // 2.x is not compatible with 4.x
57
+ expect(result.satisfiesLatest).toBe(false); // 2.x is not compatible with 5.x
63
58
  });
64
59
  });
65
60
 
66
61
  describe('version comparison and warnings', () => {
67
62
  it('should identify version states correctly', () => {
68
63
  // Exact latest version
69
- expect(resolveVersion('4.0.9').isLatest).toBe(true);
64
+ expect(resolveVersion('6.1.2-next.0+1493919587').isLatest).toBe(true);
70
65
 
71
66
  // Patch difference (satisfies but not latest)
72
- const patchResult = resolveVersion('4.0.8');
67
+ const patchResult = resolveVersion('6.1.1');
73
68
  expect(patchResult.isLatest).toBe(false);
74
69
  expect(patchResult.satisfiesLatest).toBe(true);
75
70
  expect(patchResult.warnings).toBeUndefined();
76
71
 
77
- // Minor difference (satisfies with warning)
78
- const minorResult = resolveVersion('4.1.0');
72
+ // Minor difference (satisfies)
73
+ const minorResult = resolveVersion('6.0.0');
79
74
  expect(minorResult.isLatest).toBe(false);
80
75
  expect(minorResult.satisfiesLatest).toBe(true);
81
- expect(minorResult.warnings).toBeDefined();
82
- expect(minorResult.warnings?.[0]).toContain(
83
- 'Requested minor version 1 is different from the latest minor version 0',
84
- );
85
76
 
86
- // Major difference (doesn't satisfy with warning)
87
- const majorResult = resolveVersion('5.0.0');
77
+ // Major difference (doesn't satisfy)
78
+ const majorResult = resolveVersion('7.0.0');
88
79
  expect(majorResult.satisfiesLatest).toBe(false);
89
- expect(majorResult.warnings).toBeDefined();
90
- expect(majorResult.warnings?.[0]).toContain(
91
- 'Requested major version 5 is greater than the latest major version 4',
92
- );
93
80
  });
94
81
 
95
82
  it('should handle versions without matching enum with warnings', () => {
96
- // Lower major version
83
+ // Lower major version - should map to V2
97
84
  const lowerResult = resolveVersion('3.0.0');
98
- expect(lowerResult.enumVersion).toBe('4.0.9'); // Falls back to latest
85
+ expect(lowerResult.enumVersion).toBe(MsalModuleVersion.V2);
99
86
  expect(lowerResult.warnings).toBeDefined();
100
87
  expect(lowerResult.warnings?.[0]).toContain(
101
- 'Requested major version 3 is behind the latest major version 4',
88
+ 'Requested major version 3 is behind the latest major version 6',
102
89
  );
103
90
 
104
- // Zero version
91
+ // Zero version - should map to V2
105
92
  const zeroResult = resolveVersion('0.0.0');
106
- expect(zeroResult.enumVersion).toBe('4.0.9'); // Falls back to latest
93
+ expect(zeroResult.enumVersion).toBe(MsalModuleVersion.V2);
107
94
  expect(zeroResult.warnings).toBeDefined();
108
95
  expect(zeroResult.warnings?.[0]).toContain(
109
- 'Requested major version 0 is behind the latest major version 4',
96
+ 'Requested major version 0 is behind the latest major version 6',
110
97
  );
111
98
  });
112
99
  });
@@ -122,8 +109,8 @@ describe('resolveVersion', () => {
122
109
  expect(result.warnings?.[0]).toContain(
123
110
  `Failed to parse requested version "${invalidVersion}"`,
124
111
  );
125
- // Should fall back to latest version
126
- expect(result.wantedVersion.version).toBe('4.0.9');
112
+ // Should fall back to latest version (coerced)
113
+ expect(result.wantedVersion.version).toBe('6.1.2');
127
114
  });
128
115
  });
129
116
 
@@ -137,9 +124,9 @@ describe('resolveVersion', () => {
137
124
  describe('edge cases', () => {
138
125
  it('should handle complex version strings with pre-release and build metadata', () => {
139
126
  const testCases = [
140
- { input: '4.0.9-beta.1', expected: '4.0.9' },
141
- { input: '4.0.9+build.123', expected: '4.0.9' },
142
- { input: '4.0.9-beta.1.alpha.2+build.123.456', expected: '4.0.9' },
127
+ { input: '6.1.2-next.0+1493919587-beta.1', expected: '6.1.2' },
128
+ { input: '6.1.2-next.0+1493919587+build.123', expected: '6.1.2' },
129
+ { input: '6.1.2-next.0+1493919587-beta.1.alpha.2+build.123.456', expected: '6.1.2' },
143
130
  ];
144
131
 
145
132
  testCases.forEach(({ input, expected }) => {
@@ -155,7 +142,7 @@ describe('resolveVersion', () => {
155
142
 
156
143
  // Latest version should be consistent
157
144
  expect(result1.latestVersion.version).toBe(result2.latestVersion.version);
158
- expect(result1.latestVersion.version).toBe('4.0.9');
145
+ expect(result1.latestVersion.version).toBe('6.1.2');
159
146
 
160
147
  // Structure should be consistent
161
148
  expect(result1).toHaveProperty('wantedVersion');
@@ -0,0 +1,101 @@
1
+ import { type ILoggerCallback, LogLevel } from '@azure/msal-browser';
2
+ import {
3
+ type ITelemetryProvider,
4
+ TelemetryLevel,
5
+ } from '@equinor/fusion-framework-module-telemetry';
6
+
7
+ /**
8
+ * Maps MSAL log levels to corresponding telemetry levels.
9
+ *
10
+ * This mapping ensures consistent log level translation between MSAL's logging
11
+ * system and the framework's telemetry system.
12
+ */
13
+ const levelMap: Partial<Record<LogLevel, TelemetryLevel>> = {
14
+ [LogLevel.Verbose]: TelemetryLevel.Debug,
15
+ [LogLevel.Info]: TelemetryLevel.Information,
16
+ [LogLevel.Warning]: TelemetryLevel.Warning,
17
+ [LogLevel.Error]: TelemetryLevel.Error,
18
+ };
19
+
20
+ /**
21
+ * Parses MSAL log message into structured components.
22
+ *
23
+ * MSAL log messages follow the format:
24
+ * [timestamp] : [correlationId] : [package] : [level] - [component] - [message]
25
+ *
26
+ * @param message - Raw MSAL log message
27
+ * @returns Parsed message components
28
+ */
29
+ const parseMsalMessage = (message: string) => {
30
+ // Match the structured format: [timestamp] : [correlationId] : [package] : [level] - [component] - [message]
31
+ const match = message.match(
32
+ /^\[([^\]]+)\]\s*:\s*\[([^\]]*)\]\s*:\s*([^:]+)\s*:\s*(\w+)\s*-\s*([^-]+)\s*-\s*(.*)$/,
33
+ );
34
+
35
+ if (match) {
36
+ const [, _timestamp, _correlationId, packageInfo, _logLevel, component, logMessage] = match;
37
+ return {
38
+ package: packageInfo.trim() ?? undefined,
39
+ component: component.trim() ?? undefined,
40
+ message: logMessage.trim() ?? undefined,
41
+ };
42
+ }
43
+
44
+ // Fallback for non-structured messages
45
+ return { message: message.trim() };
46
+ };
47
+
48
+ /**
49
+ * Creates a telemetry callback function for MSAL logging integration.
50
+ *
51
+ * This function bridges MSAL's internal logging system with the framework's
52
+ * telemetry infrastructure. It maps MSAL log levels to telemetry levels and
53
+ * forwards log events to the provided telemetry provider with structured metadata.
54
+ *
55
+ * The callback function returned by this method will be called by MSAL whenever
56
+ * a log event occurs, allowing for centralized logging and monitoring of
57
+ * authentication-related events.
58
+ *
59
+ * @param provider - Telemetry provider instance to receive log events
60
+ * @param metadata - Additional metadata to include with each telemetry event (e.g., module version, environment)
61
+ * @param scope - Telemetry scope identifiers for categorization and filtering
62
+ * @returns Logger callback function for MSAL that forwards events to telemetry provider
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * const callback = createClientLogCallback(
67
+ * telemetryProvider,
68
+ * { module: 'msal', version: '4.0.0' },
69
+ * ['framework', 'authentication']
70
+ * );
71
+ *
72
+ * // Use with MSAL configuration
73
+ * const config = {
74
+ * system: {
75
+ * loggerOptions: {
76
+ * loggerCallback: callback,
77
+ * piiLoggingEnabled: false
78
+ * }
79
+ * }
80
+ * };
81
+ * ```
82
+ */
83
+ export const createClientLogCallback = (
84
+ provider: ITelemetryProvider,
85
+ metadata: Record<string, unknown>,
86
+ scope: string[],
87
+ ): ILoggerCallback | undefined => {
88
+ return (level: LogLevel, message: string, _containsPii: boolean) => {
89
+ const parsedMessage = parseMsalMessage(message);
90
+
91
+ provider.trackEvent({
92
+ name: 'MsalClient',
93
+ level: levelMap[level] ?? TelemetryLevel.Information,
94
+ scope,
95
+ metadata,
96
+ properties: {
97
+ ...parsedMessage,
98
+ },
99
+ });
100
+ };
101
+ };
@@ -0,0 +1,89 @@
1
+ import type { IMsalProvider } from './MsalProvider.interface';
2
+ import { MsalModuleVersion } from './static';
3
+ import { resolveVersion } from './versioning/resolve-version';
4
+ import { createProxyProvider as createProxyProvider_v2 } from './v2/create-proxy-provider';
5
+
6
+ /**
7
+ * Creates a proxy provider for version compatibility.
8
+ *
9
+ * This function handles the creation of proxy providers that maintain
10
+ * backward compatibility with different MSAL versions while using the
11
+ * latest MSAL v4 implementation under the hood.
12
+ *
13
+ * @param provider - The base MSAL provider instance
14
+ * @param version - The target version string (e.g., '2.0.0', '4.0.0')
15
+ * @returns A proxy provider compatible with the specified version
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const baseProvider = new MsalProvider(config);
20
+ * const v2Proxy = createProxyProvider(baseProvider, '2.0.0');
21
+ * ```
22
+ */
23
+ export function createProxyProvider<T = IMsalProvider>(
24
+ provider: IMsalProvider,
25
+ version: string,
26
+ ): T {
27
+ // Resolve the requested version to determine which proxy to create
28
+ const { enumVersion } = resolveVersion(version);
29
+
30
+ switch (enumVersion) {
31
+ case MsalModuleVersion.V2:
32
+ // Create v2-compatible proxy with legacy API adapters
33
+ return createProxyProvider_v2(provider) as T;
34
+ case MsalModuleVersion.V4:
35
+ // Create transparent proxy for v4 - passes through to original provider
36
+ // This allows v4 code to be used where any version is expected
37
+ return new Proxy(provider, {
38
+ get: (target: IMsalProvider, prop: keyof IMsalProvider) => {
39
+ switch (prop) {
40
+ case 'version': {
41
+ return target.version;
42
+ }
43
+ case 'msalVersion': {
44
+ return enumVersion;
45
+ }
46
+ case 'client': {
47
+ return target.client;
48
+ }
49
+ case 'account': {
50
+ return target.account;
51
+ }
52
+ case 'acquireAccessToken': {
53
+ return target.acquireAccessToken.bind(target);
54
+ }
55
+ case 'acquireToken': {
56
+ return target.acquireToken.bind(target);
57
+ }
58
+ case 'login': {
59
+ return target.login.bind(target);
60
+ }
61
+ case 'logout': {
62
+ return target.logout.bind(target);
63
+ }
64
+ case 'handleRedirect': {
65
+ return target.handleRedirect.bind(target);
66
+ }
67
+ case 'createProxyProvider': {
68
+ return target.createProxyProvider.bind(target);
69
+ }
70
+ case 'initialize': {
71
+ return () => {
72
+ // noop - initialize is handled by the provider, not the proxy
73
+ };
74
+ }
75
+ default: {
76
+ // backstop to prevent then from being called - this is not an async operation
77
+ if (prop === 'then') return undefined;
78
+
79
+ // Exhaustive check: TypeScript-only guard to ensure all IMsalProvider keys are handled
80
+ const exhausted: never = prop;
81
+ return (target as IMsalProvider)[exhausted];
82
+ }
83
+ }
84
+ },
85
+ }) as T;
86
+ default:
87
+ throw new Error(`Version ${version} is not supported`);
88
+ }
89
+ }
package/src/index.ts CHANGED
@@ -2,15 +2,14 @@ export {
2
2
  module,
3
3
  configureMsal,
4
4
  enableMSAL,
5
- type AuthConfigurator as IAppConfigurator,
6
- type AuthConfigFn,
7
- type IAuthProvider,
8
5
  type MsalModule,
6
+ type AuthConfigFn,
9
7
  } from './module';
10
8
 
11
- export { MsalModuleVersion } from './static';
9
+ export type { IMsalProvider } from './MsalProvider.interface';
10
+ export type { IMsalClient } from './MsalClient.interface';
11
+ export { MsalClient, type MsalClientConfig } from './MsalClient';
12
12
 
13
- export { default } from './module';
13
+ export type { AccountInfo, AuthenticationResult } from './types';
14
14
 
15
- export type { AccountInfo, AuthenticationResult } from './v2/types';
16
- export type { AuthClientConfig } from './v2/configurator';
15
+ export { default } from './module';
package/src/module.ts CHANGED
@@ -4,62 +4,116 @@ import {
4
4
  SemanticVersion,
5
5
  } from '@equinor/fusion-framework-module';
6
6
 
7
- import { MsalModuleVersion } from './static';
7
+ import { MsalConfigurator } from './MsalConfigurator';
8
+ import { MsalProvider, type IMsalProvider } from './MsalProvider';
9
+ import type { MsalClientConfig } from './MsalClient';
8
10
 
9
- import { AuthConfigurator, AuthProvider, type AuthClientConfig, type IAuthProvider } from './v2';
11
+ import { version } from './version';
10
12
 
11
- export type MsalModule = Module<'auth', IAuthProvider, AuthConfigurator, [MsalModule]>;
12
- export type { AuthConfigurator, IAuthProvider };
13
+ /**
14
+ * MSAL authentication module configuration.
15
+ *
16
+ * This module provides Microsoft Authentication Library (MSAL) integration for the
17
+ * Fusion Framework, supporting MSAL v4 with backward compatibility for v2 applications.
18
+ */
19
+ export type MsalModule = Module<'auth', IMsalProvider, MsalConfigurator, [MsalModule]>;
13
20
 
21
+ /**
22
+ * MSAL authentication module definition.
23
+ *
24
+ * This module manages authentication providers with the following initialization flow:
25
+ * 1. Check for custom provider configuration
26
+ * 2. Check for existing provider in parent module (for proxy compatibility)
27
+ * 3. Create new provider with client configuration
28
+ *
29
+ * @remarks
30
+ * The module supports proxy providers for version compatibility, allowing v4 implementations
31
+ * to work with v2-compatible code during migration periods.
32
+ */
14
33
  export const module: MsalModule = {
15
34
  name: 'auth',
16
- version: new SemanticVersion(MsalModuleVersion.Latest),
17
- configure: () => new AuthConfigurator(),
35
+ version: new SemanticVersion(version),
36
+ configure: () => new MsalConfigurator(),
18
37
  initialize: async (init) => {
19
38
  const config = await init.config.createConfigAsync(init);
20
39
 
21
- // configured to use a custom provider
40
+ // Priority 1: Use custom provider if explicitly configured
22
41
  if (config.provider) {
23
42
  return config.provider;
24
43
  }
25
44
 
26
- // check if the provider is defined in the parent module
27
- const hostProvider = init.ref?.auth as AuthProvider;
45
+ // Priority 2: Check if provider exists in parent module (proxy compatibility)
46
+ // This allows child applications to reuse parent's authentication provider
47
+ const hostProvider = init.ref?.auth;
28
48
  if (hostProvider) {
29
49
  try {
30
- return hostProvider.createProxyProvider(config.version);
50
+ const proxyProvider = hostProvider.createProxyProvider(config.version);
51
+ return proxyProvider;
31
52
  } catch (error) {
32
53
  console.error('MsalModule::Failed to create proxy provider', error);
33
- // just to make sure during migration that the provider is not set
54
+ // Fallback to host provider to prevent app breakage during migration
55
+ // TODO: Consider throwing error instead once all apps are migrated to v4
34
56
  return hostProvider;
35
57
  }
36
58
  }
37
59
 
60
+ // Priority 3: Validate client configuration is provided
38
61
  if (!config.client) {
39
62
  throw new Error(
40
63
  'Client configuration is required when provider is not in the parent module nor defined',
41
64
  );
42
65
  }
43
66
 
44
- // create a new provider
45
- const authProvider = new AuthProvider(config.client);
67
+ // Create new MSAL provider instance
68
+ const provider = new MsalProvider(config);
46
69
 
47
- if (config.requiresAuth) {
48
- await authProvider.handleRedirect();
49
- await authProvider.login({ onlyIfRequired: true });
50
- }
70
+ // Initialize the provider (handles redirect callbacks, SSO, etc.)
71
+ await provider.initialize();
51
72
 
52
- return authProvider;
73
+ return provider;
53
74
  },
54
75
  };
55
76
 
77
+ /**
78
+ * Configuration function type for MSAL module setup.
79
+ *
80
+ * This function receives a builder object with methods to configure the MSAL client
81
+ * and authentication requirements.
82
+ */
56
83
  export type AuthConfigFn = (builder: {
57
- setClientConfig: (config: AuthClientConfig) => void;
84
+ /**
85
+ * Set MSAL client configuration
86
+ * @param config - Client configuration with tenant ID, client ID, etc.
87
+ */
88
+ setClientConfig: (config: MsalClientConfig) => void;
89
+ /**
90
+ * Set whether authentication is required for the application
91
+ * @param requiresAuth - If true, app will attempt automatic login on initialization
92
+ */
58
93
  setRequiresAuth: (requiresAuth: boolean) => void;
59
94
  }) => void;
60
95
 
96
+ /**
97
+ * Enables MSAL authentication module in the framework.
98
+ *
99
+ * This is a convenience function that adds the MSAL module configuration to the
100
+ * framework configurator with optional configuration callback.
101
+ *
102
+ * @param configurator - The framework modules configurator instance
103
+ * @param configure - Optional configuration callback for MSAL setup
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * enableMSAL(frameworkConfigurator, (builder) => {
108
+ * builder.setClientConfig({
109
+ * auth: { clientId: 'your-client-id', tenantId: 'your-tenant-id' }
110
+ * });
111
+ * builder.setRequiresAuth(true);
112
+ * });
113
+ * ```
114
+ */
61
115
  export const enableMSAL = (
62
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
116
+ // @biome-ignore lint/suspicious/noExplicitAny: must be any to support all module types
63
117
  configurator: IModulesConfigurator<any, any>,
64
118
  configure?: AuthConfigFn,
65
119
  ): void => {
@@ -67,6 +121,20 @@ export const enableMSAL = (
67
121
  configurator.addConfig(config);
68
122
  };
69
123
 
124
+ /**
125
+ * Creates MSAL module configuration with custom setup.
126
+ *
127
+ * @param configure - Configuration callback function
128
+ * @returns Module configuration object ready for framework integration
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * const msalConfig = configureMsal((builder) => {
133
+ * builder.setClientConfig(msalClientConfig);
134
+ * builder.setRequiresAuth(true);
135
+ * });
136
+ * ```
137
+ */
70
138
  export const configureMsal = (configure: AuthConfigFn) => ({
71
139
  module,
72
140
  configure,
package/src/static.ts CHANGED
@@ -1,8 +1,37 @@
1
- import { version } from './version';
2
-
1
+ /**
2
+ * Module identifier for the MSAL authentication module.
3
+ *
4
+ * This constant is used to register and identify the MSAL module within the Fusion Framework.
5
+ */
3
6
  export const ModuleName = 'msal' as const;
4
7
 
8
+ /**
9
+ * Enumeration of supported MSAL module versions.
10
+ *
11
+ * This enum defines the available MSAL versions and provides type-safe access to version identifiers.
12
+ * The `Latest` value is automatically set to the current module version at build time.
13
+ *
14
+ * @remarks
15
+ * - `V2`: MSAL v2 compatibility (legacy support)
16
+ * - `V4`: MSAL v4 (current major version)
17
+ * - `Latest`: Always points to the current module version (5.1.0)
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import { MsalModuleVersion } from '@equinor/fusion-framework-module-msal';
22
+ *
23
+ * // Check version
24
+ * if (version === MsalModuleVersion.Latest) {
25
+ * console.log('Using latest MSAL version');
26
+ * }
27
+ *
28
+ * // Create version-specific proxy
29
+ * const proxy = provider.createProxyProvider(MsalModuleVersion.V2);
30
+ * ```
31
+ */
5
32
  export enum MsalModuleVersion {
33
+ /** MSAL v2 compatibility version */
6
34
  V2 = 'v2',
7
- Latest = version,
35
+ /** MSAL v4 (current major version) */
36
+ V4 = 'v4',
8
37
  }
package/src/types.ts CHANGED
@@ -1,8 +1,16 @@
1
- import type { SemVer } from 'semver';
2
- import type { MsalModuleVersion } from './static';
1
+ /**
2
+ * Re-exports of core MSAL types from @azure/msal-browser.
3
+ *
4
+ * This module provides convenient access to commonly used MSAL types without
5
+ * requiring direct imports from @azure/msal-browser. These types represent
6
+ * fundamental authentication entities used throughout the MSAL module.
7
+ *
8
+ * @module
9
+ */
3
10
 
4
- // this should be defined the @equinor/fusion-framework-module package
5
- export interface IProxyProvider {
6
- version: string | SemVer;
7
- createProxyProvider<T>(version: MsalModuleVersion): T;
8
- }
11
+ export {
12
+ /** Represents account information for an authenticated user */
13
+ AccountInfo,
14
+ /** Represents the result of an authentication operation including tokens and account */
15
+ AuthenticationResult,
16
+ } from '@azure/msal-browser';
@@ -0,0 +1,11 @@
1
+ import { normalizeUri } from './normalize-uri';
2
+
3
+ /**
4
+ * Compares normalized version of urls
5
+ *
6
+ * @internal
7
+ */
8
+ export const compareOrigin = (a: string, b: string): boolean => {
9
+ const url = { a: normalizeUri(a), b: normalizeUri(b) };
10
+ return url.a === url.b;
11
+ };
@@ -12,13 +12,3 @@ export const normalizeUri = (uri: string, home: string = window.location.origin)
12
12
  const { origin, pathname } = new URL(uri);
13
13
  return origin + pathname.replace(/([^:]\/)\/+/g, '$1');
14
14
  };
15
-
16
- /**
17
- * Compares normalized version of urls
18
- *
19
- * @internal
20
- */
21
- export const compareOrigin = (a: string, b: string): boolean => {
22
- const url = { a: normalizeUri(a), b: normalizeUri(b) };
23
- return url.a === url.b;
24
- };