@equinor/fusion-framework-plugin-context-navigation 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/dist/esm/ContextNavigationConfigurator.js +178 -0
  2. package/dist/esm/ContextNavigationConfigurator.js.map +1 -0
  3. package/dist/esm/__tests__/active-app-navigation-events.test.js +115 -0
  4. package/dist/esm/__tests__/active-app-navigation-events.test.js.map +1 -0
  5. package/dist/esm/__tests__/apply-navigation.test.js +152 -0
  6. package/dist/esm/__tests__/apply-navigation.test.js.map +1 -0
  7. package/dist/esm/__tests__/enable.test.js +91 -0
  8. package/dist/esm/__tests__/enable.test.js.map +1 -0
  9. package/dist/esm/__tests__/guard-handlers.test.js +82 -0
  10. package/dist/esm/__tests__/guard-handlers.test.js.map +1 -0
  11. package/dist/esm/__tests__/plugin.test.js +483 -0
  12. package/dist/esm/__tests__/plugin.test.js.map +1 -0
  13. package/dist/esm/__tests__/reconcile.test.js +164 -0
  14. package/dist/esm/__tests__/reconcile.test.js.map +1 -0
  15. package/dist/esm/adapters/create-path-adapter.js +96 -0
  16. package/dist/esm/adapters/create-path-adapter.js.map +1 -0
  17. package/dist/esm/adapters/create-query-adapter.js +83 -0
  18. package/dist/esm/adapters/create-query-adapter.js.map +1 -0
  19. package/dist/esm/adapters/custom-adapter/create-custom-adapter.js +116 -0
  20. package/dist/esm/adapters/custom-adapter/create-custom-adapter.js.map +1 -0
  21. package/dist/esm/adapters/custom-adapter/index.js +5 -0
  22. package/dist/esm/adapters/custom-adapter/index.js.map +1 -0
  23. package/dist/esm/adapters/custom-adapter/normalize-string-result.js +37 -0
  24. package/dist/esm/adapters/custom-adapter/normalize-string-result.js.map +1 -0
  25. package/dist/esm/adapters/custom-adapter/to-app-relative.js +34 -0
  26. package/dist/esm/adapters/custom-adapter/to-app-relative.js.map +1 -0
  27. package/dist/esm/adapters/custom-adapter/to-full-path.js +28 -0
  28. package/dist/esm/adapters/custom-adapter/to-full-path.js.map +1 -0
  29. package/dist/esm/adapters/index.js +4 -0
  30. package/dist/esm/adapters/index.js.map +1 -0
  31. package/dist/esm/adapters/types.js +2 -0
  32. package/dist/esm/adapters/types.js.map +1 -0
  33. package/dist/esm/apply-navigation.js +102 -0
  34. package/dist/esm/apply-navigation.js.map +1 -0
  35. package/dist/esm/constants/context-query-param-key.js +8 -0
  36. package/dist/esm/constants/context-query-param-key.js.map +1 -0
  37. package/dist/esm/constants/index.js +3 -0
  38. package/dist/esm/constants/index.js.map +1 -0
  39. package/dist/esm/constants/uuid-pattern.js +8 -0
  40. package/dist/esm/constants/uuid-pattern.js.map +1 -0
  41. package/dist/esm/create-context-navigation-plugin.js +124 -0
  42. package/dist/esm/create-context-navigation-plugin.js.map +1 -0
  43. package/dist/esm/enable-context-navigation.js +40 -0
  44. package/dist/esm/enable-context-navigation.js.map +1 -0
  45. package/dist/esm/events.js +2 -0
  46. package/dist/esm/events.js.map +1 -0
  47. package/dist/esm/guard-handlers/consume-own-nav-token.js +29 -0
  48. package/dist/esm/guard-handlers/consume-own-nav-token.js.map +1 -0
  49. package/dist/esm/guard-handlers/guard-tick-deps.js +2 -0
  50. package/dist/esm/guard-handlers/guard-tick-deps.js.map +1 -0
  51. package/dist/esm/guard-handlers/guard-tick-payload.js +2 -0
  52. package/dist/esm/guard-handlers/guard-tick-payload.js.map +1 -0
  53. package/dist/esm/guard-handlers/handle-push-mode-guard.js +44 -0
  54. package/dist/esm/guard-handlers/handle-push-mode-guard.js.map +1 -0
  55. package/dist/esm/guard-handlers/handle-replace-mode-guard.js +40 -0
  56. package/dist/esm/guard-handlers/handle-replace-mode-guard.js.map +1 -0
  57. package/dist/esm/guard-handlers/index.js +5 -0
  58. package/dist/esm/guard-handlers/index.js.map +1 -0
  59. package/dist/esm/guard-handlers/is-in-app-scope.js +19 -0
  60. package/dist/esm/guard-handlers/is-in-app-scope.js.map +1 -0
  61. package/dist/esm/helpers/get-current-url.js +11 -0
  62. package/dist/esm/helpers/get-current-url.js.map +1 -0
  63. package/dist/esm/helpers/index.js +6 -0
  64. package/dist/esm/helpers/index.js.map +1 -0
  65. package/dist/esm/helpers/normalize-path-from-url.js +14 -0
  66. package/dist/esm/helpers/normalize-path-from-url.js.map +1 -0
  67. package/dist/esm/helpers/normalize-path.js +30 -0
  68. package/dist/esm/helpers/normalize-path.js.map +1 -0
  69. package/dist/esm/helpers/resolve-adapter.js +23 -0
  70. package/dist/esm/helpers/resolve-adapter.js.map +1 -0
  71. package/dist/esm/helpers/strip-query-params.js +14 -0
  72. package/dist/esm/helpers/strip-query-params.js.map +1 -0
  73. package/dist/esm/index.js +13 -0
  74. package/dist/esm/index.js.map +1 -0
  75. package/dist/esm/operators/active-app-navigation-events.js +52 -0
  76. package/dist/esm/operators/active-app-navigation-events.js.map +1 -0
  77. package/dist/esm/reconcile.js +76 -0
  78. package/dist/esm/reconcile.js.map +1 -0
  79. package/dist/esm/sources/app-first-source.js +41 -0
  80. package/dist/esm/sources/app-first-source.js.map +1 -0
  81. package/dist/esm/sources/context-first-source.js +47 -0
  82. package/dist/esm/sources/context-first-source.js.map +1 -0
  83. package/dist/esm/sources/index.js +4 -0
  84. package/dist/esm/sources/index.js.map +1 -0
  85. package/dist/esm/sources/types.js +16 -0
  86. package/dist/esm/sources/types.js.map +1 -0
  87. package/dist/esm/types.js +2 -0
  88. package/dist/esm/types.js.map +1 -0
  89. package/dist/esm/utils/has-custom-context-generators.js +14 -0
  90. package/dist/esm/utils/has-custom-context-generators.js.map +1 -0
  91. package/dist/esm/utils/legacy-app-navigation-fix.js +45 -0
  92. package/dist/esm/utils/legacy-app-navigation-fix.js.map +1 -0
  93. package/dist/esm/utils/resolve-context-from-url.js +37 -0
  94. package/dist/esm/utils/resolve-context-from-url.js.map +1 -0
  95. package/dist/esm/utils/url/build-app-route.js +20 -0
  96. package/dist/esm/utils/url/build-app-route.js.map +1 -0
  97. package/dist/esm/utils/url/build-context-url-for-strategy.js +43 -0
  98. package/dist/esm/utils/url/build-context-url-for-strategy.js.map +1 -0
  99. package/dist/esm/utils/url/index.js +7 -0
  100. package/dist/esm/utils/url/index.js.map +1 -0
  101. package/dist/esm/utils/url/parse-app-route.js +47 -0
  102. package/dist/esm/utils/url/parse-app-route.js.map +1 -0
  103. package/dist/esm/utils/url/resolve-context-id-from-url.js +29 -0
  104. package/dist/esm/utils/url/resolve-context-id-from-url.js.map +1 -0
  105. package/dist/esm/utils/url/strip-context-query-param.js +20 -0
  106. package/dist/esm/utils/url/strip-context-query-param.js.map +1 -0
  107. package/dist/esm/version.js +3 -0
  108. package/dist/esm/version.js.map +1 -0
  109. package/dist/tsconfig.tsbuildinfo +1 -0
  110. package/dist/types/ContextNavigationConfigurator.d.ts +125 -0
  111. package/dist/types/ContextNavigationConfigurator.d.ts.map +1 -0
  112. package/dist/types/__tests__/active-app-navigation-events.test.d.ts +2 -0
  113. package/dist/types/__tests__/active-app-navigation-events.test.d.ts.map +1 -0
  114. package/dist/types/__tests__/apply-navigation.test.d.ts +2 -0
  115. package/dist/types/__tests__/apply-navigation.test.d.ts.map +1 -0
  116. package/dist/types/__tests__/enable.test.d.ts +2 -0
  117. package/dist/types/__tests__/enable.test.d.ts.map +1 -0
  118. package/dist/types/__tests__/guard-handlers.test.d.ts +2 -0
  119. package/dist/types/__tests__/guard-handlers.test.d.ts.map +1 -0
  120. package/dist/types/__tests__/plugin.test.d.ts +2 -0
  121. package/dist/types/__tests__/plugin.test.d.ts.map +1 -0
  122. package/dist/types/__tests__/reconcile.test.d.ts +2 -0
  123. package/dist/types/__tests__/reconcile.test.d.ts.map +1 -0
  124. package/dist/types/adapters/create-path-adapter.d.ts +27 -0
  125. package/dist/types/adapters/create-path-adapter.d.ts.map +1 -0
  126. package/dist/types/adapters/create-query-adapter.d.ts +34 -0
  127. package/dist/types/adapters/create-query-adapter.d.ts.map +1 -0
  128. package/dist/types/adapters/custom-adapter/create-custom-adapter.d.ts +29 -0
  129. package/dist/types/adapters/custom-adapter/create-custom-adapter.d.ts.map +1 -0
  130. package/dist/types/adapters/custom-adapter/index.d.ts +5 -0
  131. package/dist/types/adapters/custom-adapter/index.d.ts.map +1 -0
  132. package/dist/types/adapters/custom-adapter/normalize-string-result.d.ts +21 -0
  133. package/dist/types/adapters/custom-adapter/normalize-string-result.d.ts.map +1 -0
  134. package/dist/types/adapters/custom-adapter/to-app-relative.d.ts +25 -0
  135. package/dist/types/adapters/custom-adapter/to-app-relative.d.ts.map +1 -0
  136. package/dist/types/adapters/custom-adapter/to-full-path.d.ts +22 -0
  137. package/dist/types/adapters/custom-adapter/to-full-path.d.ts.map +1 -0
  138. package/dist/types/adapters/index.d.ts +5 -0
  139. package/dist/types/adapters/index.d.ts.map +1 -0
  140. package/dist/types/adapters/types.d.ts +73 -0
  141. package/dist/types/adapters/types.d.ts.map +1 -0
  142. package/dist/types/apply-navigation.d.ts +88 -0
  143. package/dist/types/apply-navigation.d.ts.map +1 -0
  144. package/dist/types/constants/context-query-param-key.d.ts +8 -0
  145. package/dist/types/constants/context-query-param-key.d.ts.map +1 -0
  146. package/dist/types/constants/index.d.ts +3 -0
  147. package/dist/types/constants/index.d.ts.map +1 -0
  148. package/dist/types/constants/uuid-pattern.d.ts +8 -0
  149. package/dist/types/constants/uuid-pattern.d.ts.map +1 -0
  150. package/dist/types/create-context-navigation-plugin.d.ts +58 -0
  151. package/dist/types/create-context-navigation-plugin.d.ts.map +1 -0
  152. package/dist/types/enable-context-navigation.d.ts +21 -0
  153. package/dist/types/enable-context-navigation.d.ts.map +1 -0
  154. package/dist/types/events.d.ts +16 -0
  155. package/dist/types/events.d.ts.map +1 -0
  156. package/dist/types/guard-handlers/consume-own-nav-token.d.ts +21 -0
  157. package/dist/types/guard-handlers/consume-own-nav-token.d.ts.map +1 -0
  158. package/dist/types/guard-handlers/guard-tick-deps.d.ts +20 -0
  159. package/dist/types/guard-handlers/guard-tick-deps.d.ts.map +1 -0
  160. package/dist/types/guard-handlers/guard-tick-payload.d.ts +15 -0
  161. package/dist/types/guard-handlers/guard-tick-payload.d.ts.map +1 -0
  162. package/dist/types/guard-handlers/handle-push-mode-guard.d.ts +24 -0
  163. package/dist/types/guard-handlers/handle-push-mode-guard.d.ts.map +1 -0
  164. package/dist/types/guard-handlers/handle-replace-mode-guard.d.ts +19 -0
  165. package/dist/types/guard-handlers/handle-replace-mode-guard.d.ts.map +1 -0
  166. package/dist/types/guard-handlers/index.d.ts +7 -0
  167. package/dist/types/guard-handlers/index.d.ts.map +1 -0
  168. package/dist/types/guard-handlers/is-in-app-scope.d.ts +15 -0
  169. package/dist/types/guard-handlers/is-in-app-scope.d.ts.map +1 -0
  170. package/dist/types/helpers/get-current-url.d.ts +10 -0
  171. package/dist/types/helpers/get-current-url.d.ts.map +1 -0
  172. package/dist/types/helpers/index.d.ts +6 -0
  173. package/dist/types/helpers/index.d.ts.map +1 -0
  174. package/dist/types/helpers/normalize-path-from-url.d.ts +11 -0
  175. package/dist/types/helpers/normalize-path-from-url.d.ts.map +1 -0
  176. package/dist/types/helpers/normalize-path.d.ts +14 -0
  177. package/dist/types/helpers/normalize-path.d.ts.map +1 -0
  178. package/dist/types/helpers/resolve-adapter.d.ts +14 -0
  179. package/dist/types/helpers/resolve-adapter.d.ts.map +1 -0
  180. package/dist/types/helpers/strip-query-params.d.ts +12 -0
  181. package/dist/types/helpers/strip-query-params.d.ts.map +1 -0
  182. package/dist/types/index.d.ts +9 -0
  183. package/dist/types/index.d.ts.map +1 -0
  184. package/dist/types/operators/active-app-navigation-events.d.ts +47 -0
  185. package/dist/types/operators/active-app-navigation-events.d.ts.map +1 -0
  186. package/dist/types/reconcile.d.ts +43 -0
  187. package/dist/types/reconcile.d.ts.map +1 -0
  188. package/dist/types/sources/app-first-source.d.ts +14 -0
  189. package/dist/types/sources/app-first-source.d.ts.map +1 -0
  190. package/dist/types/sources/context-first-source.d.ts +15 -0
  191. package/dist/types/sources/context-first-source.d.ts.map +1 -0
  192. package/dist/types/sources/index.d.ts +4 -0
  193. package/dist/types/sources/index.d.ts.map +1 -0
  194. package/dist/types/sources/types.d.ts +61 -0
  195. package/dist/types/sources/types.d.ts.map +1 -0
  196. package/dist/types/types.d.ts +126 -0
  197. package/dist/types/types.d.ts.map +1 -0
  198. package/dist/types/utils/has-custom-context-generators.d.ts +13 -0
  199. package/dist/types/utils/has-custom-context-generators.d.ts.map +1 -0
  200. package/dist/types/utils/legacy-app-navigation-fix.d.ts +33 -0
  201. package/dist/types/utils/legacy-app-navigation-fix.d.ts.map +1 -0
  202. package/dist/types/utils/resolve-context-from-url.d.ts +20 -0
  203. package/dist/types/utils/resolve-context-from-url.d.ts.map +1 -0
  204. package/dist/types/utils/url/build-app-route.d.ts +11 -0
  205. package/dist/types/utils/url/build-app-route.d.ts.map +1 -0
  206. package/dist/types/utils/url/build-context-url-for-strategy.d.ts +14 -0
  207. package/dist/types/utils/url/build-context-url-for-strategy.d.ts.map +1 -0
  208. package/dist/types/utils/url/index.d.ts +8 -0
  209. package/dist/types/utils/url/index.d.ts.map +1 -0
  210. package/dist/types/utils/url/parse-app-route.d.ts +20 -0
  211. package/dist/types/utils/url/parse-app-route.d.ts.map +1 -0
  212. package/dist/types/utils/url/resolve-context-id-from-url.d.ts +8 -0
  213. package/dist/types/utils/url/resolve-context-id-from-url.d.ts.map +1 -0
  214. package/dist/types/utils/url/strip-context-query-param.d.ts +12 -0
  215. package/dist/types/utils/url/strip-context-query-param.d.ts.map +1 -0
  216. package/dist/types/version.d.ts +2 -0
  217. package/dist/types/version.d.ts.map +1 -0
  218. package/package.json +5 -5
@@ -0,0 +1,102 @@
1
+ import { normalizePathFromURL } from './helpers';
2
+ /**
3
+ * Apply a context-driven navigation if the adapter produces a different URL.
4
+ *
5
+ * This is the single codepath through which all plugin-initiated navigations flow.
6
+ * It performs four steps in sequence:
7
+ *
8
+ * 1. **Encode** — asks the adapter to produce a target URL from the active context.
9
+ * 2. **Compare** — skips if the target matches the current URL (no-op guard).
10
+ * 3. **Dispatch** — fires a cancelable `onContextNavigationNavigate` event so
11
+ * external listeners can veto or observe the transition.
12
+ * 4. **Navigate** — performs the actual URL transition and records an own-nav token
13
+ * so the URL guard knows to ignore the resulting navigation state change.
14
+ *
15
+ * @param payload - The navigation target details (app, adapter, context, URL).
16
+ * @param deps - Shared plugin dependencies (event bus, navigation provider, config).
17
+ * @param navOptionsOverride - Optional navigation options that override
18
+ * `config.navigationOptions` for this specific call. Used by app-switch
19
+ * reconciliation and URL-drift correction, which must always replace the
20
+ * current history entry regardless of the user's `replace` setting.
21
+ * @returns A promise that resolves after the transition is handled in this
22
+ * function (applied or skipped). It does not wait for browser-level
23
+ * navigation completion.
24
+ */
25
+ export async function applyNavigation(payload, deps, navOptionsOverride) {
26
+ const { appKey, appModules, adapter, context, currentURL } = payload;
27
+ const { event, navigation, config, eventSource, ownNavTokens, log } = deps;
28
+ // Step 1: Ask the adapter to produce the target URL for the given context.
29
+ // A null return means the adapter intentionally declines to navigate (e.g.
30
+ // context type not supported by this adapter's URL scheme).
31
+ const targetURL = adapter.encode({ context, currentURL });
32
+ // If the adapter returned null, skip navigation and log the reason.
33
+ if (!targetURL) {
34
+ event.dispatchEvent('onContextNavigationSkipped', {
35
+ detail: { appKey, reason: 'encode-returned-null' },
36
+ source: eventSource,
37
+ });
38
+ log(`Adapter [${adapter.id}] returned null for [${appKey}] — skipping.`);
39
+ return Promise.resolve();
40
+ }
41
+ // Step 2: Compare normalized paths to avoid redundant navigations.
42
+ // This prevents a replace() call when the URL is already correct, which
43
+ // would otherwise create a spurious history entry in some browsers.
44
+ if (normalizePathFromURL(targetURL) === normalizePathFromURL(currentURL)) {
45
+ event.dispatchEvent('onContextNavigationSkipped', {
46
+ detail: { appKey, reason: 'url-matches' },
47
+ source: eventSource,
48
+ });
49
+ log(`URL already correct for [${appKey}] — skipping.`);
50
+ return Promise.resolve();
51
+ }
52
+ // Step 3: Dispatch a cancelable event so external code can inspect or veto.
53
+ const navigateDetail = {
54
+ appKey,
55
+ adapterId: adapter.id,
56
+ targetURL,
57
+ sourceURL: currentURL,
58
+ context,
59
+ appModules,
60
+ };
61
+ return event
62
+ .dispatchEvent('onContextNavigationNavigate', {
63
+ detail: navigateDetail,
64
+ source: eventSource,
65
+ cancelable: true,
66
+ })
67
+ .then((ev) => {
68
+ // If any listener called event.preventDefault(), abort navigation.
69
+ if (ev.canceled) {
70
+ event.dispatchEvent('onContextNavigationSkipped', {
71
+ detail: { appKey, reason: 'canceled' },
72
+ source: eventSource,
73
+ });
74
+ log(`Navigation canceled for [${appKey}].`);
75
+ return;
76
+ }
77
+ // Step 4: Record the token BEFORE navigating so the guard sees it
78
+ // on the resulting state$ emission and skips re-processing.
79
+ ownNavTokens.add(normalizePathFromURL(targetURL));
80
+ navigation.navigate(targetURL, navOptionsOverride ?? config.navigationOptions);
81
+ // Post-navigation bookkeeping: notify listeners and invoke callback.
82
+ const navigatedDetail = {
83
+ appKey,
84
+ adapterId: adapter.id,
85
+ targetURL,
86
+ context,
87
+ appModules,
88
+ };
89
+ event.dispatchEvent('onContextNavigationNavigated', {
90
+ detail: navigatedDetail,
91
+ source: eventSource,
92
+ });
93
+ config.onTransition?.(navigatedDetail);
94
+ log(`Navigated [${appKey}]: ${currentURL.pathname}${currentURL.search} → ${targetURL.pathname}${targetURL.search}`);
95
+ })
96
+ .catch((err) => {
97
+ // Swallow dispatch/navigation errors to avoid unhandled rejections.
98
+ // Logging ensures failures are observable during development.
99
+ log(`Navigation dispatch failed for [${appKey}]: ${String(err)}`);
100
+ });
101
+ }
102
+ //# sourceMappingURL=apply-navigation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-navigation.js","sourceRoot":"","sources":["../../src/apply-navigation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Query-parameter key used to carry the context id in query-routing mode.
3
+ *
4
+ * The `$` prefix is a Fusion convention that namespaces framework-managed
5
+ * parameters, distinguishing them from app-specific query params.
6
+ */
7
+ export const CONTEXT_QUERY_PARAM_KEY = '$contextId';
8
+ //# sourceMappingURL=context-query-param-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-query-param-key.js","sourceRoot":"","sources":["../../../src/constants/context-query-param-key.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,YAAY,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { CONTEXT_QUERY_PARAM_KEY } from './context-query-param-key';
2
+ export { UUID_PATTERN } from './uuid-pattern';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/constants/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Regex matching a standard UUID (v4 format, case-insensitive).
3
+ *
4
+ * Used to identify whether a path segment is a context id vs. a route name.
5
+ * Context ids in Fusion are always UUIDs, so this is a reliable discriminator.
6
+ */
7
+ export const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8
+ //# sourceMappingURL=uuid-pattern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uuid-pattern.js","sourceRoot":"","sources":["../../../src/constants/uuid-pattern.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,iEAAiE,CAAC"}
@@ -0,0 +1,124 @@
1
+ import { Subscription, startWith, pairwise, map } from 'rxjs';
2
+ import { getCurrentURL, resolveAdapter } from './helpers';
3
+ import { activeAppNavigationEvents } from './operators/active-app-navigation-events';
4
+ import { consumeOwnNavToken, handlePushModeGuard, handleReplaceModeGuard, isInAppScope, } from './guard-handlers';
5
+ import { reconcile } from './reconcile';
6
+ import { version } from './version';
7
+ /** Event source token forwarded to all dispatched events. */
8
+ const eventSource = { name: 'contextNavigation', version };
9
+ // ── Plugin factory ────────────────────────────────────────────────────
10
+ /**
11
+ * Creates the context-navigation plugin runtime.
12
+ *
13
+ * Sets up two subscriptions:
14
+ *
15
+ * **Reconciler** — watches the portal context and app switches via the configured
16
+ * source factory. On each emission it encodes the active context into the app URL
17
+ * using the first matching adapter.
18
+ *
19
+ * **URL guard** — watches browser navigation state and detects when the URL no
20
+ * longer reflects the active context. Behavior depends on `navigationOptions.replace`:
21
+ *
22
+ * - `replace: true` (default) — each context change replaces the current history entry,
23
+ * so there is no meaningful back/forward stack within the context history. If the URL
24
+ * diverges from the active context (e.g. an external redirect stripped the context
25
+ * segment), the guard re-asserts the active context into the URL.
26
+ *
27
+ * - `replace: false` (push mode) — each context change pushes a new history entry, so
28
+ * back/forward navigations legitimately land on URLs with different context IDs. The
29
+ * guard detects this and drives context from the URL instead of overwriting it,
30
+ * keeping the browser history and context state in sync.
31
+ *
32
+ * @param args - Initialized module providers and resolved plugin config.
33
+ * @returns A teardown function compatible with {@link FrameworkPluginTeardown}.
34
+ */
35
+ export function createContextNavigationPlugin(args) {
36
+ const { app, navigation, context, event, config } = args;
37
+ // Aggregate subscription — all plugin-owned subscriptions are collected here
38
+ // so the teardown function can unsubscribe from everything in one call.
39
+ const subscriptions = new Subscription();
40
+ // Token set used to distinguish plugin-initiated navigations from user-initiated ones.
41
+ // The guard reads and consumes tokens; the reconciler and applyNavigation write them.
42
+ const ownNavTokens = new Set();
43
+ // Conditional debug logger scoped to this plugin instance.
44
+ const log = (msg) => {
45
+ // Only log debug messages if debug mode is enabled in config.
46
+ if (config.debug) {
47
+ console.debug(`[${config.portalName}] ContextNavigation: ${msg}`);
48
+ }
49
+ };
50
+ // Dependency bag shared across reconcile, applyNavigation, and guard handlers.
51
+ // Avoids passing individual arguments and keeps function signatures stable.
52
+ const deps = { event, navigation, config, eventSource, ownNavTokens, log };
53
+ // ── Reconciler subscription ─────────────────────────────────────
54
+ // The source$ observable is created by the configured sourceFactory.
55
+ // It emits whenever the active context or app changes (implementation-specific).
56
+ // Each emission triggers reconcile(), which resolves an adapter and navigates.
57
+ const source$ = config.sourceFactory({ app, context, navigation });
58
+ // Emit [null, firstEntry] on first emission, then [prevEntry, currEntry] thereafter.
59
+ // Map pairs to object with entry and isAppSwitch flag for reconciler.
60
+ subscriptions.add(source$
61
+ .pipe(startWith(null), pairwise(), map(([prev, entry]) => ({
62
+ entry: entry,
63
+ isAppSwitch: prev != null && prev.appKey !== entry.appKey,
64
+ })))
65
+ .subscribe(({ entry, isAppSwitch }) => {
66
+ reconcile(entry, deps, { isAppSwitch });
67
+ }));
68
+ // ── URL guard subscription (optional) ───────────────────────────
69
+ // When enabled, the guard monitors every browser navigation event to detect
70
+ // URL/context drift. It fires after the reconciler has already handled
71
+ // context changes, so its role is corrective, not primary.
72
+ if (config.enableUrlGuard) {
73
+ // Guard needs the context provider to drive context from URL in push mode.
74
+ const guardDeps = { ...deps, context };
75
+ subscriptions.add(
76
+ // activeAppNavigationEvents$ emits the current app key and modules
77
+ // on every navigation state change, filtering out null/unresolved states.
78
+ activeAppNavigationEvents(app, navigation).subscribe(({ appModules, appKey, routingStrategy }) => {
79
+ const appContext = appModules.context;
80
+ // No active context means nothing to guard — bail early.
81
+ if (!appContext?.currentContext) {
82
+ return;
83
+ }
84
+ const currentURL = getCurrentURL(navigation, config.origin);
85
+ // Scope check: only guard URLs that belong to this app's path namespace.
86
+ // Navigations to other apps or external paths are not our responsibility.
87
+ if (!isInAppScope(currentURL, appKey)) {
88
+ return;
89
+ }
90
+ // Own-navigation check: if this URL was navigated to by the plugin itself,
91
+ // consume the token and skip — prevents infinite reconcile/guard loops.
92
+ if (consumeOwnNavToken(currentURL, ownNavTokens)) {
93
+ return;
94
+ }
95
+ // Resolve the adapter for this app/URL combination.
96
+ // If no adapter matches, the URL format is unknown — we can't guard it.
97
+ const adapter = resolveAdapter({ appKey, appContext, routingStrategy, currentURL }, config.adapters);
98
+ // No matching adapter means we can't decode the URL — bail out.
99
+ if (!adapter) {
100
+ return;
101
+ }
102
+ const activeContext = appContext.currentContext;
103
+ const urlContextId = adapter.decode(currentURL);
104
+ // If the URL already encodes the active context, state is in sync — done.
105
+ if (urlContextId === activeContext.id) {
106
+ return;
107
+ }
108
+ // Decision branch: push mode vs replace mode determines how we reconcile drift.
109
+ if (urlContextId !== null && config.navigationOptions?.replace === false) {
110
+ // Push mode: URL has a different context ID, meaning the user navigated
111
+ // via browser back/forward. Drive context FROM the URL to stay in sync.
112
+ handlePushModeGuard(urlContextId, { appKey, appModules, routingStrategy }, currentURL, guardDeps);
113
+ return;
114
+ }
115
+ // Replace mode (default): URL diverged from active context (external redirect,
116
+ // stripped segment, etc.). Re-assert the active context INTO the URL.
117
+ handleReplaceModeGuard({ appKey, appModules, routingStrategy }, currentURL, guardDeps);
118
+ }));
119
+ }
120
+ // Return a teardown function that unsubscribes from all plugin subscriptions.
121
+ // Called by the framework when the plugin is disposed (app unmount or hot-reload).
122
+ return () => subscriptions.unsubscribe();
123
+ }
124
+ //# sourceMappingURL=create-context-navigation-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-context-navigation-plugin.js","sourceRoot":"","sources":["../../src/create-context-navigation-plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import { createPlugin } from '@equinor/fusion-framework-module/configurator';
2
+ import { ContextNavigationConfigurator } from './ContextNavigationConfigurator';
3
+ import { createContextNavigationPlugin } from './create-context-navigation-plugin';
4
+ /**
5
+ * Registers the event-driven context-to-URL reconciler plugin for a portal.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * // Minimal — all defaults (built-in adapters: custom > query > path)
10
+ * enableContextNavigation(configurator);
11
+ *
12
+ * // With custom adapter
13
+ * enableContextNavigation(configurator, (builder) => {
14
+ * builder.setPortalName('my-portal');
15
+ * builder.setDebug(true);
16
+ * builder.registerAdapter(myHashAdapter);
17
+ * });
18
+ * ```
19
+ */
20
+ export const enableContextNavigation = (
21
+ // biome-ignore lint/suspicious/noExplicitAny: must be any to support all module types
22
+ configurator, builder) => {
23
+ configurator.registerPlugin(createPlugin('contextNavigation', async (modules) => {
24
+ const contextNavigationConfigurator = new ContextNavigationConfigurator();
25
+ await builder?.(contextNavigationConfigurator);
26
+ const config = contextNavigationConfigurator.createConfig();
27
+ await config.resolveInitialContext?.({
28
+ context: modules.context,
29
+ navigation: modules.navigation,
30
+ });
31
+ return createContextNavigationPlugin({
32
+ app: modules.app,
33
+ navigation: modules.navigation,
34
+ context: modules.context,
35
+ event: modules.event,
36
+ config,
37
+ });
38
+ }));
39
+ };
40
+ //# sourceMappingURL=enable-context-navigation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enable-context-navigation.js","sourceRoot":"","sources":["../../src/enable-context-navigation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,+CAA+C,CAAC;AAK7E,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AAQnF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;AACrC,sFAAsF;AACtF,YAA4C,EAC5C,OAAkC,EAC5B,EAAE;IACR,YAAY,CAAC,cAAc,CACzB,YAAY,CAAC,mBAAmB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAClD,MAAM,6BAA6B,GAAG,IAAI,6BAA6B,EAAE,CAAC;QAE1E,MAAM,OAAO,EAAE,CAAC,6BAA6B,CAAC,CAAC;QAE/C,MAAM,MAAM,GAAG,6BAA6B,CAAC,YAAY,EAAE,CAAC;QAE5D,MAAM,MAAM,CAAC,qBAAqB,EAAE,CAAC;YACnC,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,UAAU,EAAE,OAAO,CAAC,UAAU;SAC/B,CAAC,CAAC;QAEH,OAAO,6BAA6B,CAAC;YACnC,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM;SACP,CAAC,CAAC;IACL,CAAC,CAAC,CACH,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/events.ts"],"names":[],"mappings":""}
@@ -0,0 +1,29 @@
1
+ import { normalizePathFromURL } from '../helpers';
2
+ /**
3
+ * Consume an own-navigation token if the current URL matches one previously
4
+ * navigated to by the plugin itself.
5
+ *
6
+ * Intent: prevent infinite loops between the reconciler and the guard.
7
+ * When the reconciler calls `applyNavigation`, it records the target path as a
8
+ * token. The next `navigation.state$` emission triggers the guard, which would
9
+ * otherwise see a "new" URL and try to reconcile again. By checking and consuming
10
+ * the token here, the guard knows this URL change was self-initiated and bails.
11
+ *
12
+ * Each token is consumed (deleted from the set) exactly once. This ensures that
13
+ * a subsequent user-initiated back/forward navigation to the same path IS
14
+ * processed by the guard, since the token will no longer be present.
15
+ *
16
+ * @param currentURL - The current browser URL to check against known tokens.
17
+ * @param ownNavTokens - The set of normalized paths the plugin has navigated to.
18
+ * @returns `true` if this navigation was plugin-initiated (caller should bail).
19
+ */
20
+ export function consumeOwnNavToken(currentURL, ownNavTokens) {
21
+ const normalized = normalizePathFromURL(currentURL);
22
+ // Token present — this navigation was plugin-initiated; consume it and signal the caller to bail.
23
+ if (ownNavTokens.has(normalized)) {
24
+ ownNavTokens.delete(normalized);
25
+ return true;
26
+ }
27
+ return false;
28
+ }
29
+ //# sourceMappingURL=consume-own-nav-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consume-own-nav-token.js","sourceRoot":"","sources":["../../../src/guard-handlers/consume-own-nav-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGlD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAe,EAAE,YAAiC;IACnF,MAAM,UAAU,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAEpD,kGAAkG;IAClG,IAAI,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;QACjC,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=guard-tick-deps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard-tick-deps.js","sourceRoot":"","sources":["../../../src/guard-handlers/guard-tick-deps.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=guard-tick-payload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard-tick-payload.js","sourceRoot":"","sources":["../../../src/guard-handlers/guard-tick-payload.ts"],"names":[],"mappings":""}
@@ -0,0 +1,44 @@
1
+ import { applyNavigation } from '../apply-navigation';
2
+ import { resolveAdapter } from '../helpers';
3
+ /**
4
+ * Handle a URL guard tick in **push mode** (`navigationOptions.replace: false`).
5
+ *
6
+ * Intent: keep browser history and context state bidirectionally in sync.
7
+ *
8
+ * In push mode, each context change pushes a new history entry, so back/forward
9
+ * navigations legitimately land on URLs with different context IDs. When the guard
10
+ * detects a URL containing a context ID that differs from the active context, it
11
+ * interprets this as the user having navigated through history and drives context
12
+ * FROM the URL (rather than overwriting the URL).
13
+ *
14
+ * If the URL's context ID can't be resolved (stale bookmark, deleted context),
15
+ * the function falls back to re-asserting the active context into the URL so the
16
+ * address bar isn't left pointing at a broken resource.
17
+ *
18
+ * @param urlContextId - The context ID decoded from the current URL by the adapter.
19
+ * @param payload - The active app key and resolved modules.
20
+ * @param currentURL - The current browser URL.
21
+ * @param deps - Shared plugin dependencies including the context provider.
22
+ */
23
+ export function handlePushModeGuard(urlContextId, payload, currentURL, deps) {
24
+ const { appKey, appModules, routingStrategy } = payload;
25
+ const { context, log } = deps;
26
+ // Resolve the active context and adapter — both are required to fall back.
27
+ const activeContext = appModules.context.currentContext;
28
+ // No active context to fall back to — nothing to do.
29
+ if (!activeContext) {
30
+ return;
31
+ }
32
+ const adapter = resolveAdapter({ appKey, appContext: appModules.context, routingStrategy, currentURL }, deps.config.adapters);
33
+ // No adapter matched — plugin is not responsible for this app's URL shape.
34
+ if (!adapter) {
35
+ return;
36
+ }
37
+ log(`URL guard: URL has context [${urlContextId}] for [${appKey}] — setting context from URL`);
38
+ // Attempt to set context from the URL-decoded ID.
39
+ // On failure (e.g. context deleted), fall back to re-encoding the active context.
40
+ context.setCurrentContextByIdAsync(urlContextId).catch(() => {
41
+ applyNavigation({ appKey, appModules, adapter, context: activeContext, currentURL }, deps);
42
+ });
43
+ }
44
+ //# sourceMappingURL=handle-push-mode-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handle-push-mode-guard.js","sourceRoot":"","sources":["../../../src/guard-handlers/handle-push-mode-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAK5C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,mBAAmB,CACjC,YAAoB,EACpB,OAAyB,EACzB,UAAe,EACf,IAAmB;IAEnB,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC;IACxD,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAE9B,2EAA2E;IAC3E,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,CAAC,cAAc,CAAC;IAExD,qDAAqD;IACrD,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,cAAc,CAC5B,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,CAAC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,EACvE,IAAI,CAAC,MAAM,CAAC,QAAQ,CACrB,CAAC;IAEF,2EAA2E;IAC3E,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO;IACT,CAAC;IAED,GAAG,CAAC,+BAA+B,YAAY,UAAU,MAAM,8BAA8B,CAAC,CAAC;IAE/F,kDAAkD;IAClD,kFAAkF;IAClF,OAAO,CAAC,0BAA0B,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;QAC1D,eAAe,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,EAAE,IAAI,CAAC,CAAC;IAC7F,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { applyNavigation } from '../apply-navigation';
2
+ import { resolveAdapter } from '../helpers';
3
+ /**
4
+ * Handle a URL guard tick in **replace mode** (`navigationOptions.replace: true`, the default).
5
+ *
6
+ * Intent: correct URL drift caused by external interference.
7
+ *
8
+ * In replace mode, context changes replace the current history entry, so there is
9
+ * no meaningful back/forward stack within the context history. A URL mismatch
10
+ * therefore means something external mutated the URL (a redirect, a router
11
+ * guard stripping segments, etc.). The fix is to re-assert the active context
12
+ * back into the URL, restoring the canonical app path.
13
+ *
14
+ * @param payload - The active app key and resolved modules.
15
+ * @param currentURL - The current browser URL that diverged from the active context.
16
+ * @param deps - Shared plugin dependencies.
17
+ */
18
+ export function handleReplaceModeGuard(payload, currentURL, deps) {
19
+ const { appKey, appModules, routingStrategy } = payload;
20
+ const { log } = deps;
21
+ // Resolve the active context and adapter for re-encoding.
22
+ const activeContext = appModules.context.currentContext;
23
+ // No active context to re-encode — nothing to correct.
24
+ if (!activeContext) {
25
+ return;
26
+ }
27
+ const adapter = resolveAdapter({ appKey, appContext: appModules.context, routingStrategy, currentURL }, deps.config.adapters);
28
+ // No adapter matched — plugin is not responsible for this app's URL shape.
29
+ if (!adapter) {
30
+ return;
31
+ }
32
+ log(`URL guard: context missing from URL, re-applying for [${appKey}]`);
33
+ // Always replace when correcting URL drift. The URL has no context segment
34
+ // (e.g. a bare app route the portal pushed), so we fix it in-place. Using
35
+ // push here would create an extra history entry that traps Back navigation.
36
+ applyNavigation({ appKey, appModules, adapter, context: activeContext, currentURL }, deps, {
37
+ replace: true,
38
+ });
39
+ }
40
+ //# sourceMappingURL=handle-replace-mode-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handle-replace-mode-guard.js","sourceRoot":"","sources":["../../../src/guard-handlers/handle-replace-mode-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAK5C;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAyB,EACzB,UAAe,EACf,IAAmB;IAEnB,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC;IACxD,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAErB,0DAA0D;IAC1D,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,CAAC,cAAc,CAAC;IAExD,uDAAuD;IACvD,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,cAAc,CAC5B,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,CAAC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,EACvE,IAAI,CAAC,MAAM,CAAC,QAAQ,CACrB,CAAC;IAEF,2EAA2E;IAC3E,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO;IACT,CAAC;IAED,GAAG,CAAC,yDAAyD,MAAM,GAAG,CAAC,CAAC;IACxE,2EAA2E;IAC3E,0EAA0E;IAC1E,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE;QACzF,OAAO,EAAE,IAAI;KACd,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { isInAppScope } from './is-in-app-scope';
2
+ export { consumeOwnNavToken } from './consume-own-nav-token';
3
+ export { handlePushModeGuard } from './handle-push-mode-guard';
4
+ export { handleReplaceModeGuard } from './handle-replace-mode-guard';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/guard-handlers/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Determine whether the current URL falls within the active app's path namespace.
3
+ *
4
+ * The plugin must only guard URLs that belong to its own app. Navigations to
5
+ * other apps or external paths should be ignored entirely — they are another
6
+ * app instance's responsibility.
7
+ *
8
+ * Convention: all app URLs live under `/apps/{appKey}` or `/apps/{appKey}/...`.
9
+ *
10
+ * @param currentURL - The current browser URL to check.
11
+ * @param appKey - The app key used to derive the expected base path.
12
+ * @returns `true` if the URL belongs to this app's scope.
13
+ */
14
+ export function isInAppScope(currentURL, appKey) {
15
+ const appBase = `/apps/${appKey}`;
16
+ // Exact match (app root) or path-segment-prefixed (app sub-route).
17
+ return currentURL.pathname === appBase || currentURL.pathname.startsWith(`${appBase}/`);
18
+ }
19
+ //# sourceMappingURL=is-in-app-scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-in-app-scope.js","sourceRoot":"","sources":["../../../src/guard-handlers/is-in-app-scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAAC,UAAe,EAAE,MAAc;IAC1D,MAAM,OAAO,GAAG,SAAS,MAAM,EAAE,CAAC;IAClC,mEAAmE;IACnE,OAAO,UAAU,CAAC,QAAQ,KAAK,OAAO,IAAI,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC;AAC1F,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Construct the current browser URL from the navigation provider's state.
3
+ *
4
+ * @param navigation - The navigation provider (for reading pathname and search).
5
+ * @param origin - The origin used to construct an absolute URL.
6
+ * @returns An absolute URL representing the current browser location.
7
+ */
8
+ export function getCurrentURL(navigation, origin) {
9
+ return new URL(`${navigation.path.pathname}${navigation.path.search ?? ''}`, origin);
10
+ }
11
+ //# sourceMappingURL=get-current-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-current-url.js","sourceRoot":"","sources":["../../../src/helpers/get-current-url.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,UAA+B,EAAE,MAAc;IAC3E,OAAO,IAAI,GAAG,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;AACvF,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { normalizePathFromURL } from './normalize-path-from-url';
2
+ export { normalizePath } from './normalize-path';
3
+ export { getCurrentURL } from './get-current-url';
4
+ export { resolveAdapter } from './resolve-adapter';
5
+ export { stripQueryParams } from './strip-query-params';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/helpers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { normalizePath } from './normalize-path';
2
+ /**
3
+ * Normalize a URL to its path + search representation for comparison.
4
+ *
5
+ * Strips trailing slashes from the pathname so that `/apps/foo/` and
6
+ * `/apps/foo` compare as equal.
7
+ *
8
+ * @param url - The URL to normalize.
9
+ * @returns A string of the form `pathname + search` with trailing slash removed.
10
+ */
11
+ export function normalizePathFromURL(url) {
12
+ return `${normalizePath(url.pathname)}${url.search}`;
13
+ }
14
+ //# sourceMappingURL=normalize-path-from-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize-path-from-url.js","sourceRoot":"","sources":["../../../src/helpers/normalize-path-from-url.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAQ;IAC3C,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;AACvD,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Normalizes a path string to ensure it is in a consistent format for comparison and processing.
3
+ *
4
+ * This function performs the following normalizations:
5
+ * - Ensures the path starts with a leading slash.
6
+ * - Removes any trailing slashes, except for the root path ("/").
7
+ * - Converts empty paths to the root path ("/").
8
+ * - Fixing multiple consecutive slashes to a single slash.
9
+ *
10
+ * @param path - The path string to normalize.
11
+ * @returns The normalized path string.
12
+ */
13
+ export function normalizePath(path) {
14
+ // Replace multiple consecutive slashes with a single slash
15
+ path = path.replace(/\/+/g, '/');
16
+ // Ensure the path starts with a leading slash
17
+ if (!path.startsWith('/')) {
18
+ path = `/${path}`;
19
+ }
20
+ // Remove trailing slashes, except for the root path
21
+ if (path.length > 1 && path.endsWith('/')) {
22
+ path = path.slice(0, -1);
23
+ }
24
+ // Convert empty paths to the root path
25
+ if (path === '') {
26
+ return '/';
27
+ }
28
+ return path;
29
+ }
30
+ //# sourceMappingURL=normalize-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize-path.js","sourceRoot":"","sources":["../../../src/helpers/normalize-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,2DAA2D;IAC3D,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAEjC,8CAA8C;IAC9C,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;IACpB,CAAC;IAED,oDAAoD;IACpD,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1C,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC;IAED,uCAAuC;IACvC,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;QAChB,OAAO,GAAG,CAAC;IACb,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Iterate registered adapters and return the first one that can handle
3
+ * the given resolution context.
4
+ *
5
+ * - **Object adapters** are tested via `canHandle(ctx)`.
6
+ * - **Factory adapters** are invoked with `ctx`; a non-null return means match.
7
+ *
8
+ * @param ctx - The adapter resolution context (app key, app context, current URL).
9
+ * @param adapters - The registered adapters in evaluation order.
10
+ * @returns The first matching adapter, or `null` if none matched.
11
+ */
12
+ export function resolveAdapter(ctx, adapters) {
13
+ // Iterate through adapters in order until one can handle the given context
14
+ for (const entry of adapters) {
15
+ const adapter = typeof entry === 'function' ? entry(ctx) : entry.canHandle(ctx) ? entry : null;
16
+ // Return the first adapter that can handle this context
17
+ if (adapter) {
18
+ return adapter;
19
+ }
20
+ }
21
+ return null;
22
+ }
23
+ //# sourceMappingURL=resolve-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-adapter.js","sourceRoot":"","sources":["../../../src/helpers/resolve-adapter.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAC5B,GAA6B,EAC7B,QAAkD;IAElD,2EAA2E;IAC3E,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;QAC/F,wDAAwD;QACxD,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,OAAO,CAAC;QACjB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}