@zambon-dev/shared 2.0.0 → 2.1.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.
package/CHANGELOG.md CHANGED
@@ -23,6 +23,115 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
23
23
 
24
24
  ### ⚠ Breaking Changes / Migration
25
25
 
26
+ ## [2.1.0] - 2026-09-08
27
+
28
+ ### Added
29
+
30
+ - **External sidebar destinations are now opened, in either of two modes.** `MainLayoutComponent`
31
+ subscribes to `@zambon-dev/library`’s new `SidebarService.menuExternalUrlSelected`: an item whose
32
+ `openMode` is `ExternalNewTab` opens in a new browser tab (`noopener,noreferrer`), and one marked
33
+ `ExternalEmbedded` opens an application tab that displays the destination in a sandboxed iframe,
34
+ keeping the user inside the application. Internal routes are untouched.
35
+
36
+ - **`ExternalUrlResolverService`** — substitutes the runtime placeholders in an external menu URL
37
+ and vets the result. The placeholder set is closed and case-sensitive: `{email}`, `{language}`,
38
+ `{userId}`, `{userName}`. Every substituted value is `encodeURIComponent`-ed, so a name containing
39
+ `&` cannot inject a query parameter — which also means one placeholder must occupy one whole path
40
+ segment or query-parameter value. A supported placeholder with no value becomes an empty string
41
+ (with a console warning); an unrecognized `{…}` is left exactly as configured, so a report URL
42
+ that legitimately contains braces, such as `?filter={"a":1}`, is not corrupted.
43
+
44
+ No authentication token is ever substituted, and the set is closed by construction rather than
45
+ reflected off the stored user info — `AuthenticationService` persists the whole sign-in response
46
+ under `userInfo`, tokens included, so a reflective implementation would let a URL configured as
47
+ `?t={token}` hand the JWT to a third party.
48
+
49
+ `{language}` and `{userName}` work today. `{userId}` and `{email}` resolve to an empty string
50
+ until your `Authentication/SignIn` and `Authentication/RefreshToken` responses include `userID`
51
+ and `email` (see `ICurrentUserInfo` below).
52
+
53
+ - **`ExternalContentComponent` + `externalContentRoutes`** — the embedded view. Spread the routes
54
+ into `MainLayoutComponent`’s children:
55
+
56
+ ```ts
57
+ { path: '', component: MainLayoutComponent, canActivate: [AuthGuard], children: [
58
+ ...externalContentRoutes,
59
+ // your own features
60
+ ] }
61
+ ```
62
+
63
+ Register them even if you only plan to use `ExternalNewTab`: an embedded item configured without
64
+ them opens a tab that immediately bounces to the home route.
65
+
66
+ The view is an ordinary `TabViewBase` hosted by `DefaultTabViewComponent`, so its actions appear
67
+ in the application ribbon (a **Page** group with **Refresh** and **Open in a new browser tab**)
68
+ and look like every other screen’s — the shipped `externalContentRoutes` already wires that host
69
+ up, which is the other reason not to hand-write the routes. **Refresh** carries the same icon and
70
+ label as `framework-button-refresh`, because reloading a report is the same action as refreshing
71
+ a grid.
72
+
73
+ Refreshing genuinely tears the frame down and builds a new one — a cross-origin frame cannot be
74
+ navigated any other way — so the old render visibly goes away instead of sitting there while you
75
+ wonder whether anything happened. The button spins and the panel shows a loading overlay until
76
+ the destination reports `load`, or until the slow-frame delay elapses, so a destination that
77
+ never reports one cannot leave the controls stuck.
78
+
79
+ **Open in a new browser tab** is always available, because many sites refuse to be embedded
80
+ (`X-Frame-Options`, CSP `frame-ancestors`) and a browser gives JavaScript no reliable way to
81
+ detect that — if nothing has loaded after a few seconds the view also shows a hint saying so. An
82
+ `https` application cannot embed an `http` destination at all; the same button is the way out.
83
+
84
+ The tab URL is `/external-content/<menu id>` and never carries the destination, so no one can
85
+ hand-craft a link that makes your application frame an arbitrary site. Pressing F5 on an embedded
86
+ tab restores both the frame and the tab title from `sessionStorage`. Opening that URL in a *fresh*
87
+ browser tab can only work if your `SidebarService.getMenuFromUrl()` resolves
88
+ `/external-content/<id>`; otherwise the view says the content is unavailable and asks the user to
89
+ reopen it from the menu.
90
+
91
+ - **`EXTERNAL_CONTENT_CONFIGS`** — `allowedOrigins` (default `[]`, meaning any `http`/`https`
92
+ origin) and `slowFrameHintDelay` (default 5000 ms). **Populate `allowedOrigins` in production.**
93
+ Embedded and new-tab destinations are already rejected unless they are absolute `http`/`https`
94
+ URLs, which stops a `javascript:` or `data:` URL in your menu table from executing in your users’
95
+ session; an origin allowlist narrows what is left from “any site on the internet” to your known
96
+ report hosts. The iframe is sandboxed with a fixed, non-configurable token list that withholds
97
+ `allow-top-navigation`, so a framed site cannot navigate your application away. Do not point an
98
+ embedded item at your own application’s origin — use an internal route for that.
99
+
100
+ - **`ICurrentUserInfo.email`, `.userID`, `.username`** (all optional) — the values behind
101
+ `{email}`, `{userId}` and `{userName}`. `username` was already being persisted in the base64
102
+ `userInfo` entry and is now simply typed, so `{userName}` needs no backend change; `email` and
103
+ `userID` must be added to your sign-in and refresh responses.
104
+
105
+ - **Translations** for the embedded view under `i18n/external-content/`, already included in
106
+ `ZAMBON_SHARED_I18N_RESOURCES`.
107
+
108
+ ### Fixed
109
+
110
+ - **A deep link whose URL the menu API cannot resolve no longer surfaces an unhandled error.**
111
+ `MainLayoutComponent` resolves a deep-linked tab’s title through `SidebarService.getMenuFromUrl()`
112
+ and had no error handler, so a 404 became an unhandled rejection in the console. The title is
113
+ best-effort and the failure is now swallowed — which matters more now that the embedded-content
114
+ route is a URL shape most menu endpoints do not know.
115
+
116
+ ### ⚠ Breaking Changes / Migration
117
+
118
+ - **Requires `@zambon-dev/library` 1.4.0 or later.** `SidebarMenuOpenMode`,
119
+ `toSidebarMenuOpenMode` and `SidebarService.menuExternalUrlSelected` ship in that release. The
120
+ declared peer range still allows older versions, so upgrade both packages together.
121
+ - **To use external menu items, register `externalContentRoutes`** as children of
122
+ `MainLayoutComponent` (snippet above), and make your menu endpoint return `openMode`. The
123
+ resolver accepts it as the camelCase string (`"internal"`, `"externalNewTab"`,
124
+ `"externalEmbedded"`, matched case-insensitively) or as the enum ordinal (`0`, `1`, `2`), so a
125
+ plain ASP.NET Core enum property works with no converter.
126
+ - **The embedded view needs both i18n bundles registered.** Its own strings ship in this package
127
+ (`ZAMBON_SHARED_I18N_RESOURCES`, under `assets/i18n/zambon-dev/shared/external-content/`), but the
128
+ ribbon reuses three keys owned by `@zambon-dev/framework` — `RibbonGroup-Page`, `Button-Refresh`
129
+ and `Loading` — so `ZAMBON_FRAMEWORK_I18N_RESOURCES` has to be registered too, or those render as
130
+ raw keys. Any application that already uses framework buttons registers it; if your
131
+ `TranslateLoader` hand-lists prefixes instead of spreading the two constants, add both.
132
+ - Nothing else changes: applications with no `openMode` on any menu item behave exactly as before
133
+ and need no action.
134
+
26
135
  ## [2.0.0] - 2026-07-30
27
136
 
28
137
  ### Added
@@ -191,7 +300,8 @@ URL) in `AppConfig`, and implement a hub that pushes the notification list to cl
191
300
  available via [GitHub Releases](https://github.com/RicardoZambon/ZLibraries/releases) and the
192
301
  `shared-v*` tags.
193
302
 
194
- [Unreleased]: https://github.com/RicardoZambon/ZLibraries/compare/shared-v2.0.0...HEAD
303
+ [Unreleased]: https://github.com/RicardoZambon/ZLibraries/compare/shared-v2.1.0...HEAD
304
+ [2.1.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v2.1.0
195
305
  [2.0.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v2.0.0
196
306
  [1.2.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v1.2.0
197
307
  [1.1.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v1.1.0