Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@equinor/fusion-framework-module-context": patch
---

Restore path-based resolution as a fallback in `resolveInitialContext`, composed with parent-context resolution. The context-navigation plugin introduced in #4751 removed this from the module, but consumers not yet migrated to the plugin lost initial-context resolution from the URL. `resolveInitialContext` now accepts a `path` option (`extract`/`validate`) used to resolve context from the URL before falling back to the parent context.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@equinor/fusion-framework-plugin-context-navigation": patch
---

Fix `createPathAdapter` matching on non-app routes. `canHandle` now rejects a URL when `currentURL.pathname` does not parse as a valid app route (e.g. portal chrome), preventing the path adapter from incorrectly claiming ownership of URLs where there is no app route to encode context into.
7 changes: 6 additions & 1 deletion packages/modules/context/src/configurator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,12 @@ export class ContextModuleConfigurator implements IContextModuleConfigurator {
Promise.resolve({} as Partial<ContextModuleConfig>),
);

config.resolveInitialContext ??= resolveInitialContext();
config.resolveInitialContext ??= resolveInitialContext({
path: {
extract: config.extractContextIdFromPath,
validate: config.extractContextIdFromPath ? () => true : undefined,
},
});

// TODO(#5119) - make less lazy
config.client ??= await (async (): Promise<ContextModuleConfig['client']> => {
Expand Down
29 changes: 22 additions & 7 deletions packages/modules/context/src/utils/resolve-initial-context.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { ModulesInstance } from '@equinor/fusion-framework-module';
import type { ContextModule } from '../module';
import type { ContextModuleConfig } from '../configurator';
import { concat, EMPTY, of, take } from 'rxjs';
import { concat, EMPTY, first, of } from 'rxjs';
import resolveContextFromPath, { type ContextPathResolveArgs } from './resolve-context-from-path';
import type { NavigationModule } from '@equinor/fusion-framework-module-navigation';

/**
* Resolves the initial context from the parent module.
Expand All @@ -11,6 +13,7 @@ import { concat, EMPTY, of, take } from 'rxjs';
*/
export const resolveContextFromParent: ContextModuleConfig['resolveInitialContext'] = ({ ref }) => {
const parentContext = (ref as ModulesInstance<[ContextModule]>)?.context;
// check if the parent has context module
if (!parentContext) {
// No parent context available — either portal level or parent lacks context module.
return EMPTY;
Expand All @@ -22,19 +25,31 @@ export const resolveContextFromParent: ContextModuleConfig['resolveInitialContex
/**
* Resolves the initial context for a Fusion Framework context module.
*
* Attempts to resolve the initial context from the parent context provider.
* URL-based resolution is handled by the context-navigation plugin.
* will try to resolve the initial context from the path, and if that fails, it will try to resolve the context from the parent.
*
* @param options - Optional configuration for resolving the context path.
* @returns A function that accepts the module's reference and modules, and returns an Observable of the resolved initial context.
*/

// Deliberately co-located with resolveContextFromParent, which it composes with
// fusion-lint-disable-next-line single-export-per-file
export const resolveInitialContext =
(): Required<ContextModuleConfig>['resolveInitialContext'] =>
(options?: {
path?: ContextPathResolveArgs;
}): Required<ContextModuleConfig>['resolveInitialContext'] =>
({ ref, modules }) => {
// Resolve from parent context if available.
// URL-based resolution is handled by the context-navigation plugin.
return concat(resolveContextFromParent({ ref, modules })).pipe(take(1));
const { context, navigation } = modules;
// create a path resolver from the context module
const pathResolver = resolveContextFromPath(context, options?.path);
// use the path from the navigation module, or the path from the parent navigation module
const pathname =
navigation?.path.pathname ??
(ref as Partial<ModulesInstance<[NavigationModule]>>).navigation?.path.pathname;
// try to resolve the context from the path, and if that fails, try to resolve the context from the parent
return concat(
pathname ? pathResolver(pathname) : EMPTY,
resolveContextFromParent({ ref, modules }),
).pipe(first());
};

export default resolveInitialContext;
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,17 @@ export function createPathAdapter(): ContextNavigationAdapter {
* Accepts apps with explicit `'path'` strategy or no declared strategy
* (the path adapter serves as the system-wide default fallback).
*/
canHandle({ appContext, routingStrategy }: AdapterResolutionContext): boolean {
canHandle({ appContext, routingStrategy, currentURL }: AdapterResolutionContext): boolean {
// Custom generators mean the app owns its URL shape — the custom adapter handles it, not us
if (hasCustomContextGenerators(appContext)) {
return false;
}

// Not an app route (e.g. portal chrome) — nothing for this adapter to encode context into
if (!parseAppRoute(currentURL.pathname)) {
return false;
}

const declared = routingStrategy;
// App explicitly opted into path-segment routing — take ownership
if (declared === 'path') {
Expand Down
Loading