ui5-lib-guard-router 1.5.2 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -715,6 +715,39 @@ export default class HomeController extends BaseController {
715
715
  >
716
716
  > In FLP apps with `sap-keep-alive` enabled, the component persists when navigating to other apps. Guards remain registered since the same instance is reused.
717
717
 
718
+ ### Metadata-driven guards via manifest
719
+
720
+ For common patterns like "this route requires authentication", you can store per-route metadata in a custom manifest section and use a single global guard instead of writing repetitive per-route guards:
721
+
722
+ ```json
723
+ {
724
+ "ui5.guard.router": {
725
+ "routeMeta": {
726
+ "admin": { "requiresAuth": true, "roles": ["admin"] },
727
+ "profile": { "requiresAuth": true },
728
+ "home": { "public": true }
729
+ }
730
+ }
731
+ }
732
+ ```
733
+
734
+ ```typescript
735
+ // Component.ts — read the custom section via getManifestEntry (typed path lookup)
736
+ type RouteMeta = Record<string, Record<string, unknown>>;
737
+ const routeMeta = (this.getManifestEntry("/ui5.guard.router/routeMeta") ?? {}) as RouteMeta;
738
+
739
+ router.addGuard((context) => {
740
+ const meta = routeMeta[context.toRoute] ?? {};
741
+ if (meta.requiresAuth && !authModel.getProperty("/isLoggedIn")) return "login";
742
+ if (meta.roles && !hasAnyRole(meta.roles as string[])) return "forbidden";
743
+ return true;
744
+ });
745
+ ```
746
+
747
+ `getManifestEntry()` accepts a path string (starting with `/`) to reach into nested manifest sections. The return type is `any`, so the local `RouteMeta` alias provides type safety at the consumption site.
748
+
749
+ This keeps guard logic in one place and route annotations in the manifest where they're visible and auditable. The custom namespace `ui5.guard.router` is ignored by the UI5 framework -- it's a convention for your application data.
750
+
718
751
  ### Native alternative for leave guards: Fiori Launchpad data loss prevention
719
752
 
720
753
  If your app runs inside SAP Fiori Launchpad (FLP), the shell provides built-in data loss protection through two public APIs on `sap.ushell.Container`:
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "buildManifest": {
18
18
  "manifestVersion": "0.2",
19
- "timestamp": "2026-03-24T13:12:12.834Z",
19
+ "timestamp": "2026-03-25T20:16:53.021Z",
20
20
  "versions": {
21
21
  "builderVersion": "4.1.4",
22
22
  "projectVersion": "4.0.13",
@@ -31,7 +31,7 @@
31
31
  "includedTasks": [],
32
32
  "excludedTasks": []
33
33
  },
34
- "version": "1.5.2",
34
+ "version": "1.5.3",
35
35
  "namespace": "ui5/guard/router",
36
36
  "tags": {
37
37
  "/resources/ui5/guard/router/GuardPipeline-dbg.js": {
package/dist/index.d.ts CHANGED
@@ -32,8 +32,8 @@
32
32
  // - @types/istanbul-lib-report@3.0.3
33
33
  // - @types/istanbul-lib-coverage@2.0.6
34
34
  // - @types/qunit@2.19.13
35
- /// <reference path="./resources/ui5/guard/router/Router.d.ts"/>
36
- /// <reference path="./resources/ui5/guard/router/types.d.ts"/>
37
35
  /// <reference path="./resources/ui5/guard/router/library.d.ts"/>
36
+ /// <reference path="./resources/ui5/guard/router/GuardPipeline.d.ts"/>
37
+ /// <reference path="./resources/ui5/guard/router/Router.d.ts"/>
38
38
  /// <reference path="./resources/ui5/guard/router/NavigationOutcome.d.ts"/>
39
- /// <reference path="./resources/ui5/guard/router/GuardPipeline.d.ts"/>
39
+ /// <reference path="./resources/ui5/guard/router/types.d.ts"/>
@@ -2,7 +2,7 @@
2
2
  <library xmlns="http://www.sap.com/sap.ui.library.xsd">
3
3
  <name>ui5.guard.router</name>
4
4
  <vendor>Marco</vendor>
5
- <version>1.5.2</version>
5
+ <version>1.5.3</version>
6
6
  <copyright></copyright>
7
7
  <title>UI5 Router extension with async navigation guards</title>
8
8
  <documentation>Extends sap.m.routing.Router with async navigation guards, running before route matching begins.</documentation>
@@ -383,10 +383,7 @@ sap.ui.define(["sap/m/routing/Router", "sap/base/Log", "sap/ui/core/library", ".
383
383
  * @since 1.0.1
384
384
  */
385
385
  addGuard: function _addGuard(guard) {
386
- if (typeof guard !== "function") {
387
- Log.warning("addGuard: not a function, ignoring", undefined, LOG_COMPONENT);
388
- return this;
389
- }
386
+ if (!this._isFn(guard, "addGuard")) return this;
390
387
  this._pipeline.addGlobalGuard(guard);
391
388
  return this;
392
389
  },
@@ -398,10 +395,7 @@ sap.ui.define(["sap/m/routing/Router", "sap/base/Log", "sap/ui/core/library", ".
398
395
  * @since 1.0.1
399
396
  */
400
397
  removeGuard: function _removeGuard(guard) {
401
- if (typeof guard !== "function") {
402
- Log.warning("removeGuard: not a function, ignoring", undefined, LOG_COMPONENT);
403
- return this;
404
- }
398
+ if (!this._isFn(guard, "removeGuard")) return this;
405
399
  this._pipeline.removeGlobalGuard(guard);
406
400
  return this;
407
401
  },
@@ -424,17 +418,13 @@ sap.ui.define(["sap/m/routing/Router", "sap/base/Log", "sap/ui/core/library", ".
424
418
  let hasHandler = false;
425
419
  if (guard.beforeEnter !== undefined) {
426
420
  hasHandler = true;
427
- if (typeof guard.beforeEnter !== "function") {
428
- Log.warning("addRouteGuard: not a function, ignoring", routeName, LOG_COMPONENT);
429
- } else {
421
+ if (this._isFn(guard.beforeEnter, "addRouteGuard", routeName)) {
430
422
  this._pipeline.addEnterGuard(routeName, guard.beforeEnter);
431
423
  }
432
424
  }
433
425
  if (guard.beforeLeave !== undefined) {
434
426
  hasHandler = true;
435
- if (typeof guard.beforeLeave !== "function") {
436
- Log.warning("addRouteGuard: not a function, ignoring", routeName, LOG_COMPONENT);
437
- } else {
427
+ if (this._isFn(guard.beforeLeave, "addRouteGuard", routeName)) {
438
428
  this._pipeline.addLeaveGuard(routeName, guard.beforeLeave);
439
429
  }
440
430
  }
@@ -444,10 +434,7 @@ sap.ui.define(["sap/m/routing/Router", "sap/base/Log", "sap/ui/core/library", ".
444
434
  }
445
435
  return this;
446
436
  }
447
- if (typeof guard !== "function") {
448
- Log.warning("addRouteGuard: not a function, ignoring", routeName, LOG_COMPONENT);
449
- return this;
450
- }
437
+ if (!this._isFn(guard, "addRouteGuard", routeName)) return this;
451
438
  if (!this._handleUnknownRouteRegistration(routeName, "addRouteGuard")) {
452
439
  return this;
453
440
  }
@@ -476,10 +463,7 @@ sap.ui.define(["sap/m/routing/Router", "sap/base/Log", "sap/ui/core/library", ".
476
463
  }
477
464
  return this;
478
465
  }
479
- if (typeof guard !== "function") {
480
- Log.warning("removeRouteGuard: not a function, ignoring", routeName, LOG_COMPONENT);
481
- return this;
482
- }
466
+ if (!this._isFn(guard, "removeRouteGuard", routeName)) return this;
483
467
  this._pipeline.removeEnterGuard(routeName, guard);
484
468
  return this;
485
469
  },
@@ -496,16 +480,40 @@ sap.ui.define(["sap/m/routing/Router", "sap/base/Log", "sap/ui/core/library", ".
496
480
  * @since 1.0.1
497
481
  */
498
482
  addLeaveGuard: function _addLeaveGuard(routeName, guard) {
499
- if (typeof guard !== "function") {
500
- Log.warning("addLeaveGuard: not a function, ignoring", routeName, LOG_COMPONENT);
501
- return this;
502
- }
483
+ if (!this._isFn(guard, "addLeaveGuard", routeName)) return this;
503
484
  if (!this._handleUnknownRouteRegistration(routeName, "addLeaveGuard")) {
504
485
  return this;
505
486
  }
506
487
  this._pipeline.addLeaveGuard(routeName, guard);
507
488
  return this;
508
489
  },
490
+ /**
491
+ * Disambiguate the `navTo()` overload shapes into resolved arguments.
492
+ *
493
+ * UI5's native `navTo` has two signatures where the third parameter is either
494
+ * `componentTargetInfo` (object) or `bReplace` (boolean). This method normalizes
495
+ * both forms so the rest of `navTo` can work with a single set of variables.
496
+ */
497
+ _resolveNavToArgs: function _resolveNavToArgs(componentTargetInfoOrReplace, replaceOrOptions, options) {
498
+ if (typeof componentTargetInfoOrReplace === "boolean") {
499
+ // Short form: navTo(name, params, replace, options?)
500
+ return {
501
+ replace: componentTargetInfoOrReplace,
502
+ guardOptions: typeof replaceOrOptions === "object" && replaceOrOptions !== null ? replaceOrOptions : undefined
503
+ };
504
+ }
505
+ // Long form: navTo(name, params, componentTargetInfo, replace, options?)
506
+ return {
507
+ componentTargetInfo: componentTargetInfoOrReplace,
508
+ replace: typeof replaceOrOptions === "boolean" ? replaceOrOptions : undefined,
509
+ guardOptions: options
510
+ };
511
+ },
512
+ /** Return `true` when the value is a function; otherwise log a warning. */_isFn: function _isFn(value, method, detail) {
513
+ if (typeof value === "function") return true;
514
+ Log.warning(`${method}: not a function, ignoring`, detail, LOG_COMPONENT);
515
+ return false;
516
+ },
509
517
  /**
510
518
  * Handle guard registration for a potentially unknown route.
511
519
  * Returns `true` if registration should proceed, `false` if not.
@@ -532,10 +540,7 @@ sap.ui.define(["sap/m/routing/Router", "sap/base/Log", "sap/ui/core/library", ".
532
540
  * @since 1.0.1
533
541
  */
534
542
  removeLeaveGuard: function _removeLeaveGuard(routeName, guard) {
535
- if (typeof guard !== "function") {
536
- Log.warning("removeLeaveGuard: not a function, ignoring", routeName, LOG_COMPONENT);
537
- return this;
538
- }
543
+ if (!this._isFn(guard, "removeLeaveGuard", routeName)) return this;
539
544
  this._pipeline.removeLeaveGuard(routeName, guard);
540
545
  return this;
541
546
  },
@@ -596,20 +601,11 @@ sap.ui.define(["sap/m/routing/Router", "sap/base/Log", "sap/ui/core/library", ".
596
601
  this.fireEvent("navigationSettled", result);
597
602
  },
598
603
  navTo: function _navTo(routeName, parameters, componentTargetInfoOrReplace, replaceOrOptions, options) {
599
- // Normalize the overload shapes into a single set of arguments.
600
- let componentTargetInfo;
601
- let replace;
602
- let guardOptions;
603
- if (typeof componentTargetInfoOrReplace === "boolean") {
604
- // Short form: navTo(name, params, replace, options?)
605
- replace = componentTargetInfoOrReplace;
606
- guardOptions = typeof replaceOrOptions === "object" && replaceOrOptions !== null ? replaceOrOptions : undefined;
607
- } else {
608
- // Long form: navTo(name, params, componentTargetInfo, replace, options?)
609
- componentTargetInfo = componentTargetInfoOrReplace;
610
- replace = typeof replaceOrOptions === "boolean" ? replaceOrOptions : undefined;
611
- guardOptions = options;
612
- }
604
+ const {
605
+ componentTargetInfo,
606
+ replace,
607
+ guardOptions
608
+ } = this._resolveNavToArgs(componentTargetInfoOrReplace, replaceOrOptions, options);
613
609
 
614
610
  // Redirect path: _redirect() calls this.navTo() while in committing/redirect phase.
615
611
  // Bypass preflight -- parse() will commit directly via the committing phase.