@equinor/fusion-framework-module-msal 10.0.1 → 11.0.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/README.md +17 -368
  3. package/dist/esm/MsalClient.js +9 -0
  4. package/dist/esm/MsalClient.js.map +1 -1
  5. package/dist/esm/MsalConfigurator.js +178 -88
  6. package/dist/esm/MsalConfigurator.js.map +1 -1
  7. package/dist/esm/MsalProvider.js +20 -1
  8. package/dist/esm/MsalProvider.js.map +1 -1
  9. package/dist/esm/__tests__/MsalConfigurator.test.js +75 -0
  10. package/dist/esm/__tests__/MsalConfigurator.test.js.map +1 -1
  11. package/dist/esm/__tests__/create-proxy-provider.test.js +53 -0
  12. package/dist/esm/__tests__/create-proxy-provider.test.js.map +1 -0
  13. package/dist/esm/__tests__/mock/msal-mock.test.js +399 -0
  14. package/dist/esm/__tests__/mock/msal-mock.test.js.map +1 -0
  15. package/dist/esm/create-client-log-callback.js +1 -0
  16. package/dist/esm/create-client-log-callback.js.map +1 -1
  17. package/dist/esm/create-proxy-provider.js +5 -0
  18. package/dist/esm/create-proxy-provider.js.map +1 -1
  19. package/dist/esm/index.js +5 -0
  20. package/dist/esm/index.js.map +1 -1
  21. package/dist/esm/mock/MsalMockClient.js +467 -0
  22. package/dist/esm/mock/MsalMockClient.js.map +1 -0
  23. package/dist/esm/mock/MsalMockConfigurator.js +187 -0
  24. package/dist/esm/mock/MsalMockConfigurator.js.map +1 -0
  25. package/dist/esm/mock/create-mock-token.js +60 -0
  26. package/dist/esm/mock/create-mock-token.js.map +1 -0
  27. package/dist/esm/mock/create-msal-mock-client.js +20 -0
  28. package/dist/esm/mock/create-msal-mock-client.js.map +1 -0
  29. package/dist/esm/mock/decode-jwt-segment.js +22 -0
  30. package/dist/esm/mock/decode-jwt-segment.js.map +1 -0
  31. package/dist/esm/mock/index.js +30 -0
  32. package/dist/esm/mock/index.js.map +1 -0
  33. package/dist/esm/mock/module.js +38 -0
  34. package/dist/esm/mock/module.js.map +1 -0
  35. package/dist/esm/module.js +3 -2
  36. package/dist/esm/module.js.map +1 -1
  37. package/dist/esm/msal-config-schema.js +37 -0
  38. package/dist/esm/msal-config-schema.js.map +1 -0
  39. package/dist/esm/telemetry-config-schema.js +16 -0
  40. package/dist/esm/telemetry-config-schema.js.map +1 -0
  41. package/dist/esm/util/normalize-uri.js.map +1 -1
  42. package/dist/esm/v2/Logger.js +173 -0
  43. package/dist/esm/v2/Logger.js.map +1 -0
  44. package/dist/esm/v2/create-proxy-client.js +6 -0
  45. package/dist/esm/v2/create-proxy-client.js.map +1 -1
  46. package/dist/esm/v2/create-proxy-provider.js +4 -0
  47. package/dist/esm/v2/create-proxy-provider.js.map +1 -1
  48. package/dist/esm/v2/types.js +3 -124
  49. package/dist/esm/v2/types.js.map +1 -1
  50. package/dist/esm/v4/create-proxy-provider.js +1 -0
  51. package/dist/esm/v4/create-proxy-provider.js.map +1 -1
  52. package/dist/esm/v4/types.js.map +1 -1
  53. package/dist/esm/version.js +1 -1
  54. package/dist/esm/version.js.map +1 -1
  55. package/dist/esm/versioning/VersionError.js.map +1 -1
  56. package/dist/esm/versioning/resolve-version.js +4 -1
  57. package/dist/esm/versioning/resolve-version.js.map +1 -1
  58. package/dist/tsconfig.tsbuildinfo +1 -1
  59. package/dist/types/MsalClient.d.ts +4 -0
  60. package/dist/types/MsalConfigurator.d.ts +97 -20
  61. package/dist/types/MsalProvider.d.ts +13 -0
  62. package/dist/types/__tests__/create-proxy-provider.test.d.ts +1 -0
  63. package/dist/types/__tests__/mock/msal-mock.test.d.ts +1 -0
  64. package/dist/types/create-proxy-provider.d.ts +3 -0
  65. package/dist/types/index.d.ts +5 -0
  66. package/dist/types/mock/MsalMockClient.d.ts +270 -0
  67. package/dist/types/mock/MsalMockConfigurator.d.ts +156 -0
  68. package/dist/types/mock/create-mock-token.d.ts +54 -0
  69. package/dist/types/mock/create-msal-mock-client.d.ts +14 -0
  70. package/dist/types/mock/decode-jwt-segment.d.ts +13 -0
  71. package/dist/types/mock/index.d.ts +29 -0
  72. package/dist/types/mock/module.d.ts +35 -0
  73. package/dist/types/msal-config-schema.d.ts +64 -0
  74. package/dist/types/static.d.ts +1 -1
  75. package/dist/types/telemetry-config-schema.d.ts +8 -0
  76. package/dist/types/v2/IAuthClient.interface.d.ts +2 -2
  77. package/dist/types/v2/Logger.d.ts +92 -0
  78. package/dist/types/v2/MsalProvider.interface.d.ts +1 -1
  79. package/dist/types/v2/types.d.ts +3 -49
  80. package/dist/types/v4/types.d.ts +1 -3
  81. package/dist/types/version.d.ts +1 -1
  82. package/docs/api-reference.md +85 -0
  83. package/docs/auth-code-flow.md +86 -0
  84. package/docs/migration-v2-to-v4.md +115 -0
  85. package/docs/testing.md +167 -0
  86. package/docs/troubleshooting.md +17 -0
  87. package/docs/version-management.md +67 -0
  88. package/package.json +14 -7
  89. package/src/MsalClient.ts +9 -0
  90. package/src/MsalConfigurator.ts +204 -115
  91. package/src/MsalProvider.ts +20 -1
  92. package/src/__tests__/MsalConfigurator.test.ts +106 -0
  93. package/src/__tests__/create-proxy-provider.test.ts +77 -0
  94. package/src/__tests__/mock/msal-mock.test.ts +544 -0
  95. package/src/create-client-log-callback.ts +1 -0
  96. package/src/create-proxy-provider.ts +5 -0
  97. package/src/index.ts +6 -0
  98. package/src/mock/MsalMockClient.ts +599 -0
  99. package/src/mock/MsalMockConfigurator.ts +241 -0
  100. package/src/mock/create-mock-token.ts +92 -0
  101. package/src/mock/create-msal-mock-client.ts +25 -0
  102. package/src/mock/decode-jwt-segment.ts +22 -0
  103. package/src/mock/index.ts +29 -0
  104. package/src/mock/module.ts +54 -0
  105. package/src/module.ts +3 -2
  106. package/src/msal-config-schema.ts +81 -0
  107. package/src/telemetry-config-schema.ts +25 -0
  108. package/src/v2/IAuthClient.interface.ts +2 -2
  109. package/src/v2/Logger.ts +204 -0
  110. package/src/v2/MsalProvider.interface.ts +1 -1
  111. package/src/v2/create-proxy-client.ts +6 -0
  112. package/src/v2/create-proxy-provider.ts +4 -0
  113. package/src/v2/types.ts +8 -158
  114. package/src/v4/create-proxy-provider.ts +1 -0
  115. package/src/v4/types.ts +3 -3
  116. package/src/version.ts +1 -1
  117. package/src/versioning/resolve-version.ts +4 -1
  118. package/vitest.config.ts +1 -1
@@ -0,0 +1,115 @@
1
+ # Migration Guide
2
+
3
+ ## MSAL v2 to v4 Migration
4
+
5
+ This package has been upgraded from MSAL Browser v2 to v4, providing the latest security improvements and features from Microsoft.
6
+
7
+ ### What Changed in v4
8
+
9
+ **New MSAL Browser v4 Features:**
10
+ - Enhanced security with improved token management
11
+ - Better performance and memory usage
12
+ - New authentication API structure with nested request objects
13
+ - Improved error handling and retry mechanisms
14
+
15
+ **Architecture Changes:**
16
+ - **Module Hoisting**: The module uses module hoisting, meaning sub-module instances proxy the parent module instance
17
+ - **Shared Authentication State**: Authentication state is shared across all module instances
18
+ - **Async Initialization**: New `initialize()` method must be called before using the provider
19
+
20
+ ### Breaking Changes
21
+
22
+ 1. **Auto-initialization via Framework**
23
+ ```typescript
24
+ // The provider initializes automatically when framework loads
25
+ const framework = await initialize(configurator);
26
+ const auth = framework.auth; // Already initialized
27
+
28
+ // Manual initialization is only needed for standalone usage
29
+ const provider = new MsalProvider(config);
30
+ await provider.initialize();
31
+ ```
32
+
33
+ 2. **API Method Signature Updates**
34
+ - `logout()` now returns `Promise<boolean>` instead of `Promise<void>`
35
+ - `handleRedirect()` now returns `Promise<AuthenticationResult | null>` instead of `Promise<void>`
36
+ - Methods now expect nested request objects (v4 format)
37
+
38
+ 3. **Account Property Changes**
39
+ - Use `account` property (returns `AccountInfo | null`) - v4 native
40
+ - `defaultAccount` is deprecated and only available via v2 proxy layer
41
+ - Migration: Replace `defaultAccount` with `account` throughout your code
42
+
43
+ ### Migration Steps
44
+
45
+ 1. **Update Token Acquisition** (Recommended)
46
+ ```typescript
47
+ // Before (v2 format - still works via proxy)
48
+ const token = await framework.auth.acquireAccessToken({
49
+ scopes: ['api.read']
50
+ });
51
+
52
+ // After (v4 format - recommended)
53
+ const token = await framework.auth.acquireAccessToken({
54
+ request: { scopes: ['api.read'] }
55
+ });
56
+ ```
57
+
58
+ 2. **Update Logout Handling**
59
+ ```typescript
60
+ // Before
61
+ await framework.auth.logout();
62
+
63
+ // After (check return value)
64
+ const success = await framework.auth.logout();
65
+ if (success) {
66
+ // Handle successful logout
67
+ }
68
+ ```
69
+
70
+ 3. **Update Redirect Handling**
71
+ ```typescript
72
+ // Before
73
+ await framework.auth.handleRedirect();
74
+
75
+ // After (handle result)
76
+ const result = await framework.auth.handleRedirect();
77
+ if (result?.account) {
78
+ // User authenticated successfully
79
+ console.log('Logged in as:', result.account.username);
80
+ }
81
+ ```
82
+
83
+ 4. **Update Configuration** (if needed)
84
+ ```typescript
85
+ // Ensure only the root module configures MSAL
86
+ enableMSAL(configurator, (builder) => {
87
+ builder.setClientConfig({
88
+ auth: {
89
+ clientId: 'your-client-id',
90
+ tenantId: 'your-tenant-id',
91
+ redirectUri: 'https://your-app.com/callback'
92
+ }
93
+ });
94
+ builder.setRequiresAuth(true);
95
+ });
96
+ ```
97
+
98
+ 5. **Remove Duplicate Configurations**: Remove MSAL configuration from child modules
99
+
100
+ ### Backward Compatibility
101
+
102
+ The module includes a **v2 proxy layer** that automatically converts v2 API calls to v4 format. This means:
103
+ - ✅ Existing code continues to work without changes
104
+ - ✅ Legacy format `{ scopes: [] }` is still supported
105
+ - ✅ Deprecated v2 properties like `defaultAccount` are available via v2 proxy (with deprecation warnings)
106
+ - ⚠️ New v4 features require using v4 format
107
+
108
+ ### Benefits of Migration
109
+
110
+ - **Better Security**: Latest MSAL v4 security improvements and token handling
111
+ - **Improved Performance**: Faster token acquisition, better caching, reduced memory usage
112
+ - **Enhanced Error Handling**: More robust error recovery and retry mechanisms
113
+ - **Future-Proof**: Access to latest Microsoft authentication features and updates
114
+ - **Shared State**: Improved authentication state management across app scopes via module hoisting
115
+ - **Better Developer Experience**: Cleaner API, better TypeScript support, comprehensive documentation
@@ -0,0 +1,167 @@
1
+ # MSAL — test double
2
+
3
+ Authenticate in-process instead of against Entra ID.
4
+
5
+ ```typescript
6
+ import { enableMsalMock } from '@equinor/fusion-framework-module-msal/mock';
7
+
8
+ enableMsalMock(configurator);
9
+ ```
10
+
11
+ Import path: `@equinor/fusion-framework-module-msal/mock`. The entry point has **no test-runner dependency**.
12
+
13
+ ## What is substituted
14
+
15
+ > [!IMPORTANT]
16
+ > Only `IMsalClient` — the object that would contact Entra ID. The real `MsalConfigurator`, the real `MsalProvider` and the real schema validation all still run.
17
+
18
+ That distinction is the point. Scope resolution, silent-first token acquisition, account handling, proxy providers and telemetry stay on the production code path, so a test observes real provider behaviour:
19
+
20
+ ```typescript
21
+ const fusion = await mockFramework((configurator) => {
22
+ // the client is configured with *what it talks to*, exactly as in production
23
+ configurator.msal.setClientConfig({ auth: { clientId: 'my-app', tenantId: 'my-tenant' } });
24
+ });
25
+
26
+ const token = await fusion.modules.auth.acquireAccessToken();
27
+ // scope is 'my-app/.default' — resolved by the real provider, not by the test double
28
+ ```
29
+
30
+ A double that replaced the provider would have skipped that logic and reported whatever it was told to.
31
+
32
+ ## Defaults
33
+
34
+ A user named `Test User` is signed in. Tokens are real JWTs, minted in-process with a fixed issue time, so they are identical across runs and machines and can be compared or snapshotted directly.
35
+
36
+ When no client configuration is declared, a stand-in one is used, so an application boots under test without credentials it does not have.
37
+
38
+ ## Choosing the signed-in user
39
+
40
+ `MsalMockClient` takes the same `MsalClientConfig` the real `MsalClient` takes — a client configuration has no notion of who is signed in, so the user is declared separately and signed in on the client as it is built:
41
+
42
+ ```typescript
43
+ import { enableMsalMock } from '@equinor/fusion-framework-module-msal/mock';
44
+
45
+ enableMsalMock(configurator, (builder) => {
46
+ builder.setAccount({ name: 'Ada Lovelace', username: 'ada@equinor.com' });
47
+ });
48
+ ```
49
+
50
+ Pass `null` when nobody is signed in:
51
+
52
+ ```typescript
53
+ enableMsalMock(configurator, (builder) => {
54
+ builder.setAccount(null);
55
+ });
56
+ ```
57
+
58
+ `setAccount` also takes an ordinary config-builder callback, resolved while the configuration is assembled and handed the same arguments every other builder callback receives:
59
+
60
+ ```typescript
61
+ enableMsalMock(configurator, (builder) => {
62
+ builder.setAccount(async ({ hasModule }) => ({
63
+ name: hasModule('app') ? 'App User' : 'Portal User',
64
+ }));
65
+ });
66
+ ```
67
+
68
+ The user is in place **before** `MsalProvider.initialize()` runs, so the provider's own start-up path acts on it. Combined with `setRequiresAuth(true)`, a test observes the real automatic login rather than a state assigned after the fact.
69
+
70
+ `setClient` replaces the client, but not the rule: the declared user is signed in on whichever client the module authenticates through, so a mock client supplied that way receives it too.
71
+
72
+ | Option | Default | Purpose |
73
+ | --- | --- | --- |
74
+ | `name` | `Test User` | Display name |
75
+ | `username` | `test.user@equinor.com` | UPN / email |
76
+ | `userId` | `fusion-mock-user` | Object ID |
77
+ | `tenantId` | the client's configured tenant | Tenant |
78
+ | `scopes` | `fusion-mock-scope` | Granted when a request specifies none |
79
+ | `account` | derived | A preconfigured `AccountInfo` to use outright |
80
+ | `signedOut` | `false` | Start without a signed-in user |
81
+
82
+ The client tokens are issued for comes from the client configuration (`setClientConfig`), not from the user.
83
+
84
+ ### Why the user is signed in at construction
85
+
86
+ The account is put in the client's cache as the client is built — before the provider exists. That reproduces the production shape of a returning user with a live session: the provider finds an account already there and takes the branch it takes in the browser.
87
+
88
+ Assigning the account **after** `MsalProvider.initialize()` — from an `onInitialized` hook, say — looks equivalent but is not. `initialize()` exchanges an auth code, calls `handleRedirect()` and, when `requiresAuth` is set, performs an automatic login. A late assignment silently overwrites all of that, so a test asserting on the sign-in journey would be observing its own assignment rather than the framework.
89
+
90
+ ## Changing the user between tests
91
+
92
+ When a suite shares one framework instance but needs a different user per test, set the active account directly:
93
+
94
+ ```typescript
95
+ beforeEach(() => {
96
+ fusion.modules.auth.client.setActiveAccount(account);
97
+ });
98
+ ```
99
+
100
+ The mock keeps a real account cache, so this behaves the way MSAL does: `getActiveAccount`, `getAllAccounts` and `getAccount(filter)` all agree afterwards. Unlike real MSAL, an account that was never issued by a sign-in is accepted and added to the cache, which is what makes the one-liner above possible.
101
+
102
+ Signing out (`logout`, `logoutPopup`, `logoutRedirect`) removes the account from the cache rather than merely deactivating it, as MSAL does.
103
+
104
+ ## Running inside a host application
105
+
106
+ When the module is hoisted onto a host application's provider — an app inside a portal — no client is built. The app authenticates through the host's client, exactly as in production.
107
+
108
+ A user declared with `setAccount` is still honoured: it is signed in on the **host's** client, because that is the client the app authenticates through. The alternative would be for `setAccount` to silently do nothing precisely when an app is being tested inside a portal.
109
+
110
+ The session is shared, so this changes who the host sees signed in too — as it does in production. If the host does not authenticate through a mock client, `setAccount` throws rather than failing quietly.
111
+
112
+ ## Testing signed-out behaviour
113
+
114
+ Both `null` and `signedOut: true` start without a session. Silent flows then resolve empty so the provider follows its unauthenticated path, while an explicit login still succeeds — which lets a test drive the sign-in journey rather than only its end state.
115
+
116
+ They differ in what the login resolves to. `null` forgets the identity, so a login produces the default user:
117
+
118
+ ```typescript
119
+ builder.setAccount(null);
120
+ ```
121
+
122
+ `signedOut: true` keeps it, so a login produces the user the test named — which is what to reach for when the assertion is about *who* signed in:
123
+
124
+ ```typescript
125
+ builder.setAccount({ name: 'Ada Lovelace', signedOut: true });
126
+ ```
127
+
128
+ ## Mocking an individual call
129
+
130
+ > [!IMPORTANT]
131
+ > That is your test runner's job. This module ships **no mocking API**.
132
+
133
+ The mock client is a plain class with ordinary methods, so `vi.spyOn`, `bun:test`'s `spyOn` and Node's `t.mock.method` all work on it directly — with their own call assertions, argument matchers and reset semantics, which a framework-specific API would not give you.
134
+
135
+ The provider exposes the client it authenticates through, so a spy has a stable target:
136
+
137
+ ```typescript
138
+ vi.spyOn(fusion.modules.auth.client, 'acquireToken').mockResolvedValue(result);
139
+
140
+ afterEach(() => vi.restoreAllMocks());
141
+ ```
142
+
143
+ ## Minting a token directly
144
+
145
+ For code that only needs a token — an HTTP interceptor test, say — skip the client:
146
+
147
+ ```typescript
148
+ import { createMockToken } from '@equinor/fusion-framework-module-msal/mock';
149
+
150
+ const token = createMockToken({ oid: 'fusion-mock-user' });
151
+ ```
152
+
153
+ ## Exports
154
+
155
+ | Export | Purpose |
156
+ | --- | --- |
157
+ | `enableMsalMock(configurator, configure?)` | Register the module with an in-process client |
158
+ | `msalMockModule` | The module itself, for manual registration |
159
+ | `MsalMockConfigurator` | The real configurator, backed by an in-process client |
160
+ | `MsalMockClient(config)` | The in-process client, taking the same `MsalClientConfig` as `MsalClient` |
161
+ | `createMsalMockClient(config, user?)` | Convenience alias for `new MsalMockClient(config)` |
162
+ | `createMockToken(claims?)` | Mint a deterministic JWT |
163
+
164
+ ## Related
165
+
166
+ - [Module README](../README.md) — production configuration
167
+ - [`@equinor/fusion-framework/mock`](../../../framework/docs/testing.md) — mock every framework boundary at once
@@ -0,0 +1,17 @@
1
+ # Troubleshooting
2
+
3
+ ## Common Issues
4
+
5
+ | Issue | Solution |
6
+ |-------|----------|
7
+ | **Authentication Loop** | Ensure redirect URIs match your application's routing |
8
+ | **Token Acquisition Fails** | Check that required scopes are properly configured |
9
+ | **Module Not Found** | Ensure the module is properly configured and framework is initialized |
10
+ | **Multiple MSAL Instances** | Remove duplicate configurations from child modules |
11
+ | **Redirect Returns Void** | For redirect flows, use `handleRedirect()` after navigation completes |
12
+ | **Token Empty/Undefined** | Verify user is authenticated and scopes are correct |
13
+
14
+ ## Getting Help
15
+
16
+ - 📖 [MSAL Cookbook](https://github.com/equinor/fusion-framework/tree/main/cookbooks/app-react-msal) - Complete working examples
17
+ - 🐛 [Report Issues](https://github.com/equinor/fusion/issues) - Bug reports and feature requests
@@ -0,0 +1,67 @@
1
+ # Version Management
2
+
3
+ The MSAL module includes built-in version checking to ensure compatibility between different MSAL library versions.
4
+
5
+ ## Version Resolution
6
+
7
+ ```typescript
8
+ import { resolveVersion, VersionError } from '@equinor/fusion-framework-module-msal/versioning';
9
+
10
+ // Resolve and validate a version
11
+ const result = resolveVersion('2.0.0');
12
+ console.log(result.isLatest); // false
13
+ console.log(result.satisfiesLatest); // true
14
+ console.log(result.enumVersion); // MsalModuleVersion.V2
15
+ ```
16
+
17
+ ## Version Checking Behavior
18
+
19
+ - **Major Version Incompatibility**: Throws `VersionError` if requested major version is greater than latest
20
+ - **Minor Version Mismatch**: Logs warning but allows execution
21
+ - **Patch Differences**: Ignored for compatibility
22
+ - **Invalid Versions**: Throws `VersionError` with descriptive message
23
+
24
+ ## API Reference
25
+
26
+ ### `resolveVersion(version: string | SemVer): ResolvedVersion`
27
+
28
+ Resolves and validates a version string against the latest available MSAL version.
29
+
30
+ **Parameters:**
31
+ - `version` - Version string or SemVer object to resolve
32
+
33
+ **Returns:** `ResolvedVersion` object containing:
34
+ - `wantedVersion: SemVer` - The parsed requested version
35
+ - `latestVersion: SemVer` - The latest available version
36
+ - `isLatest: boolean` - Whether the version is exactly the latest
37
+ - `satisfiesLatest: boolean` - Whether the major version matches latest
38
+ - `enumVersion: MsalModuleVersion` - Corresponding enum version
39
+
40
+ **Throws:** `VersionError` for invalid or incompatible versions
41
+
42
+ ### `VersionError`
43
+
44
+ Error class for version-related issues with the following types:
45
+ - `InvalidVersion` - Requested version is not a valid semver
46
+ - `InvalidLatestVersion` - Latest version parsing failed (build issue)
47
+ - `MajorIncompatibility` - Major version is greater than latest
48
+ - `MinorMismatch` - Minor version differs (warning only)
49
+ - `PatchDifference` - Patch version differs (info only)
50
+ - `IncompatibleVersion` - General incompatibility
51
+
52
+ ## Error Handling
53
+
54
+ ```typescript
55
+ import { resolveVersion, VersionError } from '@equinor/fusion-framework-module-msal/versioning';
56
+
57
+ try {
58
+ const result = resolveVersion('3.0.0'); // Assuming latest is 2.x
59
+ } catch (error) {
60
+ if (error instanceof VersionError) {
61
+ console.error('Version error:', error.message);
62
+ console.error('Requested:', error.requestedVersion);
63
+ console.error('Latest:', error.latestVersion);
64
+ console.error('Type:', error.type);
65
+ }
66
+ }
67
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equinor/fusion-framework-module-msal",
3
- "version": "10.0.1",
3
+ "version": "11.0.0-next.0",
4
4
  "description": "Microsoft Authentication Library (MSAL) integration module for Fusion Framework",
5
5
  "main": "dist/esm/index.js",
6
6
  "types": "dist/types/index.d.ts",
@@ -16,6 +16,10 @@
16
16
  "./v4": {
17
17
  "import": "./dist/esm/v4/index.js",
18
18
  "types": "./dist/types/v4/index.d.ts"
19
+ },
20
+ "./mock": {
21
+ "import": "./dist/esm/mock/index.js",
22
+ "types": "./dist/types/mock/index.d.ts"
19
23
  }
20
24
  },
21
25
  "typesVersions": {
@@ -28,6 +32,9 @@
28
32
  ],
29
33
  "v4": [
30
34
  "dist/types/v4/index.d.ts"
35
+ ],
36
+ "mock": [
37
+ "dist/types/mock/index.d.ts"
31
38
  ]
32
39
  }
33
40
  },
@@ -48,18 +55,18 @@
48
55
  "devDependencies": {
49
56
  "@types/semver": "^7.7.1",
50
57
  "semver": "^7.7.4",
51
- "typescript": "^6.0.3",
58
+ "typescript": "^7.0.2",
52
59
  "zod": "^4.4.3",
53
- "@equinor/fusion-framework-module": "^6.1.0",
54
- "@equinor/fusion-framework-module-telemetry": "^7.0.0"
60
+ "@equinor/fusion-framework-module": "^6.1.3-next.0",
61
+ "@equinor/fusion-framework-module-telemetry": "^8.0.0-next.0"
55
62
  },
56
63
  "peerDependencies": {
57
64
  "@types/semver": "^7.0.0",
58
65
  "semver": "^7.0.0",
59
- "typescript": "^5.0.0",
66
+ "typescript": ">=5.0.0",
60
67
  "zod": "^4.0.0",
61
- "@equinor/fusion-framework-module": "^6.1.0",
62
- "@equinor/fusion-framework-module-telemetry": "^7.0.0"
68
+ "@equinor/fusion-framework-module": "^6.1.3-next.0",
69
+ "@equinor/fusion-framework-module-telemetry": "^8.0.0-next.0"
63
70
  },
64
71
  "peerDependenciesMeta": {
65
72
  "@equinor/fusion-framework-module-telemetry": {
package/src/MsalClient.ts CHANGED
@@ -145,11 +145,14 @@ export class MsalClient extends PublicClientApplication implements IMsalClient {
145
145
  * - **Redirect**: Navigates browser to Microsoft login page. Returns `void` because the browser
146
146
  * navigates to a new page. After redirect completes, the result will be available via
147
147
  * `handleRedirectPromise()` when the app loads on the new page.
148
+ *
149
+ * @throws {Error} If an invalid `options.behavior` value is provided.
148
150
  */
149
151
  async login(options: Required<LoginOptions>): Promise<LoginResult> {
150
152
  // Attempt silent authentication first if enabled
151
153
  // This provides better UX by avoiding unnecessary popups/redirects
152
154
  if (options.silent) {
155
+ // Warn early when neither an account nor login hint is available for silent SSO
153
156
  if (!options.request.account && !options.request.loginHint) {
154
157
  this.getLogger().warning(
155
158
  'No account or login hint provided, please provide an account or login hint in the request',
@@ -216,6 +219,7 @@ export class MsalClient extends PublicClientApplication implements IMsalClient {
216
219
  * ```
217
220
  */
218
221
  async logout(options?: LogoutOptions): Promise<void> {
222
+ // Warn when no account was supplied since the active account will be used instead
219
223
  if (!options?.account) {
220
224
  this.getLogger().warning(
221
225
  'No account available for logout, please provide an account in the options',
@@ -253,14 +257,18 @@ export class MsalClient extends PublicClientApplication implements IMsalClient {
253
257
  *
254
258
  * The default silent behavior is determined by presence of account in the request.
255
259
  * This provides optimal UX by minimizing unnecessary user interactions.
260
+ *
261
+ * @throws {Error} If no `request` is provided in `options`.
256
262
  */
257
263
  async acquireToken(options: AcquireTokenOptions): Promise<AcquireTokenResult> {
258
264
  const { behavior = 'redirect', silent = !!options.request?.account, request } = options;
259
265
 
266
+ // A request is required to know which scopes/account to acquire a token for
260
267
  if (!request) {
261
268
  throw new Error('No request provided, please provide a request in the options');
262
269
  }
263
270
 
271
+ // Warn when no scopes are requested, since MSAL requires at least one scope
264
272
  if (request.scopes.length === 0) {
265
273
  this.getLogger().warning(
266
274
  'No scopes provided, please provide scopes in the request option, see options.request for more information.',
@@ -271,6 +279,7 @@ export class MsalClient extends PublicClientApplication implements IMsalClient {
271
279
  // Attempt silent token acquisition first
272
280
  // This fetches from cache or uses refresh token without user interaction
273
281
  if (silent) {
282
+ // Only silent-acquire when an account is available to look up cached tokens for
274
283
  if (request.account) {
275
284
  try {
276
285
  this.getLogger().verbose(