@github/include-fragment-element
Advanced tools
Comparing version 6.0.1 to 6.1.0
@@ -0,3 +1,10 @@ | ||
interface CSPTrustedTypesPolicy { | ||
createHTML: (s: string, response: Response) => CSPTrustedHTMLToStringable; | ||
} | ||
interface CSPTrustedHTMLToStringable { | ||
toString: () => string; | ||
} | ||
export default class IncludeFragmentElement extends HTMLElement { | ||
#private; | ||
static setCSPTrustedTypesPolicy(policy: CSPTrustedTypesPolicy | Promise<CSPTrustedTypesPolicy> | null): void; | ||
static get observedAttributes(): string[]; | ||
@@ -26,1 +33,2 @@ get src(): string; | ||
} | ||
export {}; |
@@ -12,38 +12,11 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
}; | ||
var _IncludeFragmentElement_instances, _IncludeFragmentElement_busy, _IncludeFragmentElement_observer, _IncludeFragmentElement_handleData, _IncludeFragmentElement_getData, _IncludeFragmentElement_fetchDataWithEvents; | ||
var _IncludeFragmentElement_instances, _IncludeFragmentElement_busy, _IncludeFragmentElement_observer, _IncludeFragmentElement_handleData, _IncludeFragmentElement_getData, _IncludeFragmentElement_getStringOrErrorData, _IncludeFragmentElement_task, _IncludeFragmentElement_fetchDataWithEvents; | ||
const privateData = new WeakMap(); | ||
function task() { | ||
return new Promise(resolve => setTimeout(resolve, 0)); | ||
} | ||
function isWildcard(accept) { | ||
return accept && !!accept.split(',').find(x => x.match(/^\s*\*\/\*/)); | ||
} | ||
let cspTrustedTypesPolicyPromise = null; | ||
export default class IncludeFragmentElement extends HTMLElement { | ||
constructor() { | ||
super(); | ||
_IncludeFragmentElement_instances.add(this); | ||
_IncludeFragmentElement_busy.set(this, false); | ||
_IncludeFragmentElement_observer.set(this, new IntersectionObserver(entries => { | ||
for (const entry of entries) { | ||
if (entry.isIntersecting) { | ||
const { target } = entry; | ||
__classPrivateFieldGet(this, _IncludeFragmentElement_observer, "f").unobserve(target); | ||
if (!(target instanceof IncludeFragmentElement)) | ||
return; | ||
if (target.loading === 'lazy') { | ||
__classPrivateFieldGet(this, _IncludeFragmentElement_instances, "m", _IncludeFragmentElement_handleData).call(this); | ||
} | ||
} | ||
} | ||
}, { | ||
rootMargin: '0px 0px 256px 0px', | ||
threshold: 0.01 | ||
})); | ||
this.attachShadow({ mode: 'open' }).innerHTML = ` | ||
<style> | ||
:host { | ||
display: block; | ||
} | ||
</style> | ||
<slot></slot>`; | ||
static setCSPTrustedTypesPolicy(policy) { | ||
cspTrustedTypesPolicyPromise = policy === null ? policy : Promise.resolve(policy); | ||
} | ||
@@ -82,3 +55,3 @@ static get observedAttributes() { | ||
get data() { | ||
return __classPrivateFieldGet(this, _IncludeFragmentElement_instances, "m", _IncludeFragmentElement_getData).call(this); | ||
return __classPrivateFieldGet(this, _IncludeFragmentElement_instances, "m", _IncludeFragmentElement_getStringOrErrorData).call(this); | ||
} | ||
@@ -97,2 +70,27 @@ attributeChangedCallback(attribute, oldVal) { | ||
} | ||
constructor() { | ||
super(); | ||
_IncludeFragmentElement_instances.add(this); | ||
_IncludeFragmentElement_busy.set(this, false); | ||
_IncludeFragmentElement_observer.set(this, new IntersectionObserver(entries => { | ||
for (const entry of entries) { | ||
if (entry.isIntersecting) { | ||
const { target } = entry; | ||
__classPrivateFieldGet(this, _IncludeFragmentElement_observer, "f").unobserve(target); | ||
if (!(target instanceof IncludeFragmentElement)) | ||
return; | ||
if (target.loading === 'lazy') { | ||
__classPrivateFieldGet(this, _IncludeFragmentElement_instances, "m", _IncludeFragmentElement_handleData).call(this); | ||
} | ||
} | ||
} | ||
}, { | ||
rootMargin: '0px 0px 256px 0px', | ||
threshold: 0.01 | ||
})); | ||
const shadowRoot = this.attachShadow({ mode: 'open' }); | ||
const style = document.createElement('style'); | ||
style.textContent = `:host {display: block;}`; | ||
shadowRoot.append(style, document.createElement('slot')); | ||
} | ||
connectedCallback() { | ||
@@ -120,3 +118,3 @@ if (this.src && this.loading === 'eager') { | ||
load() { | ||
return __classPrivateFieldGet(this, _IncludeFragmentElement_instances, "m", _IncludeFragmentElement_getData).call(this); | ||
return __classPrivateFieldGet(this, _IncludeFragmentElement_instances, "m", _IncludeFragmentElement_getStringOrErrorData).call(this); | ||
} | ||
@@ -127,10 +125,15 @@ fetch(request) { | ||
} | ||
_IncludeFragmentElement_busy = new WeakMap(), _IncludeFragmentElement_observer = new WeakMap(), _IncludeFragmentElement_instances = new WeakSet(), _IncludeFragmentElement_handleData = function _IncludeFragmentElement_handleData() { | ||
_IncludeFragmentElement_busy = new WeakMap(), _IncludeFragmentElement_observer = new WeakMap(), _IncludeFragmentElement_instances = new WeakSet(), _IncludeFragmentElement_handleData = async function _IncludeFragmentElement_handleData() { | ||
if (__classPrivateFieldGet(this, _IncludeFragmentElement_busy, "f")) | ||
return Promise.resolve(); | ||
return; | ||
__classPrivateFieldSet(this, _IncludeFragmentElement_busy, true, "f"); | ||
__classPrivateFieldGet(this, _IncludeFragmentElement_observer, "f").unobserve(this); | ||
return __classPrivateFieldGet(this, _IncludeFragmentElement_instances, "m", _IncludeFragmentElement_getData).call(this).then((html) => { | ||
try { | ||
const data = await __classPrivateFieldGet(this, _IncludeFragmentElement_instances, "m", _IncludeFragmentElement_getData).call(this); | ||
if (data instanceof Error) { | ||
throw data; | ||
} | ||
const dataTreatedAsString = data; | ||
const template = document.createElement('template'); | ||
template.innerHTML = html; | ||
template.innerHTML = dataTreatedAsString; | ||
const fragment = document.importNode(template.content, true); | ||
@@ -142,12 +145,14 @@ const canceled = !this.dispatchEvent(new CustomEvent('include-fragment-replace', { cancelable: true, detail: { fragment } })); | ||
this.dispatchEvent(new CustomEvent('include-fragment-replaced')); | ||
}, () => { | ||
} | ||
catch (_a) { | ||
this.classList.add('is-error'); | ||
}); | ||
}, _IncludeFragmentElement_getData = function _IncludeFragmentElement_getData() { | ||
} | ||
}, _IncludeFragmentElement_getData = async function _IncludeFragmentElement_getData() { | ||
const src = this.src; | ||
let data = privateData.get(this); | ||
if (data && data.src === src) { | ||
return data.data; | ||
const cachedData = privateData.get(this); | ||
if (cachedData && cachedData.src === src) { | ||
return cachedData.data; | ||
} | ||
else { | ||
let data; | ||
if (src) { | ||
@@ -162,9 +167,17 @@ data = __classPrivateFieldGet(this, _IncludeFragmentElement_instances, "m", _IncludeFragmentElement_fetchDataWithEvents).call(this); | ||
} | ||
}, _IncludeFragmentElement_fetchDataWithEvents = function _IncludeFragmentElement_fetchDataWithEvents() { | ||
return task() | ||
.then(() => { | ||
this.dispatchEvent(new Event('loadstart')); | ||
return this.fetch(this.request()); | ||
}) | ||
.then(response => { | ||
}, _IncludeFragmentElement_getStringOrErrorData = async function _IncludeFragmentElement_getStringOrErrorData() { | ||
const data = await __classPrivateFieldGet(this, _IncludeFragmentElement_instances, "m", _IncludeFragmentElement_getData).call(this); | ||
if (data instanceof Error) { | ||
throw data; | ||
} | ||
return data.toString(); | ||
}, _IncludeFragmentElement_task = async function _IncludeFragmentElement_task(eventsToDispatch) { | ||
await new Promise(resolve => setTimeout(resolve, 0)); | ||
for (const eventType of eventsToDispatch) { | ||
this.dispatchEvent(new Event(eventType)); | ||
} | ||
}, _IncludeFragmentElement_fetchDataWithEvents = async function _IncludeFragmentElement_fetchDataWithEvents() { | ||
try { | ||
await __classPrivateFieldGet(this, _IncludeFragmentElement_instances, "m", _IncludeFragmentElement_task).call(this, ['loadstart']); | ||
const response = await this.fetch(this.request()); | ||
if (response.status !== 200) { | ||
@@ -177,17 +190,15 @@ throw new Error(`Failed to load resource: the server responded with a status of ${response.status}`); | ||
} | ||
return response.text(); | ||
}) | ||
.then(data => { | ||
task().then(() => { | ||
this.dispatchEvent(new Event('load')); | ||
this.dispatchEvent(new Event('loadend')); | ||
}); | ||
const responseText = await response.text(); | ||
let data = responseText; | ||
if (cspTrustedTypesPolicyPromise) { | ||
const cspTrustedTypesPolicy = await cspTrustedTypesPolicyPromise; | ||
data = cspTrustedTypesPolicy.createHTML(responseText, response); | ||
} | ||
__classPrivateFieldGet(this, _IncludeFragmentElement_instances, "m", _IncludeFragmentElement_task).call(this, ['load', 'loadend']); | ||
return data; | ||
}, error => { | ||
task().then(() => { | ||
this.dispatchEvent(new Event('error')); | ||
this.dispatchEvent(new Event('loadend')); | ||
}); | ||
} | ||
catch (error) { | ||
__classPrivateFieldGet(this, _IncludeFragmentElement_instances, "m", _IncludeFragmentElement_task).call(this, ['error', 'loadend']); | ||
throw error; | ||
}); | ||
} | ||
}; | ||
@@ -194,0 +205,0 @@ if (!window.customElements.get('include-fragment')) { |
{ | ||
"name": "@github/include-fragment-element", | ||
"version": "6.0.1", | ||
"version": "6.1.0", | ||
"main": "dist/index.js", | ||
@@ -16,6 +16,7 @@ "module": "dist/index.js", | ||
"lint": "eslint . --ext .js,.ts && tsc --noEmit", | ||
"format": "eslint --fix . --ext .js,.ts", | ||
"prebuild": "npm run clean && npm run lint && mkdir dist", | ||
"build": "tsc", | ||
"pretest": "npm run build", | ||
"test": "karma start ./test/karma.config.cjs", | ||
"test": "web-test-runner", | ||
"prepublishOnly": "npm run build", | ||
@@ -27,12 +28,9 @@ "postpublish": "npm publish --ignore-scripts --@github:registry='https://npm.pkg.github.com'" | ||
"@github/prettier-config": "0.0.4", | ||
"chai": "^4.3.4", | ||
"chromium": "^3.0.3", | ||
"@open-wc/testing": "^3.1.7", | ||
"@web/dev-server-esbuild": "^0.3.3", | ||
"@web/test-runner": "^0.15.0", | ||
"@web/test-runner-playwright": "^0.9.0", | ||
"esbuild": "^0.15.15", | ||
"eslint": "^8.12.0", | ||
"eslint-plugin-github": "^4.3.6", | ||
"karma": "^6.3.4", | ||
"karma-chai": "^0.1.0", | ||
"karma-chrome-launcher": "^3.1.0", | ||
"karma-mocha": "^2.0.1", | ||
"karma-mocha-reporter": "^2.2.5", | ||
"mocha": "^9.0.3", | ||
"typescript": "^4.3.5" | ||
@@ -39,0 +37,0 @@ }, |
@@ -103,3 +103,48 @@ # <include-fragment> element | ||
### CSP Trusted Types | ||
You can call `setCSPTrustedTypesPolicy(policy: TrustedTypePolicy | Promise<TrustedTypePolicy> | null)` from JavaScript to set a [CSP trusted types policy](https://web.dev/trusted-types/), which can perform (synchronous) filtering or rejection of the `fetch` response before it is inserted into the page: | ||
```ts | ||
import IncludeFragmentElement from "include-fragment-element"; | ||
import DOMPurify from "dompurify"; // Using https://github.com/cure53/DOMPurify | ||
// This policy removes all HTML markup except links. | ||
const policy = trustedTypes.createPolicy("links-only", { | ||
createHTML: (htmlText: string) => { | ||
return DOMPurify.sanitize(htmlText, { | ||
ALLOWED_TAGS: ["a"], | ||
ALLOWED_ATTR: ["href"], | ||
RETURN_TRUSTED_TYPE: true, | ||
}); | ||
}, | ||
}); | ||
IncludeFragmentElement.setCSPTrustedTypesPolicy(policy); | ||
``` | ||
The policy has access to the `fetch` response object. Due to platform constraints, only synchronous information from the response (in addition to the HTML text body) can be used in the policy: | ||
```ts | ||
import IncludeFragmentElement from "include-fragment-element"; | ||
const policy = trustedTypes.createPolicy("require-server-header", { | ||
createHTML: (htmlText: string, response: Response) => { | ||
if (response.headers.get("X-Server-Sanitized") !== "sanitized=true") { | ||
// Note: this will reject the contents, but the error may be caught before it shows in the JS console. | ||
throw new Error("Rejecting HTML that was not marked by the server as sanitized."); | ||
} | ||
return htmlText; | ||
}, | ||
}); | ||
IncludeFragmentElement.setCSPTrustedTypesPolicy(policy); | ||
``` | ||
Note that: | ||
- Only a single policy can be set, shared by all `IncludeFragmentElement` fetches. | ||
- You should call `setCSPTrustedTypesPolicy()` ahead of any other load of `include-fragment-element` in your code. | ||
- If your policy itself requires asynchronous work to construct, you can also pass a `Promise<TrustedTypePolicy>`. | ||
- Pass `null` to remove the policy. | ||
- Not all browsers [support the trusted types API in JavaScript](https://caniuse.com/mdn-api_trustedtypes). You may want to use the [recommended tinyfill](https://github.com/w3c/trusted-types#tinyfill) to construct a policy without causing issues in other browsers. | ||
## Relation to Server Side Includes | ||
@@ -106,0 +151,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
20758
9
234
185
1