@zambon-dev/shared 2.0.0 → 2.1.1

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