@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.
@@ -0,0 +1,10 @@
1
+ {
2
+ "ExternalContent-Blocked-Title": "Cannot be displayed",
3
+ "ExternalContent-Blocked-Message": "This menu item points to an address that is not allowed. Ask an administrator to review it.",
4
+ "ExternalContent-OpenInNewTab": "Open in a new browser tab",
5
+ "ExternalContent-SlowHint": "Still loading. Some sites do not allow being displayed inside another application — try opening it in a new browser tab.",
6
+ "ExternalContent-Unavailable-Title": "Content unavailable",
7
+ "ExternalContent-Unavailable-Message": "Reopen this item from the menu to load it.",
8
+
9
+ "": ""
10
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "ExternalContent-Blocked-Title": "Não é possível exibir",
3
+ "ExternalContent-Blocked-Message": "Este item de menu aponta para um endereço não permitido. Peça a um administrador para revisá-lo.",
4
+ "ExternalContent-OpenInNewTab": "Abrir em nova aba do navegador",
5
+ "ExternalContent-SlowHint": "Ainda carregando. Alguns sites não permitem ser exibidos dentro de outra aplicação — tente abrir em uma nova aba do navegador.",
6
+ "ExternalContent-Unavailable-Title": "Conteúdo indisponível",
7
+ "ExternalContent-Unavailable-Message": "Reabra este item pelo menu para carregá-lo.",
8
+
9
+ "": ""
10
+ }
package/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './lib/auth';
2
2
  export * from './lib/components';
3
+ export { ExternalContentComponent, externalContentRoutes } from './lib/features/external-content';
3
4
  export { OperationsHistoryChildListComponent, OperationsHistoryModalComponent } from './lib/features/operations-history';
4
5
  export { ServicesHistoryChildListComponent, ServicesHistoryViewComponent } from './lib/features/services-history';
5
6
  export * from './lib/i18n';
@@ -0,0 +1,43 @@
1
+ import { OnInit } from '@angular/core';
2
+ import { SafeResourceUrl } from '@angular/platform-browser';
3
+ import { TabViewBase } from '@zambon-dev/framework';
4
+ import * as i0 from "@angular/core";
5
+ /**
6
+ * Displays an external destination inside an application tab.
7
+ *
8
+ * Routed as `/external-content/:menuID` — the destination itself never travels in the URL, so no
9
+ * one can hand-craft a link that makes the application frame an arbitrary site.
10
+ *
11
+ * A `TabViewBase` like any other screen, so its actions live in the ribbon rather than in a
12
+ * bar of its own: it must be routed under `DefaultTabViewComponent`, which is what renders the
13
+ * `#ribbon` template this view publishes. Use the exported `externalContentRoutes`.
14
+ */
15
+ export declare class ExternalContentComponent extends TabViewBase implements OnInit {
16
+ protected frameUrl?: SafeResourceUrl;
17
+ protected isBlocked: boolean;
18
+ protected isFrameLoading: boolean;
19
+ protected isSlow: boolean;
20
+ protected isUnavailable: boolean;
21
+ protected label: string;
22
+ protected resolvedUrl: string;
23
+ private activatedRoute;
24
+ private configs;
25
+ private externalContentService;
26
+ private externalUrlResolverService;
27
+ private sanitizer;
28
+ private slowHintTimeout?;
29
+ private tabService;
30
+ constructor();
31
+ ngOnDestroy(): void;
32
+ ngOnInit(): void;
33
+ protected onFrameLoad(): void;
34
+ protected onOpenInNewTab(): void;
35
+ protected onReload(): void;
36
+ private clearSlowHint;
37
+ private isOriginAllowed;
38
+ private show;
39
+ private displayFrame;
40
+ private startSlowHint;
41
+ static ɵfac: i0.ɵɵFactoryDeclaration<ExternalContentComponent, never>;
42
+ static ɵcmp: i0.ɵɵComponentDeclaration<ExternalContentComponent, "shared-external-content", never, {}, {}, never, never, true, never>;
43
+ }
@@ -0,0 +1,25 @@
1
+ import { Routes } from '@angular/router';
2
+ /**
3
+ * Route table for embedded external content. Spread it into `MainLayoutComponent`'s children:
4
+ *
5
+ * ```ts
6
+ * { path: '', component: MainLayoutComponent, canActivate: [AuthGuard], children: [
7
+ * ...externalContentRoutes,
8
+ * // the application's own features
9
+ * ] }
10
+ * ```
11
+ *
12
+ * Three things about the shape are load-bearing and must not be "simplified" away:
13
+ *
14
+ * - The path is nested one segment per route. `RouteHelper.getRouteURL` and
15
+ * `CustomReuseStrategy.getUrlFromRoute` collect segments walking up the parent chain and then
16
+ * reverse the flat list, so a route declared `'external-content/:menuID'` rebuilds as
17
+ * `/:menuID/external-content` and every tab URL is wrong.
18
+ * - `DefaultTabViewComponent` hosts the view. It is what renders the `#ribbon` template the
19
+ * component publishes, so dropping it leaves the Reload and Open-in-a-new-browser-tab actions
20
+ * with nowhere to appear.
21
+ * - `FRAMEWORK_VIEW_TYPE` must be present. `TabsComponent` re-creates the tab after a page refresh
22
+ * by looking for a `Details` or `List` view in the activated route tree, and navigates to `/`
23
+ * when it finds neither — the embedded tab would vanish on F5.
24
+ */
25
+ export declare const externalContentRoutes: Routes;
@@ -0,0 +1,2 @@
1
+ export { ExternalContentComponent } from './external-content.component';
2
+ export { externalContentRoutes } from './external-content.routes';
@@ -4,6 +4,8 @@ export declare class MainLayoutComponent implements OnInit, OnDestroy {
4
4
  private logoutModal;
5
5
  private authenticationService;
6
6
  private destroy$;
7
+ private externalContentService;
8
+ private externalUrlResolverService;
7
9
  private router;
8
10
  private sidebarService;
9
11
  private tabService;
@@ -13,6 +15,7 @@ export declare class MainLayoutComponent implements OnInit, OnDestroy {
13
15
  ngOnInit(): void;
14
16
  onLogoutClick(): void;
15
17
  onLogoutConfirm(): void;
18
+ private openExternalMenu;
16
19
  static ɵfac: i0.ɵɵFactoryDeclaration<MainLayoutComponent, never>;
17
20
  static ɵcmp: i0.ɵɵComponentDeclaration<MainLayoutComponent, "shared-main-layout", never, {}, {}, never, never, true, never>;
18
21
  }
@@ -1,8 +1,23 @@
1
1
  export interface ICurrentUserInfo {
2
2
  costCenterName: string;
3
+ /**
4
+ * Optional e-mail address. Present only when the sign-in response includes it; it is one of the
5
+ * values behind the `{email}` placeholder in an external menu URL.
6
+ */
7
+ email?: string;
3
8
  name: string;
4
9
  /** Optional avatar image URL. Falls back to initials when absent. */
5
10
  pictureUrl?: string;
6
11
  /** Optional job position/title shown under the user name. Line is hidden when absent. */
7
12
  position?: string;
13
+ /**
14
+ * Optional stable user identifier. Present only when the sign-in response includes it; it is
15
+ * the value behind the `{userId}` placeholder in an external menu URL.
16
+ */
17
+ userID?: number;
18
+ /**
19
+ * Login user name. Already carried by the persisted sign-in response; declared here because it
20
+ * is the value behind the `{userName}` placeholder in an external menu URL.
21
+ */
22
+ username?: string;
8
23
  }
@@ -0,0 +1,28 @@
1
+ import { InjectionToken } from '@angular/core';
2
+ /** First segment of the route that hosts embedded external content. */
3
+ export declare const EXTERNAL_CONTENT_ROUTE_PATH: string;
4
+ /**
5
+ * Flat projection of an external menu item, used to restore an embedded tab.
6
+ *
7
+ * Deliberately not a `SidebarMenu`: that class carries a `parent` back-reference, so it is a
8
+ * circular structure that `JSON.stringify` refuses, and holding one would pin the whole menu tree.
9
+ */
10
+ export interface IExternalContentEntry {
11
+ id: number;
12
+ label: string;
13
+ url: string;
14
+ }
15
+ export declare class ExternalContentConfigs {
16
+ /**
17
+ * Origins an embedded menu item may point at, for example `['https://reports.example.com']`.
18
+ * Empty — the default — allows any `http`/`https` origin.
19
+ *
20
+ * Populating it is the single highest-value control here after the scheme check: it narrows a
21
+ * compromised menu row from "frame anything on the internet" to "frame one of our report hosts".
22
+ */
23
+ allowedOrigins: string[];
24
+ /** Milliseconds to wait for the frame's first `load` before hinting that framing may be refused. */
25
+ slowFrameHintDelay: number;
26
+ constructor(options?: Partial<ExternalContentConfigs>);
27
+ }
28
+ export declare const EXTERNAL_CONTENT_CONFIGS: InjectionToken<ExternalContentConfigs>;
@@ -1,5 +1,6 @@
1
1
  export * from './auth-response';
2
2
  export * from './current-user-info';
3
+ export * from './external-content';
3
4
  export * from './notification';
4
5
  export * from './operations-history';
5
6
  export * from './services-history';
@@ -0,0 +1,35 @@
1
+ import { SidebarMenu } from '@zambon-dev/library';
2
+ import { Observable } from 'rxjs';
3
+ import { IExternalContentEntry } from '../models';
4
+ import * as i0 from "@angular/core";
5
+ /**
6
+ * Remembers which external destination sits behind an embedded tab's `/external-content/:menuID`
7
+ * route, and resolves it back when the view is created.
8
+ *
9
+ * The destination never travels in the route, so the id has to be resolved to a URL somehow. That
10
+ * happens in three steps, cheapest first:
11
+ *
12
+ * 1. the in-memory registry — the item was clicked in this browsing session;
13
+ * 2. `sessionStorage` — the user pressed F5 on the embedded tab;
14
+ * 3. the application's `SidebarService.getMenuFromUrl()` — a cold deep link.
15
+ *
16
+ * Step 3 reuses the abstract hook every application already implements, so nothing new is required
17
+ * of a consumer: an application whose menu endpoint cannot resolve `/external-content/:id` simply
18
+ * gets the unavailable state on a cold deep link, which is the documented behaviour.
19
+ */
20
+ export declare class ExternalContentService {
21
+ private readonly maxEntries;
22
+ private readonly storageKey;
23
+ private registry;
24
+ private sidebarService;
25
+ /** Resolves the destination behind an embedded tab, or `undefined` when it cannot be recovered. */
26
+ find(menuID: number): Observable<IExternalContentEntry | undefined>;
27
+ /** Remembers a menu so its embedded tab survives a page refresh. */
28
+ register(menu: SidebarMenu): void;
29
+ private readAllFromStorage;
30
+ private readFromStorage;
31
+ private toEntry;
32
+ private writeToStorage;
33
+ static ɵfac: i0.ɵɵFactoryDeclaration<ExternalContentService, never>;
34
+ static ɵprov: i0.ɵɵInjectableDeclaration<ExternalContentService>;
35
+ }
@@ -0,0 +1,49 @@
1
+ import * as i0 from "@angular/core";
2
+ /**
3
+ * Resolves the runtime placeholders in an external menu URL, and vets the result before it is
4
+ * handed to the browser.
5
+ *
6
+ * Placeholder syntax is `{name}`, case-sensitive, drawn from a **closed** set:
7
+ *
8
+ * | Token | Source |
9
+ * |--------------|-----------------------------------------------------|
10
+ * | `{email}` | `ICurrentUserInfo.email` |
11
+ * | `{language}` | `TranslateService.currentLang`, then `defaultLang` |
12
+ * | `{userId}` | `ICurrentUserInfo.userID` |
13
+ * | `{userName}` | `ICurrentUserInfo.username` |
14
+ *
15
+ * The set is closed by construction rather than reflected off the stored user info, and that is a
16
+ * security property, not a style choice: `AuthenticationService` persists the entire sign-in
17
+ * response under `userInfo`, tokens included, so a reflective implementation would let a menu URL
18
+ * configured as `?t={token}` hand the JWT to a third party. No authentication token is ever
19
+ * substituted.
20
+ */
21
+ export declare class ExternalUrlResolverService {
22
+ private authenticationService;
23
+ private translate;
24
+ /** Placeholder grammar, used only to *detect* tokens this version does not know. */
25
+ private readonly unknownPlaceholderPattern;
26
+ /**
27
+ * Whether `url` is an absolute `http`/`https` address.
28
+ *
29
+ * Rejects relative paths, malformed values, protocol-relative `//host/path` (which throws
30
+ * without a base), and the schemes that would execute rather than navigate: `javascript:`,
31
+ * `data:`, `blob:`, `file:`.
32
+ */
33
+ isAllowed(url: string): boolean;
34
+ /**
35
+ * Substitutes every known placeholder, URL-encoding each value.
36
+ *
37
+ * Because every value is percent-encoded, a placeholder must occupy one *whole* value — a full
38
+ * path segment, or a full query-parameter value. Encoding is what stops a display name that
39
+ * contains `&` from injecting an extra query parameter into the destination.
40
+ *
41
+ * A known placeholder with no value becomes an empty string; an unrecognized `{…}` is left
42
+ * exactly as configured, so a report URL that legitimately contains braces is not corrupted.
43
+ */
44
+ resolve(url: string): string;
45
+ private getPlaceholderValues;
46
+ private warnAboutUnknownPlaceholders;
47
+ static ɵfac: i0.ɵɵFactoryDeclaration<ExternalUrlResolverService, never>;
48
+ static ɵprov: i0.ɵɵInjectableDeclaration<ExternalUrlResolverService>;
49
+ }
@@ -1,4 +1,6 @@
1
1
  export { AuthenticationService } from './authentication.service';
2
+ export { ExternalContentService } from './external-content.service';
3
+ export { ExternalUrlResolverService } from './external-url-resolver.service';
2
4
  export { NotificationsService } from './notifications.service';
3
5
  export { OperationsHistoryService } from './operations-history.service';
4
6
  export { ServicesHistoryService } from './services-history.service';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zambon-dev/shared",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Shared Angular services, utilities, and styles for Zambon applications.",
5
5
  "keywords": [
6
6
  "angular",