refactor: use uhtml, cleanup express logic
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
"kysely": "^0.27.3",
|
||||
"pg": "^8.12.0",
|
||||
"pino": "^9.2.0",
|
||||
"uhtml": "^4.7.1",
|
||||
"uint8arrays": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import {type Hole, html} from 'uhtml'
|
||||
|
||||
export function linkRedirectContents(link: string): Hole {
|
||||
return html`
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0; URL='${link}'" />
|
||||
<meta
|
||||
http-equiv="Cache-Control"
|
||||
content="no-store, no-cache, must-revalidate, max-age=0" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
</html>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import {type Hole, html} from 'uhtml'
|
||||
|
||||
export function linkWarningContents(opts: {
|
||||
type: 'warn' | 'block'
|
||||
link: string
|
||||
}): Hole {
|
||||
return html`
|
||||
<div class="warning-icon">⚠️</div>
|
||||
<h1>
|
||||
${opts.type === 'warn' ? 'Potentially Dangerous Link' : 'Blocked Link'}
|
||||
</h1>
|
||||
<p class="warning-text">
|
||||
${opts.type === 'warn'
|
||||
? 'This link may be malicious. You should proceed at your own risk.'
|
||||
: 'This link has been identified as malicious and has blocked for your safety.'}
|
||||
</p>
|
||||
<div class="blocked-site">
|
||||
<p class="site-url">${opts.link}</p>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
${opts.type === 'warn'
|
||||
? html`<a class="button secondary" href="${opts.link}"
|
||||
>Continue Anyway</a
|
||||
>`
|
||||
: null}
|
||||
<a class="button primary" href="https://bsky.app">Return to Bluesky</a>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import {type Hole, html} from 'uhtml'
|
||||
|
||||
export function linkWarningLayout(
|
||||
title: string,
|
||||
containerContents: Hole,
|
||||
): Hole {
|
||||
return html`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
http-equiv="Cache-Control"
|
||||
content="no-store, no-cache, must-revalidate, max-age=0" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>${title}</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
Arial, sans-serif;
|
||||
background-color: #ffffff;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
font-size: 15px;
|
||||
color: #536471;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 24px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.blocked-site {
|
||||
background-color: #f7f9fa;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 24px;
|
||||
text-align: left;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.site-name {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #000000;
|
||||
margin-bottom: 4px;
|
||||
word-break: break-word;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.site-url {
|
||||
font-size: 14px;
|
||||
color: #536471;
|
||||
word-break: break-all;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
border: none;
|
||||
border-radius: 24px;
|
||||
padding: 12px 32px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
max-width: 280px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.primary {
|
||||
background-color: #1d9bf0;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background-color: #1a8cd8;
|
||||
}
|
||||
|
||||
.back-button:active {
|
||||
background-color: #1681c4;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.warning-text {
|
||||
padding: 0 10px;
|
||||
}
|
||||
.blocked-site {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">${containerContents}</div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
}
|
||||
+53
-232
@@ -2,10 +2,13 @@ import assert from 'node:assert'
|
||||
|
||||
import {ToolsOzoneSafelinkDefs} from '@atproto/api'
|
||||
import {DAY, SECOND} from '@atproto/common'
|
||||
import escapeHTML from 'escape-html'
|
||||
import {type Express} from 'express'
|
||||
import {type Hole} from 'uhtml'
|
||||
|
||||
import {type AppContext} from '../context.js'
|
||||
import {linkRedirectContents} from '../html/linkRedirectContents.js'
|
||||
import {linkWarningContents} from '../html/linkWarningContents.js'
|
||||
import {linkWarningLayout} from '../html/linkWarningLayout.js'
|
||||
import {redirectLogger} from '../logger.js'
|
||||
import {handler} from './util.js'
|
||||
|
||||
@@ -41,247 +44,65 @@ export default function (ctx: AppContext, app: Express) {
|
||||
return res.status(302).end()
|
||||
}
|
||||
|
||||
// Default to a max age header
|
||||
res.setHeader('Cache-Control', `max-age=${(7 * DAY) / SECOND}`)
|
||||
res.type('html')
|
||||
res.status(200)
|
||||
res.type('html')
|
||||
|
||||
let hole: Hole | undefined
|
||||
|
||||
if (ctx.cfg.service.safelinkEnabled) {
|
||||
const rulePresent: ToolsOzoneSafelinkDefs.Event | undefined =
|
||||
ctx.cfg.eventCache.smartGet(link)
|
||||
|
||||
// begin link safety checks
|
||||
if (
|
||||
rulePresent &&
|
||||
rulePresent.eventType === ToolsOzoneSafelinkDefs.REMOVERULE
|
||||
) {
|
||||
redirectLogger.info(
|
||||
`No rule or Remove rule matched for ${rulePresent.url}`,
|
||||
)
|
||||
const escaped = escapeHTML(url.href)
|
||||
const html = safe_redirect(escaped)
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html',
|
||||
'Content-Length': Buffer.byteLength(html),
|
||||
})
|
||||
res.end(html) // Critical - must call end()
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
rulePresent &&
|
||||
rulePresent.action === ToolsOzoneSafelinkDefs.WHITELIST
|
||||
) {
|
||||
redirectLogger.info(`Whitelist rule matched for ${rulePresent.url}`)
|
||||
const escaped = escapeHTML(url.href)
|
||||
const html = safe_redirect(escaped)
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html',
|
||||
'Content-Length': Buffer.byteLength(html),
|
||||
})
|
||||
res.end(html) // Critical - must call end()
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
rulePresent &&
|
||||
rulePresent.action === ToolsOzoneSafelinkDefs.BLOCK
|
||||
) {
|
||||
redirectLogger.info(`Block rule matched for ${rulePresent.url}`)
|
||||
res.setHeader('Cache-Control', 'no-store')
|
||||
const html = warnRedirect(
|
||||
'Blocked Link',
|
||||
'This link has been identified as malicious, it has been blocked to protect your account and data',
|
||||
'Go Back To BlueSky',
|
||||
'DANGER',
|
||||
escapeHTML(url.toString()),
|
||||
`https://${ctx.cfg.service.appHostname}`,
|
||||
)
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html',
|
||||
'Content-Length': Buffer.byteLength(html),
|
||||
})
|
||||
res.end(html) // Critical - must call end()
|
||||
return
|
||||
}
|
||||
|
||||
if (rulePresent && rulePresent.action === ToolsOzoneSafelinkDefs.WARN) {
|
||||
redirectLogger.info(`Warn rule matched for ${rulePresent.url}`)
|
||||
res.setHeader('Cache-Control', 'no-store')
|
||||
const html = warnRedirect(
|
||||
'Warning: Malicious Link',
|
||||
'This link has been identified as malicious, continue at your own risk',
|
||||
'continue at your own risk',
|
||||
'DANGER',
|
||||
escapeHTML(url.toString()),
|
||||
`https://${ctx.cfg.service.appHostname}`,
|
||||
)
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html',
|
||||
'Content-Length': Buffer.byteLength(html),
|
||||
})
|
||||
res.end(html) // Critical - must call end()
|
||||
return
|
||||
if (rulePresent) {
|
||||
switch (rulePresent.action) {
|
||||
case ToolsOzoneSafelinkDefs.BLOCK:
|
||||
hole = linkWarningLayout(
|
||||
'Blocked Link Warning',
|
||||
linkWarningContents({
|
||||
type: 'block',
|
||||
link: url.href,
|
||||
}),
|
||||
)
|
||||
res.setHeader('Cache-Control', 'no-store')
|
||||
redirectLogger.info(`Block rule matched for ${rulePresent.url}`)
|
||||
break
|
||||
case ToolsOzoneSafelinkDefs.WARN:
|
||||
hole = linkWarningLayout(
|
||||
'Malicious Link Warning',
|
||||
linkWarningContents({
|
||||
type: 'warn',
|
||||
link: url.href,
|
||||
}),
|
||||
)
|
||||
res.setHeader('Cache-Control', 'no-store')
|
||||
redirectLogger.info(`Warn rule matched for ${rulePresent.url}`)
|
||||
break
|
||||
case ToolsOzoneSafelinkDefs.WHITELIST:
|
||||
redirectLogger.info(
|
||||
`Whitelist rule matched for ${rulePresent.url}`,
|
||||
)
|
||||
break
|
||||
case ToolsOzoneSafelinkDefs.REMOVERULE:
|
||||
redirectLogger.info(`Remove rule matched for ${rulePresent.url}`)
|
||||
break
|
||||
default:
|
||||
redirectLogger.warn(
|
||||
`${rulePresent.action} rule (an unknown rule) matched for ${rulePresent.url}`,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
redirectLogger.info(`No rule present for ${rulePresent.url}`)
|
||||
}
|
||||
}
|
||||
|
||||
const escaped = escapeHTML(url.href)
|
||||
const html = safe_redirect(escaped)
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html',
|
||||
'Content-Length': Buffer.byteLength(html),
|
||||
})
|
||||
res.end(html) // Critical - must call end()
|
||||
return
|
||||
// If there is no hole defined yet, we will create a redirect hole
|
||||
if (!hole) {
|
||||
hole = linkRedirectContents(url.href)
|
||||
}
|
||||
|
||||
return res.end(String(hole))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
const safe_redirect = (escaped: string) =>
|
||||
`<html><head>
|
||||
<meta http-equiv="refresh" content="0; URL='${escaped}'" />
|
||||
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate, max-age=0" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
<style>:root { color-scheme: light dark; }</style>
|
||||
</head></html>`
|
||||
|
||||
const warnRedirect = (
|
||||
mainText: string,
|
||||
warningText: string,
|
||||
buttonText: string,
|
||||
reason: string,
|
||||
siteUrl: string,
|
||||
returnUrl = 'https://bsky.app',
|
||||
) => {
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate, max-age=0" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>${mainText}</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
|
||||
background-color: #ffffff;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
font-size: 15px;
|
||||
color: #536471;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 24px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.blocked-site {
|
||||
background-color: #f7f9fa;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 24px;
|
||||
text-align: left;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.site-name {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #000000;
|
||||
margin-bottom: 4px;
|
||||
word-break: break-word;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.site-url {
|
||||
font-size: 14px;
|
||||
color: #536471;
|
||||
word-break: break-all;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
background-color: #1d9bf0;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 24px;
|
||||
padding: 12px 32px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
max-width: 280px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background-color: #1a8cd8;
|
||||
}
|
||||
|
||||
.back-button:active {
|
||||
background-color: #1681c4;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.warning-text {
|
||||
padding: 0 10px;
|
||||
}
|
||||
.blocked-site {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="warning-icon">⚠️</div>
|
||||
<h1>${mainText}</h1>
|
||||
<p class="warning-text">${escapeHTML(warningText)}</p>
|
||||
<div class="blocked-site">
|
||||
<span class="site-name">${escapeHTML(reason)}</span>
|
||||
<span class="site-url">${escapeHTML(siteUrl)}</span>
|
||||
</div>
|
||||
<button class="back-button" id="redirect-button">${escapeHTML(
|
||||
buttonText,
|
||||
)}</button>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById('redirect-button').addEventListener('click', function() {
|
||||
window.location.href = ${JSON.stringify(returnUrl)};
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
|
||||
@@ -62,6 +62,11 @@
|
||||
cborg "^1.6.0"
|
||||
multiformats "^9.5.4"
|
||||
|
||||
"@preact/signals-core@^1.8.0":
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@preact/signals-core/-/signals-core-1.10.0.tgz#765eb7045998b98c437e1ad1a660e5ff96a40136"
|
||||
integrity sha512-qlKeXlfqtlC+sjxCPHt6Sk0/dXBrKZVcPlianqjNc/vW263YBFiP5mRrgKpHoO0q222Thm1TdYQWfCKpbbgvwA==
|
||||
|
||||
"@types/cors@^2.8.17":
|
||||
version "2.8.17"
|
||||
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.17.tgz#5d718a5e494a8166f569d986794e49c48b216b2b"
|
||||
@@ -90,6 +95,18 @@
|
||||
pg-protocol "*"
|
||||
pg-types "^4.0.1"
|
||||
|
||||
"@webreflection/signal@^2.1.2":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@webreflection/signal/-/signal-2.1.2.tgz#8adcf99b33f7e8ddfade4742b171c1743dc930d2"
|
||||
integrity sha512-0dW0fstQQkIt588JwhDiPS4xgeeQcQnBHn6MVInrBzmFlnLtzoSJL9G7JqdAlZVVi19tfb8R1QisZIT31cgiug==
|
||||
|
||||
"@webreflection/uparser@^0.4.0":
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@webreflection/uparser/-/uparser-0.4.0.tgz#49c105455e9482f9a7398f96eb398e421e440f26"
|
||||
integrity sha512-kAFWUEw5eool295y01VDr+DOsyog6lURX9l288JCJAD2gxc0tFk34dYaAi6O3BbJyfSoncVEV+nw87bsssdppQ==
|
||||
dependencies:
|
||||
domconstants "^1.1.6"
|
||||
|
||||
abort-controller@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
|
||||
@@ -223,6 +240,11 @@ cors@^2.8.5:
|
||||
object-assign "^4"
|
||||
vary "^1"
|
||||
|
||||
custom-function@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/custom-function/-/custom-function-2.0.0.tgz#e421ce1712fa5f8e240a518080a4c82ae97e8606"
|
||||
integrity sha512-2OPHkZzq3mK1nWpJqWWkGD6Z+0AajNeIxmXl+MRVL8Vysjjf5tf9B5mo713/X2khEwBn/3BKQ7NphpP1vpVKug==
|
||||
|
||||
debug@2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
@@ -259,6 +281,41 @@ detect-libc@^2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700"
|
||||
integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==
|
||||
|
||||
dom-serializer@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
|
||||
integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
|
||||
dependencies:
|
||||
domelementtype "^2.3.0"
|
||||
domhandler "^5.0.2"
|
||||
entities "^4.2.0"
|
||||
|
||||
domconstants@^1.1.6:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/domconstants/-/domconstants-1.1.6.tgz#c6dd4e181a3ddf641a3d2f55ec79569a409d82d4"
|
||||
integrity sha512-CuaDrThJ4VM+LyZ4ax8n52k0KbLJZtffyGkuj1WhpTRRcSfcy/9DfOBa68jenhX96oNUTunblSJEUNC4baFdmQ==
|
||||
|
||||
domelementtype@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
|
||||
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
|
||||
|
||||
domhandler@^5.0.2, domhandler@^5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
|
||||
integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
|
||||
dependencies:
|
||||
domelementtype "^2.3.0"
|
||||
|
||||
domutils@^3.1.0:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78"
|
||||
integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==
|
||||
dependencies:
|
||||
dom-serializer "^2.0.0"
|
||||
domelementtype "^2.3.0"
|
||||
domhandler "^5.0.3"
|
||||
|
||||
ee-first@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
@@ -269,6 +326,11 @@ encodeurl@~1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
|
||||
|
||||
entities@^4.2.0, entities@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
|
||||
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
|
||||
|
||||
es-define-property@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845"
|
||||
@@ -378,6 +440,11 @@ function-bind@^1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
|
||||
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
|
||||
|
||||
gc-hook@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/gc-hook/-/gc-hook-0.4.1.tgz#61e0ef4c5c2a13ae6f938cc2b0b9a71a94a12e68"
|
||||
integrity sha512-uiF+uUftDVLr+VRdudsdsT3/LQYnv2ntwhRH964O7xXDI57Smrek5olv75Wb8Nnz6U+7iVTRXsBlxKcsaDTJTQ==
|
||||
|
||||
get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
|
||||
@@ -425,6 +492,21 @@ hasown@^2.0.0:
|
||||
dependencies:
|
||||
function-bind "^1.1.2"
|
||||
|
||||
html-escaper@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-3.0.3.tgz#4d336674652beb1dcbc29ef6b6ba7f6be6fdfed6"
|
||||
integrity sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==
|
||||
|
||||
htmlparser2@^9.1.0:
|
||||
version "9.1.0"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-9.1.0.tgz#cdb498d8a75a51f739b61d3f718136c369bc8c23"
|
||||
integrity sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==
|
||||
dependencies:
|
||||
domelementtype "^2.3.0"
|
||||
domhandler "^5.0.3"
|
||||
domutils "^3.1.0"
|
||||
entities "^4.5.0"
|
||||
|
||||
http-errors@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
|
||||
@@ -987,6 +1069,27 @@ typescript@^5.4.5:
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611"
|
||||
integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==
|
||||
|
||||
udomdiff@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/udomdiff/-/udomdiff-1.1.2.tgz#2979769943afddfb1e6f40e8bda41e431bdcd813"
|
||||
integrity sha512-v+Z8Jal+GtmKGtJ34GIQlCJAxrDt9kbjpNsNvYoAXFyr4gNfWlD4uJJuoNNu/0UTVaKvQwHaSU095YDl71lKPw==
|
||||
|
||||
uhtml@^4.7.1:
|
||||
version "4.7.1"
|
||||
resolved "https://registry.yarnpkg.com/uhtml/-/uhtml-4.7.1.tgz#5a0e9c08aefb08c8e9d59e208d7d52f1646561e3"
|
||||
integrity sha512-2Nv8m2WTVBAmep42aYDnMDTRf87yHRWFSif9uEqkCB1fgX85q7rxTXkP6PNINCNUs9/KmQJ+RBgSH1BNZmxEtg==
|
||||
dependencies:
|
||||
"@webreflection/uparser" "^0.4.0"
|
||||
custom-function "^2.0.0"
|
||||
domconstants "^1.1.6"
|
||||
gc-hook "^0.4.1"
|
||||
html-escaper "^3.0.3"
|
||||
htmlparser2 "^9.1.0"
|
||||
udomdiff "^1.1.2"
|
||||
optionalDependencies:
|
||||
"@preact/signals-core" "^1.8.0"
|
||||
"@webreflection/signal" "^2.1.2"
|
||||
|
||||
uint8arrays@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.0.0.tgz#260869efb8422418b6f04e3fac73a3908175c63b"
|
||||
|
||||
Reference in New Issue
Block a user