@memberjunction/ng-dashboard-viewer 6.2.0-edge.4 → 6.2.0-edge.5

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.
@@ -38,7 +38,13 @@ export declare class WebURLPartComponent extends BaseDashboardPart implements Af
38
38
  private iframeLoaded;
39
39
  constructor(cdr: ChangeDetectorRef, sanitizer: DomSanitizer);
40
40
  ngAfterViewInit(): void;
41
+ protected cleanup(): void;
41
42
  loadContent(): Promise<void>;
43
+ /**
44
+ * True when the URL parses and uses a scheme that is safe to hand to
45
+ * bypassSecurityTrustResourceUrl for iframe embedding (http/https only).
46
+ */
47
+ private isEmbeddableUrl;
42
48
  /**
43
49
  * Check if the iframe content might be blocked.
44
50
  * Called after a timeout - if the iframe load event hasn't fired, show the fallback link.
@@ -1 +1 @@
1
- {"version":3,"file":"weburl-part.component.d.ts","sourceRoot":"","sources":["../../../src/lib/parts/weburl-part.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,iBAAiB,EAAE,UAAU,EAAa,aAAa,EAAE,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;;AAG1D;;;GAGG;AACH,qBA2Na,mBAAoB,SAAQ,iBAAkB,YAAW,aAAa;IAC1D,SAAS,EAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAE/D,yCAAyC;IACzC,IAAI,SAAS,IAAI,UAAU,CAAC,iBAAiB,CAAC,CAE7C;IACD,yCAAyC;IACzC,IAAI,SAAS,CAAC,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,EAEjD;IAEM,OAAO,EAAE,eAAe,GAAG,IAAI,CAAQ;IACvC,MAAM,EAAE,MAAM,CAAM;IAE3B,sCAAsC;IACtC,IAAW,MAAM,IAAI,MAAM,CAE1B;IACD,sCAAsC;IACtC,IAAW,MAAM,CAAC,KAAK,EAAE,MAAM,EAE9B;IACM,WAAW,EAAE,UAAU,GAAG,QAAQ,GAAG,YAAY,CAAc;IAEtE,2CAA2C;IAC3C,IAAW,WAAW,IAAI,UAAU,GAAG,QAAQ,GAAG,YAAY,CAE7D;IACD,2CAA2C;IAC3C,IAAW,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,QAAQ,GAAG,YAAY,EAEjE;IACM,eAAe,EAAE,OAAO,CAAQ;IAEvC,+CAA+C;IAC/C,IAAW,eAAe,IAAI,OAAO,CAEpC;IACD,+CAA+C;IAC/C,IAAW,eAAe,CAAC,KAAK,EAAE,OAAO,EAExC;IACM,gBAAgB,EAAE,OAAO,CAAS;IAEzC,gDAAgD;IAChD,IAAW,gBAAgB,IAAI,OAAO,CAErC;IACD,gDAAgD;IAChD,IAAW,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAEzC;IAED,OAAO,CAAC,SAAS,CAAe;IAChC,OAAO,CAAC,gBAAgB,CAA8C;IACtE,OAAO,CAAC,YAAY,CAAkB;gBAE1B,GAAG,EAAE,iBAAiB,EAAE,SAAS,EAAE,YAAY;IAK3D,eAAe,IAAI,IAAI;IAOV,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAkDzC;;;OAGG;IACH,OAAO,CAAC,cAAc;IAStB;;OAEG;IACI,YAAY,IAAI,IAAI;IAgB3B,4CAA4C;IACrC,YAAY,IAAI,IAAI;IAI3B;;OAEG;IACI,aAAa,CAAC,MAAM,EAAE,KAAK,GAAG,IAAI;IAKzC,6CAA6C;IACtC,aAAa,CAAC,MAAM,EAAE,KAAK,GAAG,IAAI;yCAtKhC,mBAAmB;2CAAnB,mBAAmB;CAyK/B"}
1
+ {"version":3,"file":"weburl-part.component.d.ts","sourceRoot":"","sources":["../../../src/lib/parts/weburl-part.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,iBAAiB,EAAE,UAAU,EAAa,aAAa,EAAE,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;;AAG1D;;;GAGG;AACH,qBA2Na,mBAAoB,SAAQ,iBAAkB,YAAW,aAAa;IAC1D,SAAS,EAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAE/D,yCAAyC;IACzC,IAAI,SAAS,IAAI,UAAU,CAAC,iBAAiB,CAAC,CAE7C;IACD,yCAAyC;IACzC,IAAI,SAAS,CAAC,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,EAEjD;IAEM,OAAO,EAAE,eAAe,GAAG,IAAI,CAAQ;IACvC,MAAM,EAAE,MAAM,CAAM;IAE3B,sCAAsC;IACtC,IAAW,MAAM,IAAI,MAAM,CAE1B;IACD,sCAAsC;IACtC,IAAW,MAAM,CAAC,KAAK,EAAE,MAAM,EAE9B;IACM,WAAW,EAAE,UAAU,GAAG,QAAQ,GAAG,YAAY,CAAc;IAEtE,2CAA2C;IAC3C,IAAW,WAAW,IAAI,UAAU,GAAG,QAAQ,GAAG,YAAY,CAE7D;IACD,2CAA2C;IAC3C,IAAW,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,QAAQ,GAAG,YAAY,EAEjE;IACM,eAAe,EAAE,OAAO,CAAQ;IAEvC,+CAA+C;IAC/C,IAAW,eAAe,IAAI,OAAO,CAEpC;IACD,+CAA+C;IAC/C,IAAW,eAAe,CAAC,KAAK,EAAE,OAAO,EAExC;IACM,gBAAgB,EAAE,OAAO,CAAS;IAEzC,gDAAgD;IAChD,IAAW,gBAAgB,IAAI,OAAO,CAErC;IACD,gDAAgD;IAChD,IAAW,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAEzC;IAED,OAAO,CAAC,SAAS,CAAe;IAChC,OAAO,CAAC,gBAAgB,CAA8C;IACtE,OAAO,CAAC,YAAY,CAAkB;gBAE1B,GAAG,EAAE,iBAAiB,EAAE,SAAS,EAAE,YAAY;IAK3D,eAAe,IAAI,IAAI;cAOJ,OAAO,IAAI,IAAI;IAQrB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IA6DzC;;;OAGG;IACH,OAAO,CAAC,eAAe;IASvB;;;OAGG;IACH,OAAO,CAAC,cAAc;IAStB;;OAEG;IACI,YAAY,IAAI,IAAI;IAgB3B,4CAA4C;IACrC,YAAY,IAAI,IAAI;IAI3B;;OAEG;IACI,aAAa,CAAC,MAAM,EAAE,KAAK,GAAG,IAAI;IAKzC,6CAA6C;IACtC,aAAa,CAAC,MAAM,EAAE,KAAK,GAAG,IAAI;yCAtMhC,mBAAmB;2CAAnB,mBAAmB;CAyM/B"}
@@ -192,6 +192,13 @@ let WebURLPartComponent = class WebURLPartComponent extends BaseDashboardPart {
192
192
  this.loadContent();
193
193
  }
194
194
  }
195
+ cleanup() {
196
+ if (this.loadCheckTimeout) {
197
+ clearTimeout(this.loadCheckTimeout);
198
+ this.loadCheckTimeout = null;
199
+ }
200
+ super.cleanup();
201
+ }
195
202
  async loadContent() {
196
203
  const config = this.getConfig();
197
204
  const url = config?.['url'];
@@ -203,12 +210,22 @@ let WebURLPartComponent = class WebURLPartComponent extends BaseDashboardPart {
203
210
  // Reset state
204
211
  this.iframeLoaded = false;
205
212
  this.ShowFallbackLink = false;
213
+ this.ErrorMessage = null;
206
214
  if (!url) {
207
215
  this.SafeUrl = null;
208
216
  this.RawUrl = '';
209
217
  this.cdr.detectChanges();
210
218
  return;
211
219
  }
220
+ // SECURITY: only http(s) URLs may be embedded. bypassSecurityTrustResourceUrl disables
221
+ // Angular's sanitizer, so without this check a stored javascript: (or other scheme) URL
222
+ // in a shared dashboard panel would execute in every viewer's browser.
223
+ if (!this.isEmbeddableUrl(url)) {
224
+ this.SafeUrl = null;
225
+ this.RawUrl = '';
226
+ this.setError('Only http:// and https:// URLs can be embedded.');
227
+ return;
228
+ }
212
229
  this.setLoading(true);
213
230
  try {
214
231
  // Store raw URL for fallback link
@@ -231,6 +248,19 @@ let WebURLPartComponent = class WebURLPartComponent extends BaseDashboardPart {
231
248
  this.setError(error instanceof Error ? error.message : 'Failed to load URL');
232
249
  }
233
250
  }
251
+ /**
252
+ * True when the URL parses and uses a scheme that is safe to hand to
253
+ * bypassSecurityTrustResourceUrl for iframe embedding (http/https only).
254
+ */
255
+ isEmbeddableUrl(url) {
256
+ try {
257
+ const parsed = new URL(url);
258
+ return parsed.protocol === 'http:' || parsed.protocol === 'https:';
259
+ }
260
+ catch {
261
+ return false;
262
+ }
263
+ }
234
264
  /**
235
265
  * Check if the iframe content might be blocked.
236
266
  * Called after a timeout - if the iframe load event hasn't fired, show the fallback link.
@@ -280,7 +310,7 @@ let WebURLPartComponent = class WebURLPartComponent extends BaseDashboardPart {
280
310
  } if (rf & 2) {
281
311
  let _t;
282
312
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.IframeRef = _t.first);
283
- } }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 6, vars: 9, consts: [["iframe", ""], [1, "weburl-part"], [1, "loading-state"], ["Variant", "error", "Icon", "fa-solid fa-triangle-exclamation", "Title", "Couldn't load content", "Size", "compact", 1, "part-placeholder", 3, "Message"], ["Icon", "fa-solid fa-globe", "Title", "No URL Configured", "Message", "Use Edit part to set a URL.", "Size", "compact", 1, "part-placeholder"], [1, "iframe-fallback"], [1, "fa-solid", "fa-spinner", "fa-spin"], ["actions", "", "target", "_blank", 1, "open-link", 3, "href"], [1, "fa-solid", "fa-up-right-from-square"], ["sandbox", "allow-scripts allow-same-origin allow-forms allow-popups", "allowfullscreen", "", 1, "content-iframe", 3, "src", "title"], ["sandbox", "allow-scripts allow-same-origin allow-forms allow-popups", 1, "content-iframe", 3, "src", "title"], ["sandbox", "allow-scripts", "allowfullscreen", "", 1, "content-iframe", 3, "src", "title"], ["sandbox", "allow-scripts", 1, "content-iframe", 3, "src", "title"], ["sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-top-navigation", "allowfullscreen", "", 1, "content-iframe", 3, "src", "title"], ["sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-top-navigation", 1, "content-iframe", 3, "src", "title"], ["sandbox", "allow-scripts allow-same-origin allow-forms allow-popups", "allowfullscreen", "", 1, "content-iframe", 3, "load", "error", "src", "title"], ["sandbox", "allow-scripts allow-same-origin allow-forms allow-popups", 1, "content-iframe", 3, "load", "error", "src", "title"], ["sandbox", "allow-scripts", "allowfullscreen", "", 1, "content-iframe", 3, "load", "error", "src", "title"], ["sandbox", "allow-scripts", 1, "content-iframe", 3, "load", "error", "src", "title"], ["sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-top-navigation", "allowfullscreen", "", 1, "content-iframe", 3, "load", "error", "src", "title"], ["sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-top-navigation", 1, "content-iframe", 3, "load", "error", "src", "title"], ["target", "_blank", 3, "href"], [1, "fa-solid", "fa-external-link-alt"]], template: function WebURLPartComponent_Template(rf, ctx) { if (rf & 1) {
313
+ } }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 6, vars: 9, consts: [["iframe", ""], [1, "weburl-part"], [1, "loading-state"], ["Variant", "error", "Icon", "fa-solid fa-triangle-exclamation", "Title", "Couldn't load content", "Size", "compact", 1, "part-placeholder", 3, "Message"], ["Icon", "fa-solid fa-globe", "Title", "No URL Configured", "Message", "Use Edit part to set a URL.", "Size", "compact", 1, "part-placeholder"], [1, "iframe-fallback"], [1, "fa-solid", "fa-spinner", "fa-spin"], ["actions", "", "target", "_blank", "rel", "noopener noreferrer", 1, "open-link", 3, "href"], [1, "fa-solid", "fa-up-right-from-square"], ["sandbox", "allow-scripts allow-same-origin allow-forms allow-popups", "allowfullscreen", "", 1, "content-iframe", 3, "src", "title"], ["sandbox", "allow-scripts allow-same-origin allow-forms allow-popups", 1, "content-iframe", 3, "src", "title"], ["sandbox", "allow-scripts", "allowfullscreen", "", 1, "content-iframe", 3, "src", "title"], ["sandbox", "allow-scripts", 1, "content-iframe", 3, "src", "title"], ["sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-top-navigation", "allowfullscreen", "", 1, "content-iframe", 3, "src", "title"], ["sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-top-navigation", 1, "content-iframe", 3, "src", "title"], ["sandbox", "allow-scripts allow-same-origin allow-forms allow-popups", "allowfullscreen", "", 1, "content-iframe", 3, "load", "error", "src", "title"], ["sandbox", "allow-scripts allow-same-origin allow-forms allow-popups", 1, "content-iframe", 3, "load", "error", "src", "title"], ["sandbox", "allow-scripts", "allowfullscreen", "", 1, "content-iframe", 3, "load", "error", "src", "title"], ["sandbox", "allow-scripts", 1, "content-iframe", 3, "load", "error", "src", "title"], ["sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-top-navigation", "allowfullscreen", "", 1, "content-iframe", 3, "load", "error", "src", "title"], ["sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-top-navigation", 1, "content-iframe", 3, "load", "error", "src", "title"], ["target", "_blank", "rel", "noopener noreferrer", 3, "href"], [1, "fa-solid", "fa-external-link-alt"]], template: function WebURLPartComponent_Template(rf, ctx) { if (rf & 1) {
284
314
  i0.ɵɵelementStart(0, "div", 1);
285
315
  i0.ɵɵconditionalCreate(1, WebURLPartComponent_Conditional_1_Template, 4, 0, "div", 2);
286
316
  i0.ɵɵconditionalCreate(2, WebURLPartComponent_Conditional_2_Template, 2, 2, "mj-empty-state", 3);
@@ -328,7 +358,7 @@ export { WebURLPartComponent };
328
358
  [Message]="ErrorMessage"
329
359
  Size="compact">
330
360
  @if (rawUrl) {
331
- <a actions [href]="rawUrl" target="_blank" class="open-link">
361
+ <a actions [href]="rawUrl" target="_blank" rel="noopener noreferrer" class="open-link">
332
362
  <i class="fa-solid fa-up-right-from-square"></i>
333
363
  Open in new window
334
364
  </a>
@@ -429,7 +459,7 @@ export { WebURLPartComponent };
429
459
  @if (!IsLoading && !ErrorMessage && SafeUrl && showFallbackLink) {
430
460
  <div class="iframe-fallback">
431
461
  <span>If the content doesn't load:</span>
432
- <a [href]="rawUrl" target="_blank">
462
+ <a [href]="rawUrl" target="_blank" rel="noopener noreferrer">
433
463
  <i class="fa-solid fa-external-link-alt"></i>
434
464
  Open in new window
435
465
  </a>
@@ -1 +1 @@
1
- {"version":3,"file":"weburl-part.component.js","sourceRoot":"","sources":["../../../src/lib/parts/weburl-part.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, ChangeDetectorRef, ElementRef, ViewChild, AfterViewInit } from '@angular/core';\nimport { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';\nimport { RegisterClass } from '@memberjunction/global';\nimport { BaseDashboardPart } from './base-dashboard-part';\nimport { PanelConfig } from '../models/dashboard-types';\n\n/**\n * Runtime renderer for WebURL dashboard parts.\n * Displays web content in an iframe with configurable sandbox permissions.\n */\n@RegisterClass(BaseDashboardPart, 'WebURLPanelRenderer')\n@Component({\n standalone: false,\n selector: 'mj-weburl-part',\n template: `\n <div class=\"weburl-part\" [class.loading]=\"IsLoading\" [class.error]=\"ErrorMessage\">\n <!-- Loading state -->\n @if (IsLoading) {\n <div class=\"loading-state\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span>Loading...</span>\n </div>\n }\n \n <!-- Error state -->\n @if (ErrorMessage) {\n <mj-empty-state\n class=\"part-placeholder\"\n Variant=\"error\"\n Icon=\"fa-solid fa-triangle-exclamation\"\n Title=\"Couldn't load content\"\n [Message]=\"ErrorMessage\"\n Size=\"compact\">\n @if (rawUrl) {\n <a actions [href]=\"rawUrl\" target=\"_blank\" class=\"open-link\">\n <i class=\"fa-solid fa-up-right-from-square\"></i>\n Open in new window\n </a>\n }\n </mj-empty-state>\n }\n\n <!-- No URL configured -->\n @if (!IsLoading && !ErrorMessage && !SafeUrl) {\n <mj-empty-state\n class=\"part-placeholder\"\n Icon=\"fa-solid fa-globe\"\n Title=\"No URL Configured\"\n Message=\"Use Edit part to set a URL.\"\n Size=\"compact\" />\n }\n \n <!-- Iframe container - sandbox and allowfullscreen must be static, so we use ng-container to switch -->\n @if (!IsLoading && !ErrorMessage && SafeUrl) {\n <!-- Standard sandbox + fullscreen enabled -->\n @if (sandboxMode === 'standard' && allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups\"\n allowfullscreen\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Standard sandbox + fullscreen disabled -->\n @if (sandboxMode === 'standard' && !allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups\"\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Strict sandbox + fullscreen enabled -->\n @if (sandboxMode === 'strict' && allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts\"\n allowfullscreen\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Strict sandbox + fullscreen disabled -->\n @if (sandboxMode === 'strict' && !allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts\"\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Permissive sandbox + fullscreen enabled -->\n @if (sandboxMode === 'permissive' && allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-top-navigation\"\n allowfullscreen\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Permissive sandbox + fullscreen disabled -->\n @if (sandboxMode === 'permissive' && !allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-top-navigation\"\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n }\n \n <!-- Fallback link shown below iframe if content might be blocked -->\n @if (!IsLoading && !ErrorMessage && SafeUrl && showFallbackLink) {\n <div class=\"iframe-fallback\">\n <span>If the content doesn't load:</span>\n <a [href]=\"rawUrl\" target=\"_blank\">\n <i class=\"fa-solid fa-external-link-alt\"></i>\n Open in new window\n </a>\n </div>\n }\n </div>\n `,\n styles: [`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .weburl-part {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n position: relative; /* Required for overlay positioning */\n }\n\n .loading-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--mj-text-secondary);\n text-align: center;\n padding: 24px;\n }\n\n .loading-state i {\n font-size: 48px;\n color: var(--mj-text-muted);\n margin-bottom: 16px;\n }\n\n .part-placeholder {\n height: 100%;\n }\n\n /* Projected \"open in new window\" link inside the error empty-state */\n .open-link {\n padding: 8px 16px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n text-decoration: none;\n border-radius: 4px;\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n }\n\n .open-link:hover {\n background: var(--mj-brand-primary-hover);\n }\n\n .content-iframe {\n width: 100%;\n height: 100%;\n border: none;\n flex: 1;\n }\n\n .iframe-fallback {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 8px;\n background: var(--mj-bg-surface-card);\n border-top: 1px solid var(--mj-border-default);\n font-size: 12px;\n color: var(--mj-text-secondary);\n }\n\n .iframe-fallback a {\n color: var(--mj-brand-primary);\n text-decoration: none;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .iframe-fallback a:hover {\n text-decoration: underline;\n }\n `]\n})\nexport class WebURLPartComponent extends BaseDashboardPart implements AfterViewInit {\n @ViewChild('iframe') IframeRef!: ElementRef<HTMLIFrameElement>;\n\n /** @deprecated Use {@link IframeRef}. */\n get iframeRef(): ElementRef<HTMLIFrameElement> {\n return this.IframeRef;\n }\n /** @deprecated Use {@link IframeRef}. */\n set iframeRef(value: ElementRef<HTMLIFrameElement>) {\n this.IframeRef = value;\n }\n\n public SafeUrl: SafeResourceUrl | null = null;\n public RawUrl: string = '';\n\n /** @deprecated Use {@link RawUrl}. */\n public get rawUrl(): string {\n return this.RawUrl;\n }\n /** @deprecated Use {@link RawUrl}. */\n public set rawUrl(value: string) {\n this.RawUrl = value;\n }\n public SandboxMode: 'standard' | 'strict' | 'permissive' = 'standard';\n\n /** @deprecated Use {@link SandboxMode}. */\n public get sandboxMode(): 'standard' | 'strict' | 'permissive' {\n return this.SandboxMode;\n }\n /** @deprecated Use {@link SandboxMode}. */\n public set sandboxMode(value: 'standard' | 'strict' | 'permissive') {\n this.SandboxMode = value;\n }\n public AllowFullscreen: boolean = true;\n\n /** @deprecated Use {@link AllowFullscreen}. */\n public get allowFullscreen(): boolean {\n return this.AllowFullscreen;\n }\n /** @deprecated Use {@link AllowFullscreen}. */\n public set allowFullscreen(value: boolean) {\n this.AllowFullscreen = value;\n }\n public ShowFallbackLink: boolean = false;\n\n /** @deprecated Use {@link ShowFallbackLink}. */\n public get showFallbackLink(): boolean {\n return this.ShowFallbackLink;\n }\n /** @deprecated Use {@link ShowFallbackLink}. */\n public set showFallbackLink(value: boolean) {\n this.ShowFallbackLink = value;\n } // Hidden by default, shown if content might be blocked\n\n private sanitizer: DomSanitizer;\n private loadCheckTimeout: ReturnType<typeof setTimeout> | null = null;\n private iframeLoaded: boolean = false;\n\n constructor(cdr: ChangeDetectorRef, sanitizer: DomSanitizer) {\n super(cdr);\n this.sanitizer = sanitizer;\n }\n\n ngAfterViewInit(): void {\n // Initial load if panel is already set\n if (this.Panel) {\n this.loadContent();\n }\n }\n\n public async loadContent(): Promise<void> {\n const config = this.getConfig<PanelConfig>();\n const url = config?.['url'] as string | undefined;\n\n // Clear any existing timeout\n if (this.loadCheckTimeout) {\n clearTimeout(this.loadCheckTimeout);\n this.loadCheckTimeout = null;\n }\n\n // Reset state\n this.iframeLoaded = false;\n this.ShowFallbackLink = false;\n\n if (!url) {\n this.SafeUrl = null;\n this.RawUrl = '';\n this.cdr.detectChanges();\n return;\n }\n\n this.setLoading(true);\n\n try {\n // Store raw URL for fallback link\n this.RawUrl = url;\n\n // Sanitize and set URL for iframe src binding\n this.SafeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);\n\n // Set sandbox mode (used by template to select correct iframe)\n this.SandboxMode = (config?.['sandboxMode'] as 'standard' | 'strict' | 'permissive') || 'standard';\n\n // Set fullscreen permission\n this.AllowFullscreen = (config?.['allowFullscreen'] as boolean) ?? true;\n\n this.setLoading(false);\n\n // Set up a delayed check to detect if embedding might be blocked\n // If after 3 seconds the iframe hasn't successfully loaded content we can access,\n // show the blocked overlay. This is a heuristic since we can't detect X-Frame-Options directly.\n this.loadCheckTimeout = setTimeout(() => {\n this.checkIfBlocked();\n }, 3000);\n\n } catch (error) {\n this.setError(error instanceof Error ? error.message : 'Failed to load URL');\n }\n }\n\n /**\n * Check if the iframe content might be blocked.\n * Called after a timeout - if the iframe load event hasn't fired, show the fallback link.\n */\n private checkIfBlocked(): void {\n // If the load event hasn't fired after the timeout, content might be blocked\n if (!this.iframeLoaded) {\n this.ShowFallbackLink = true;\n this.cdr.detectChanges();\n }\n // If iframeLoaded is true, content loaded successfully - keep fallback hidden\n }\n\n /**\n * Handle iframe load event\n */\n public OnIframeLoad(): void {\n // Mark that the load event fired\n this.iframeLoaded = true;\n\n // Clear the timeout since the iframe loaded\n if (this.loadCheckTimeout) {\n clearTimeout(this.loadCheckTimeout);\n this.loadCheckTimeout = null;\n }\n\n // The iframe loaded something - could still be blocked content\n // We can't reliably detect X-Frame-Options blocking from JS\n // The fallback link provides a way for users to open the site if it doesn't load\n this.cdr.detectChanges();\n }\n\n /** @deprecated Use {@link OnIframeLoad}. */\n public onIframeLoad(): void {\n return this.OnIframeLoad();\n }\n\n /**\n * Handle iframe error event\n */\n public OnIframeError(_event: Event): void {\n // This may fire for some load failures, but not for X-Frame-Options\n this.setError('This site cannot be embedded. It may block iframe embedding for security reasons.');\n }\n\n /** @deprecated Use {@link OnIframeError}. */\n public onIframeError(_event: Event): void {\n return this.OnIframeError(_event);\n }\n}\n"]}
1
+ {"version":3,"file":"weburl-part.component.js","sourceRoot":"","sources":["../../../src/lib/parts/weburl-part.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, ChangeDetectorRef, ElementRef, ViewChild, AfterViewInit } from '@angular/core';\nimport { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';\nimport { RegisterClass } from '@memberjunction/global';\nimport { BaseDashboardPart } from './base-dashboard-part';\nimport { PanelConfig } from '../models/dashboard-types';\n\n/**\n * Runtime renderer for WebURL dashboard parts.\n * Displays web content in an iframe with configurable sandbox permissions.\n */\n@RegisterClass(BaseDashboardPart, 'WebURLPanelRenderer')\n@Component({\n standalone: false,\n selector: 'mj-weburl-part',\n template: `\n <div class=\"weburl-part\" [class.loading]=\"IsLoading\" [class.error]=\"ErrorMessage\">\n <!-- Loading state -->\n @if (IsLoading) {\n <div class=\"loading-state\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span>Loading...</span>\n </div>\n }\n \n <!-- Error state -->\n @if (ErrorMessage) {\n <mj-empty-state\n class=\"part-placeholder\"\n Variant=\"error\"\n Icon=\"fa-solid fa-triangle-exclamation\"\n Title=\"Couldn't load content\"\n [Message]=\"ErrorMessage\"\n Size=\"compact\">\n @if (rawUrl) {\n <a actions [href]=\"rawUrl\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"open-link\">\n <i class=\"fa-solid fa-up-right-from-square\"></i>\n Open in new window\n </a>\n }\n </mj-empty-state>\n }\n\n <!-- No URL configured -->\n @if (!IsLoading && !ErrorMessage && !SafeUrl) {\n <mj-empty-state\n class=\"part-placeholder\"\n Icon=\"fa-solid fa-globe\"\n Title=\"No URL Configured\"\n Message=\"Use Edit part to set a URL.\"\n Size=\"compact\" />\n }\n \n <!-- Iframe container - sandbox and allowfullscreen must be static, so we use ng-container to switch -->\n @if (!IsLoading && !ErrorMessage && SafeUrl) {\n <!-- Standard sandbox + fullscreen enabled -->\n @if (sandboxMode === 'standard' && allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups\"\n allowfullscreen\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Standard sandbox + fullscreen disabled -->\n @if (sandboxMode === 'standard' && !allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups\"\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Strict sandbox + fullscreen enabled -->\n @if (sandboxMode === 'strict' && allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts\"\n allowfullscreen\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Strict sandbox + fullscreen disabled -->\n @if (sandboxMode === 'strict' && !allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts\"\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Permissive sandbox + fullscreen enabled -->\n @if (sandboxMode === 'permissive' && allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-top-navigation\"\n allowfullscreen\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Permissive sandbox + fullscreen disabled -->\n @if (sandboxMode === 'permissive' && !allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-top-navigation\"\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n }\n \n <!-- Fallback link shown below iframe if content might be blocked -->\n @if (!IsLoading && !ErrorMessage && SafeUrl && showFallbackLink) {\n <div class=\"iframe-fallback\">\n <span>If the content doesn't load:</span>\n <a [href]=\"rawUrl\" target=\"_blank\" rel=\"noopener noreferrer\">\n <i class=\"fa-solid fa-external-link-alt\"></i>\n Open in new window\n </a>\n </div>\n }\n </div>\n `,\n styles: [`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .weburl-part {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n position: relative; /* Required for overlay positioning */\n }\n\n .loading-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--mj-text-secondary);\n text-align: center;\n padding: 24px;\n }\n\n .loading-state i {\n font-size: 48px;\n color: var(--mj-text-muted);\n margin-bottom: 16px;\n }\n\n .part-placeholder {\n height: 100%;\n }\n\n /* Projected \"open in new window\" link inside the error empty-state */\n .open-link {\n padding: 8px 16px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n text-decoration: none;\n border-radius: 4px;\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n }\n\n .open-link:hover {\n background: var(--mj-brand-primary-hover);\n }\n\n .content-iframe {\n width: 100%;\n height: 100%;\n border: none;\n flex: 1;\n }\n\n .iframe-fallback {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 8px;\n background: var(--mj-bg-surface-card);\n border-top: 1px solid var(--mj-border-default);\n font-size: 12px;\n color: var(--mj-text-secondary);\n }\n\n .iframe-fallback a {\n color: var(--mj-brand-primary);\n text-decoration: none;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .iframe-fallback a:hover {\n text-decoration: underline;\n }\n `]\n})\nexport class WebURLPartComponent extends BaseDashboardPart implements AfterViewInit {\n @ViewChild('iframe') IframeRef!: ElementRef<HTMLIFrameElement>;\n\n /** @deprecated Use {@link IframeRef}. */\n get iframeRef(): ElementRef<HTMLIFrameElement> {\n return this.IframeRef;\n }\n /** @deprecated Use {@link IframeRef}. */\n set iframeRef(value: ElementRef<HTMLIFrameElement>) {\n this.IframeRef = value;\n }\n\n public SafeUrl: SafeResourceUrl | null = null;\n public RawUrl: string = '';\n\n /** @deprecated Use {@link RawUrl}. */\n public get rawUrl(): string {\n return this.RawUrl;\n }\n /** @deprecated Use {@link RawUrl}. */\n public set rawUrl(value: string) {\n this.RawUrl = value;\n }\n public SandboxMode: 'standard' | 'strict' | 'permissive' = 'standard';\n\n /** @deprecated Use {@link SandboxMode}. */\n public get sandboxMode(): 'standard' | 'strict' | 'permissive' {\n return this.SandboxMode;\n }\n /** @deprecated Use {@link SandboxMode}. */\n public set sandboxMode(value: 'standard' | 'strict' | 'permissive') {\n this.SandboxMode = value;\n }\n public AllowFullscreen: boolean = true;\n\n /** @deprecated Use {@link AllowFullscreen}. */\n public get allowFullscreen(): boolean {\n return this.AllowFullscreen;\n }\n /** @deprecated Use {@link AllowFullscreen}. */\n public set allowFullscreen(value: boolean) {\n this.AllowFullscreen = value;\n }\n public ShowFallbackLink: boolean = false;\n\n /** @deprecated Use {@link ShowFallbackLink}. */\n public get showFallbackLink(): boolean {\n return this.ShowFallbackLink;\n }\n /** @deprecated Use {@link ShowFallbackLink}. */\n public set showFallbackLink(value: boolean) {\n this.ShowFallbackLink = value;\n } // Hidden by default, shown if content might be blocked\n\n private sanitizer: DomSanitizer;\n private loadCheckTimeout: ReturnType<typeof setTimeout> | null = null;\n private iframeLoaded: boolean = false;\n\n constructor(cdr: ChangeDetectorRef, sanitizer: DomSanitizer) {\n super(cdr);\n this.sanitizer = sanitizer;\n }\n\n ngAfterViewInit(): void {\n // Initial load if panel is already set\n if (this.Panel) {\n this.loadContent();\n }\n }\n\n protected override cleanup(): void {\n if (this.loadCheckTimeout) {\n clearTimeout(this.loadCheckTimeout);\n this.loadCheckTimeout = null;\n }\n super.cleanup();\n }\n\n public async loadContent(): Promise<void> {\n const config = this.getConfig<PanelConfig>();\n const url = config?.['url'] as string | undefined;\n\n // Clear any existing timeout\n if (this.loadCheckTimeout) {\n clearTimeout(this.loadCheckTimeout);\n this.loadCheckTimeout = null;\n }\n\n // Reset state\n this.iframeLoaded = false;\n this.ShowFallbackLink = false;\n this.ErrorMessage = null;\n\n if (!url) {\n this.SafeUrl = null;\n this.RawUrl = '';\n this.cdr.detectChanges();\n return;\n }\n\n // SECURITY: only http(s) URLs may be embedded. bypassSecurityTrustResourceUrl disables\n // Angular's sanitizer, so without this check a stored javascript: (or other scheme) URL\n // in a shared dashboard panel would execute in every viewer's browser.\n if (!this.isEmbeddableUrl(url)) {\n this.SafeUrl = null;\n this.RawUrl = '';\n this.setError('Only http:// and https:// URLs can be embedded.');\n return;\n }\n\n this.setLoading(true);\n\n try {\n // Store raw URL for fallback link\n this.RawUrl = url;\n\n // Sanitize and set URL for iframe src binding\n this.SafeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);\n\n // Set sandbox mode (used by template to select correct iframe)\n this.SandboxMode = (config?.['sandboxMode'] as 'standard' | 'strict' | 'permissive') || 'standard';\n\n // Set fullscreen permission\n this.AllowFullscreen = (config?.['allowFullscreen'] as boolean) ?? true;\n\n this.setLoading(false);\n\n // Set up a delayed check to detect if embedding might be blocked\n // If after 3 seconds the iframe hasn't successfully loaded content we can access,\n // show the blocked overlay. This is a heuristic since we can't detect X-Frame-Options directly.\n this.loadCheckTimeout = setTimeout(() => {\n this.checkIfBlocked();\n }, 3000);\n\n } catch (error) {\n this.setError(error instanceof Error ? error.message : 'Failed to load URL');\n }\n }\n\n /**\n * True when the URL parses and uses a scheme that is safe to hand to\n * bypassSecurityTrustResourceUrl for iframe embedding (http/https only).\n */\n private isEmbeddableUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n return parsed.protocol === 'http:' || parsed.protocol === 'https:';\n } catch {\n return false;\n }\n }\n\n /**\n * Check if the iframe content might be blocked.\n * Called after a timeout - if the iframe load event hasn't fired, show the fallback link.\n */\n private checkIfBlocked(): void {\n // If the load event hasn't fired after the timeout, content might be blocked\n if (!this.iframeLoaded) {\n this.ShowFallbackLink = true;\n this.cdr.detectChanges();\n }\n // If iframeLoaded is true, content loaded successfully - keep fallback hidden\n }\n\n /**\n * Handle iframe load event\n */\n public OnIframeLoad(): void {\n // Mark that the load event fired\n this.iframeLoaded = true;\n\n // Clear the timeout since the iframe loaded\n if (this.loadCheckTimeout) {\n clearTimeout(this.loadCheckTimeout);\n this.loadCheckTimeout = null;\n }\n\n // The iframe loaded something - could still be blocked content\n // We can't reliably detect X-Frame-Options blocking from JS\n // The fallback link provides a way for users to open the site if it doesn't load\n this.cdr.detectChanges();\n }\n\n /** @deprecated Use {@link OnIframeLoad}. */\n public onIframeLoad(): void {\n return this.OnIframeLoad();\n }\n\n /**\n * Handle iframe error event\n */\n public OnIframeError(_event: Event): void {\n // This may fire for some load failures, but not for X-Frame-Options\n this.setError('This site cannot be embedded. It may block iframe embedding for security reasons.');\n }\n\n /** @deprecated Use {@link OnIframeError}. */\n public onIframeError(_event: Event): void {\n return this.OnIframeError(_event);\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-dashboard-viewer",
3
- "version": "6.2.0-edge.4",
3
+ "version": "6.2.0-edge.5",
4
4
  "description": "MemberJunction: Angular components for metadata-driven dashboards with Golden Layout panels, supporting views, queries, artifacts, and custom content",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -21,7 +21,7 @@
21
21
  "devDependencies": {
22
22
  "@angular/compiler": "21.2.22",
23
23
  "@angular/compiler-cli": "21.2.22",
24
- "@memberjunction/ng-test-utils": "6.2.0-edge.4",
24
+ "@memberjunction/ng-test-utils": "6.2.0-edge.5",
25
25
  "vitest": "^4.0.18"
26
26
  },
27
27
  "peerDependencies": {
@@ -33,17 +33,17 @@
33
33
  "golden-layout": "^2.6.0"
34
34
  },
35
35
  "dependencies": {
36
- "@memberjunction/ng-ui-components": "6.2.0-edge.4",
37
- "@memberjunction/core": "6.2.0-edge.4",
38
- "@memberjunction/core-entities": "6.2.0-edge.4",
39
- "@memberjunction/global": "6.2.0-edge.4",
40
- "@memberjunction/ng-artifacts": "6.2.0-edge.4",
41
- "@memberjunction/ng-base-types": "6.2.0-edge.4",
42
- "@memberjunction/ng-entity-viewer": "6.2.0-edge.4",
43
- "@memberjunction/ng-map-view": "6.2.0-edge.4",
44
- "@memberjunction/ng-query-viewer": "6.2.0-edge.4",
45
- "@memberjunction/ng-shared-generic": "6.2.0-edge.4",
46
- "@memberjunction/ng-trees": "6.2.0-edge.4",
36
+ "@memberjunction/ng-ui-components": "6.2.0-edge.5",
37
+ "@memberjunction/core": "6.2.0-edge.5",
38
+ "@memberjunction/core-entities": "6.2.0-edge.5",
39
+ "@memberjunction/global": "6.2.0-edge.5",
40
+ "@memberjunction/ng-artifacts": "6.2.0-edge.5",
41
+ "@memberjunction/ng-base-types": "6.2.0-edge.5",
42
+ "@memberjunction/ng-entity-viewer": "6.2.0-edge.5",
43
+ "@memberjunction/ng-map-view": "6.2.0-edge.5",
44
+ "@memberjunction/ng-query-viewer": "6.2.0-edge.5",
45
+ "@memberjunction/ng-shared-generic": "6.2.0-edge.5",
46
+ "@memberjunction/ng-trees": "6.2.0-edge.5",
47
47
  "rxjs": "^7.8.2",
48
48
  "tslib": "^2.8.1"
49
49
  },