Skip to content

Commit 97bc483

Browse files
feat(elements): pos-html-tool and sanitizeHtmlTool
1 parent 624a4a0 commit 97bc483

File tree

9 files changed

+575
-5
lines changed

9 files changed

+575
-5
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
<!-- Auto Generated Below -->
3+
4+
5+
## Properties
6+
7+
| Property | Attribute | Description | Type | Default |
8+
| ---------- | ---------- | ------------------------------------ | -------- | ----------- |
9+
| `fragment` | `fragment` | HTML fragment to sanitize and render | `string` | `undefined` |
10+
11+
12+
----------------------------------------------
13+
14+
*Built with [StencilJS](https://stenciljs.com/)*

elements/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"@stencil/store": "^2.2.0",
2929
"@uvdsl/solid-oidc-client-browser": "^0.1.1",
3030
"idb": "^8.0.3",
31+
"isomorphic-dompurify": "^2.26.0",
3132
"pollen-css": "^5.0.2",
3233
"rxjs": "^7.8.2",
3334
"stencil-router-v2": "^0.6.0"

elements/src/components.d.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ export namespace Components {
6060
}
6161
interface PosGettingStarted {
6262
}
63+
interface PosHtmlTool {
64+
/**
65+
* HTML fragment to sanitize and render
66+
*/
67+
"fragment": string;
68+
}
6369
/**
6470
* Tries fetch an image with the solid authentication, and can visualize http errors like 403 or 404 if this fails.
6571
* Falls back to classic <img src="..."> on network errors like CORS.
@@ -526,6 +532,12 @@ declare global {
526532
prototype: HTMLPosGettingStartedElement;
527533
new (): HTMLPosGettingStartedElement;
528534
};
535+
interface HTMLPosHtmlToolElement extends Components.PosHtmlTool, HTMLStencilElement {
536+
}
537+
var HTMLPosHtmlToolElement: {
538+
prototype: HTMLPosHtmlToolElement;
539+
new (): HTMLPosHtmlToolElement;
540+
};
529541
interface HTMLPosImageElementEventMap {
530542
"pod-os:init": any;
531543
"pod-os:resource-loaded": string;
@@ -960,6 +972,7 @@ declare global {
960972
"pos-error-toast": HTMLPosErrorToastElement;
961973
"pos-example-resources": HTMLPosExampleResourcesElement;
962974
"pos-getting-started": HTMLPosGettingStartedElement;
975+
"pos-html-tool": HTMLPosHtmlToolElement;
963976
"pos-image": HTMLPosImageElement;
964977
"pos-internal-router": HTMLPosInternalRouterElement;
965978
"pos-label": HTMLPosLabelElement;
@@ -1071,6 +1084,12 @@ declare namespace LocalJSX {
10711084
interface PosGettingStarted {
10721085
"onPod-os:login"?: (event: PosGettingStartedCustomEvent<void>) => void;
10731086
}
1087+
interface PosHtmlTool {
1088+
/**
1089+
* HTML fragment to sanitize and render
1090+
*/
1091+
"fragment"?: string;
1092+
}
10741093
/**
10751094
* Tries fetch an image with the solid authentication, and can visualize http errors like 403 or 404 if this fails.
10761095
* Falls back to classic <img src="..."> on network errors like CORS.
@@ -1254,6 +1273,7 @@ declare namespace LocalJSX {
12541273
"pos-error-toast": PosErrorToast;
12551274
"pos-example-resources": PosExampleResources;
12561275
"pos-getting-started": PosGettingStarted;
1276+
"pos-html-tool": PosHtmlTool;
12571277
"pos-image": PosImage;
12581278
"pos-internal-router": PosInternalRouter;
12591279
"pos-label": PosLabel;
@@ -1307,6 +1327,7 @@ declare module "@stencil/core" {
13071327
"pos-error-toast": LocalJSX.PosErrorToast & JSXBase.HTMLAttributes<HTMLPosErrorToastElement>;
13081328
"pos-example-resources": LocalJSX.PosExampleResources & JSXBase.HTMLAttributes<HTMLPosExampleResourcesElement>;
13091329
"pos-getting-started": LocalJSX.PosGettingStarted & JSXBase.HTMLAttributes<HTMLPosGettingStartedElement>;
1330+
"pos-html-tool": LocalJSX.PosHtmlTool & JSXBase.HTMLAttributes<HTMLPosHtmlToolElement>;
13101331
/**
13111332
* Tries fetch an image with the solid authentication, and can visualize http errors like 403 or 404 if this fails.
13121333
* Falls back to classic <img src="..."> on network errors like CORS.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { newSpecPage } from '@stencil/core/testing';
2+
import { PosHtmlTool } from './pos-html-tool';
3+
4+
describe('pos-html-tool', () => {
5+
it('inserts sanitized HTML into the page', async () => {
6+
const page = await newSpecPage({
7+
components: [PosHtmlTool],
8+
html: `<pos-html-tool/>`,
9+
});
10+
page.rootInstance.fragment = '<pos-label/>';
11+
await page.waitForChanges();
12+
expect(page.root?.innerHTML).toEqualHtml('<pos-label/>');
13+
});
14+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Component, h, Host, Prop } from '@stencil/core';
2+
import { sanitizeHtmlTool } from './sanitizeHtmlTool';
3+
4+
@Component({
5+
tag: 'pos-html-tool',
6+
shadow: true,
7+
})
8+
export class PosHtmlTool {
9+
/**
10+
* HTML fragment to sanitize and render
11+
*/
12+
@Prop() fragment: string;
13+
14+
render() {
15+
return <Host innerHTML={sanitizeHtmlTool(this.fragment)}></Host>;
16+
}
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { sanitizeHtmlTool } from './sanitizeHtmlTool';
2+
3+
describe('sanitizeHtmlTool', () => {
4+
it('keeps whitelisted elements', () => {
5+
const sanitized = sanitizeHtmlTool('<pos-label/>');
6+
expect(sanitized).toEqual('<pos-label/>');
7+
});
8+
9+
it('removes unknown HTML elements from fragment', () => {
10+
const sanitized = sanitizeHtmlTool('<unknown-element>');
11+
expect(sanitized).toEqual('');
12+
});
13+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import DOMPurify from 'isomorphic-dompurify';
2+
3+
export function sanitizeHtmlTool(htmlToolFragment: string) {
4+
return DOMPurify.sanitize(htmlToolFragment, { ADD_TAGS: ['pos-label'] });
5+
}

elements/src/components/pos-label/pos-label.integration.spec.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { PosApp } from '../pos-app/pos-app';
44
import { PosResource } from '../pos-resource/pos-resource';
55
import { PosLabel } from './pos-label';
66
import { when } from 'jest-when';
7+
import { sanitizeHtmlTool } from '../pos-html-tool/sanitizeHtmlTool';
78

89
describe('pos-label', () => {
910
it('renders label for successfully loaded resource', async () => {
@@ -101,4 +102,9 @@ describe('pos-label', () => {
101102
</pos-label>
102103
`);
103104
});
105+
106+
it('is whitelisted by sanitizeHtmlTool', () => {
107+
const sanitized = sanitizeHtmlTool('<pos-label/>');
108+
expect(sanitized).toEqual('<pos-label/>');
109+
});
104110
});

0 commit comments

Comments
 (0)