From 5809fdde511a0dc9c05124e0bf11710aea6fd9d9 Mon Sep 17 00:00:00 2001 From: Hailey Date: Wed, 27 Aug 2025 10:08:24 -0700 Subject: [PATCH] rework --- bskylink/package.json | 1 - bskylink/src/bin.ts | 6 +- bskylink/src/cache/cache.ts | 113 -------------- bskylink/src/cache/rule.ts | 10 +- bskylink/src/cache/safelinkClient.ts | 177 ++++++++++++++++------ bskylink/src/config.ts | 7 - bskylink/src/context.ts | 7 +- bskylink/src/db/schema.ts | 29 +++- bskylink/src/html/linkRedirectContents.ts | 5 +- bskylink/src/html/linkWarningContents.ts | 44 +++--- bskylink/src/html/linkWarningLayout.ts | 21 +-- bskylink/src/routes/redirect.ts | 22 ++- bskylink/yarn.lock | 103 ------------- 13 files changed, 205 insertions(+), 340 deletions(-) delete mode 100644 bskylink/src/cache/cache.ts diff --git a/bskylink/package.json b/bskylink/package.json index 3df3361391..ab7dc03255 100644 --- a/bskylink/package.json +++ b/bskylink/package.json @@ -20,7 +20,6 @@ "lru-cache": "^11.1.0", "pg": "^8.12.0", "pino": "^9.2.0", - "uhtml": "^4.7.1", "uint8arrays": "^5.1.0" }, "devDependencies": { diff --git a/bskylink/src/bin.ts b/bskylink/src/bin.ts index 8a4dfe03ed..8b930f8296 100644 --- a/bskylink/src/bin.ts +++ b/bskylink/src/bin.ts @@ -13,10 +13,12 @@ async function main() { const link = await LinkService.create(cfg) - if (cfg.service.safelinkEnabled) { - cfg.eventCache.adaptiveFetchAndUpdate() + if (link.ctx.cfg.service.safelinkEnabled) { + link.ctx.safelinkClient.runFetchEvents() } + console.log('here') + await link.start() httpLogger.info('link service is running') process.on('SIGTERM', async () => { diff --git a/bskylink/src/cache/cache.ts b/bskylink/src/cache/cache.ts deleted file mode 100644 index 521b4c5730..0000000000 --- a/bskylink/src/cache/cache.ts +++ /dev/null @@ -1,113 +0,0 @@ -import {ToolsOzoneSafelinkDefs} from '@atproto/api' - -import {type ServiceConfig} from '../config.js' -import {redirectLogger} from '../logger.js' -import {OzoneAgent} from './safelinkClient.js' - -export class EventCache { - private rules = new Map() - private cfg: ServiceConfig - private pollInterval = 1 * 1000 // start at 1 second - - constructor(cfg: ServiceConfig) { - this.cfg = cfg - } - - async getConfig(): Promise { - return this.cfg - } - - // Adaptive polling: slow down if no new events, speed up if updates found - async adaptiveFetchAndUpdate() { - const prevCursor = cacheCursor - const eventConfig = await this.getConfig() - if (eventConfig === undefined) { - redirectLogger.info( - `[adaptiveFetchAndUpdate] No Configuration found, skipping fetch.`, - ) - } else { - await this.fetchAndUpdateEvents(eventConfig) - } - if (cacheCursor === prevCursor) { - this.pollInterval = Math.min(this.pollInterval * 2, 10 * 60 * 1000) - redirectLogger.info( - `[adaptiveFetchAndUpdate] No new events, backing off. Next poll in ${ - this.pollInterval / 1000 - }s`, - ) - } else { - this.pollInterval = 5 * 1000 - redirectLogger.info( - `[adaptiveFetchAndUpdate] New events found, resetting poll interval to ${ - this.pollInterval / 1000 - }s`, - ) - } - setTimeout(() => this.adaptiveFetchAndUpdate(), this.pollInterval) - } - - // Fetch and update events from the server - async fetchAndUpdateEvents(cfg: ServiceConfig) { - if (!cfg || !cfg.ozoneUrl || !cfg.ozoneAgentHandle || !cfg.ozoneAgentPass) { - console.error( - '[eventCache:fetchAndUpdateEvents] No active config, skipping actions', - ) - return - } - redirectLogger.info( - `[eventCache] Fetching events with cursor: ${cacheCursor}`, - ) - const ozoneAgent = new OzoneAgent(cfg) - const ozoneSession = await ozoneAgent.getSession?.() - if (!ozoneSession) { - console.error( - '[eventCache:fetchAndUpdateEvents] No active session found.', - ) - return - } - const ozoneDid = ozoneSession.did - if (!ozoneDid || ozoneDid === 'did:plc:invalid') { - console.error( - '[eventCache:fetchAndUpdateEvents] Invalid or missing session DID.', - ) - return - } - ozoneAgent.agent.setHeader?.('atproto-proxy', `${ozoneDid}#atproto_labeler`) - - const res = await ozoneAgent.agent.tools?.ozone?.safelink?.queryEvents?.({ - cursor: cacheCursor, - limit: 100, - }) - - if (res?.data.cursor === cacheCursor) { - redirectLogger.info( - '[eventCache:fetchAndUpdateEvents] No new events to update.', - ) - return - } - - redirectLogger.info( - `[eventCache:fetchAndUpdateEvents] Received response:`, - { - ...res, - data: { - ...res?.data, - rules: Array.isArray(res?.data?.events) - ? res.data.events.map(event => JSON.stringify(event)) - : res?.data?.events, - }, - }, - ) - - for (const event of res.data.events) { - this.smartUpdate(event) - } - - cacheCursor = res.data?.cursor - - redirectLogger.info( - '[eventCache:fetchAndUpdateEvents] Current cache contents:', - ) - redirectLogger.info(this.list()) - } -} diff --git a/bskylink/src/cache/rule.ts b/bskylink/src/cache/rule.ts index 012afa6995..09c4821f66 100644 --- a/bskylink/src/cache/rule.ts +++ b/bskylink/src/cache/rule.ts @@ -1,12 +1,12 @@ -import {SafelinkRule} from '../db/schema' +import {type SafelinkRule} from '../db/schema' export const exampleRule: SafelinkRule = { id: 1, - eventType: '#addRule', + eventType: 'addRule', url: 'https://malicious.example.com/phishing', - pattern: '#domain', - action: '#block', - reason: '#phishing', + pattern: 'domain', + action: 'block', + reason: 'phishing', createdBy: 'did:plc:adminozonetools', createdAt: '2024-06-01T12:00:00Z', comment: 'Known phishing domain detected by automated scan.', diff --git a/bskylink/src/cache/safelinkClient.ts b/bskylink/src/cache/safelinkClient.ts index c9c87d527c..d3d8970c8e 100644 --- a/bskylink/src/cache/safelinkClient.ts +++ b/bskylink/src/cache/safelinkClient.ts @@ -1,10 +1,14 @@ -import {Agent, AtpAgent, CredentialSession} from '@atproto/api' +import { + AtpAgent, + CredentialSession, + type ToolsOzoneSafelinkQueryEvents, +} from '@atproto/api' import {LRUCache} from 'lru-cache' -import {type ServiceConfig} from '../config' -import {SafelinkRule, RulePatternType} from '../db/schema' -import Database from '../db' -import {redirectLogger} from '../logger' +import {type ServiceConfig} from '../config.js' +import type Database from '../db/index.js' +import {type RulePatternType, type SafelinkRule} from '../db/schema.js' +import {redirectLogger} from '../logger.js' export class SafelinkClient { private domainCache: LRUCache @@ -12,14 +16,11 @@ export class SafelinkClient { private db: Database - constructor({ - db, - }: { - identifier: string - password: string - pdsHost: string - db: Database - }) { + private ozoneAgent: OzoneAgent + + private cursor: string + + constructor({cfg, db}: {cfg: ServiceConfig; db: Database}) { this.domainCache = new LRUCache({ max: 10000, }) @@ -29,6 +30,14 @@ export class SafelinkClient { }) this.db = db + + this.ozoneAgent = new OzoneAgent( + cfg.ozoneUrl!, + cfg.ozoneAgentHandle!, + cfg.ozoneAgentPass!, + ) + + this.cursor = '' } public async tryFindRule(link: string): Promise { @@ -50,7 +59,7 @@ export class SafelinkClient { } try { - const maybeUrlRule = await this.getRule(u.href, '#url') + const maybeUrlRule = await this.getRule(this.db, u.href, 'url') this.urlCache.set(u.href, maybeUrlRule) return maybeUrlRule } catch (e) { @@ -58,7 +67,7 @@ export class SafelinkClient { } try { - const maybeDomainRule = await this.getRule(u.href, '#domain') + const maybeDomainRule = await this.getRule(this.db, u.href, 'domain') this.domainCache.set(d.href, maybeDomainRule) return maybeDomainRule } catch (e) { @@ -68,16 +77,21 @@ export class SafelinkClient { return 'ok' } - private getRule(url: string, pattern: RulePatternType) { - return this.db.db + private async getRule( + db: Database, + url: string, + pattern: RulePatternType, + ): Promise { + // @ts-ignore + return db.db .selectFrom('safelink_rule') .where('url', '=', url) .where('pattern', '=', pattern) .executeTakeFirstOrThrow() } - private addRule(rule: SafelinkRule) { - this.db.db + private async addRule(db: Database, rule: SafelinkRule) { + db.db .insertInto('safelink_rule') .values(rule) .execute() @@ -88,17 +102,17 @@ export class SafelinkClient { ) }) - if (rule.pattern === '#domain') { + if (rule.pattern === 'domain') { this.domainCache.set(rule.url, rule) } else { this.urlCache.set(rule.url, rule) } } - private async removeRule(rule: SafelinkRule) { - await this.db.db + private async removeRule(db: Database, rule: SafelinkRule) { + await db.db .deleteFrom('safelink_rule') - .where('pattern', '=', '#domain') + .where('pattern', '=', 'domain') .where('url', '=', rule.url) .execute() .catch(err => { @@ -108,28 +122,103 @@ export class SafelinkClient { ) }) - if (rule.pattern === '#domain') { + if (rule.pattern === 'domain') { this.domainCache.delete(rule.url) } else { this.urlCache.delete(rule.url) } } - public run() { - // poll and add/remove rules as needed + public async runFetchEvents() { + let agent: AtpAgent + try { + agent = await this.ozoneAgent.getAgent() + } catch (err) { + redirectLogger.error({error: err}, 'error getting Ozone agent') + setTimeout(() => this.runFetchEvents(), 10_000) + return + } + + let res: ToolsOzoneSafelinkQueryEvents.Response + try { + const cursor = await this.getCursor() + res = await agent.tools.ozone.safelink.queryEvents({ + cursor, + limit: 100, + }) + } catch (err) { + redirectLogger.error( + {error: err}, + 'error fetching safelink events from Ozone', + ) + setTimeout(() => this.runFetchEvents(), 10_000) + return + } + + if (res.data.cursor === this.cursor) { + setTimeout(() => this.runFetchEvents(), 10_000) + } else { + await this.db.transaction(async db => { + for (const rule of res.data.events) { + if (rule.eventType === 'removeRule') { + await this.removeRule(db, rule) + } else { + await this.addRule(db, rule) + } + } + }) + if (res.data.cursor) { + await this.setCursor(res.data.cursor) + } + setTimeout(() => this.runFetchEvents(), 1_000) + } + } + + private async getCursor() { + if (this.cursor === '') { + // TODO: catch err + const res = await this.db.db + .selectFrom('safelink_cursor') + .orderBy('createdAt desc') + .limit(1) + .executeTakeFirst() + + if (!res) { + return '' + } + + this.cursor = res.cursor + } + return this.cursor + } + + private async setCursor(cursor: string) { + try { + await this.db.db + .insertInto('safelink_cursor') + .values({ + cursor, + createdAt: new Date(), + }) + .execute() + } catch (err) { + redirectLogger.error({error: err}, 'failed to update safelink cursor') + } } } export class OzoneAgent { - public session: CredentialSession - public agent: AtpAgent - private cfg: ServiceConfig + private identifier: string + private password: string - constructor(cfg: ServiceConfig) { - this.cfg = cfg - this.session = new CredentialSession( - new URL(cfg.ozoneUrl || 'http://localhost:2583'), - ) + private session: CredentialSession + private agent: AtpAgent + + constructor(pdsHost: string, identifier: string, password: string) { + this.identifier = identifier + this.password = password + + this.session = new CredentialSession(new URL(pdsHost)) this.agent = new AtpAgent(this.session) } @@ -141,25 +230,19 @@ export class OzoneAgent { } public async getAgent(): Promise { - if (!this.cfg.ozoneAgentHandle && !this.cfg.ozoneAgentPass) { + if (!this.identifier && !this.password) { throw new Error( 'OZONE_AGENT_HANDLE and OZONE_AGENT_PASS environment variables must be set', ) } - const identifier = this.cfg.ozoneAgentHandle || 'did:plc:invalid' - const password = this.cfg.ozoneAgentPass || 'invalid' - if (!this.session.hasSession) { - await this.session.login({identifier, password}) - } - - try { - await this.agent.com.atproto.server.getSession() - } catch (err) { - if ((err as any).status === 401) { - await this.session.login({identifier, password}) - } + redirectLogger.info('creating Ozone session') + await this.session.login({ + identifier: this.identifier, + password: this.password, + }) + redirectLogger.info('ozone session created successfully') } return this.agent diff --git a/bskylink/src/config.ts b/bskylink/src/config.ts index 7eb4e8acbc..c73015ecb3 100644 --- a/bskylink/src/config.ts +++ b/bskylink/src/config.ts @@ -1,11 +1,7 @@ import {envBool, envInt, envList, envStr} from '@atproto/common' -// import { type EventCache, eventCache } from '../cache/cache.js' -import {EventCache} from './cache/cache.js' - export type Config = { service: ServiceConfig - eventCache: EventCache db: DbConfig } @@ -96,11 +92,8 @@ export const envToCfg = (env: Environment): Config => { }, } - const eventCache = new EventCache(serviceCfg) - return { service: serviceCfg, - eventCache: eventCache, db: dbCfg, } } diff --git a/bskylink/src/context.ts b/bskylink/src/context.ts index f7243f0b20..1520513ceb 100644 --- a/bskylink/src/context.ts +++ b/bskylink/src/context.ts @@ -1,5 +1,5 @@ import {SafelinkClient} from './cache/safelinkClient.js' -import {Config} from './config.js' +import {type Config} from './config.js' import Database from './db/index.js' export type AppContextOptions = { @@ -16,7 +16,10 @@ export class AppContext { constructor(private opts: AppContextOptions) { this.cfg = this.opts.cfg this.db = this.opts.db - this.safelinkClient = new SafelinkClient({db: this.opts.db}) + this.safelinkClient = new SafelinkClient({ + cfg: this.opts.cfg.service, + db: this.opts.db, + }) } static async fromConfig(cfg: Config, overrides?: Partial) { diff --git a/bskylink/src/db/schema.ts b/bskylink/src/db/schema.ts index d8f0e9d10f..08412a105c 100644 --- a/bskylink/src/db/schema.ts +++ b/bskylink/src/db/schema.ts @@ -1,8 +1,9 @@ -import {Selectable} from 'kysely' +import {type GeneratedAlways, type Selectable} from 'kysely' export type DbSchema = { link: Link safelink_rule: SafelinkRule + safelink_cursor: SafelinkCursor } export interface Link { @@ -15,10 +16,19 @@ export enum LinkType { StarterPack = 1, } -export type RuleEventType = '#addRule' | '#updateRule' | '#removeRule' -export type RulePatternType = '#domain' | '#url' -export type RuleActionType = '#block' | '#warn' | '#whitelist' -export type RuleReasonType = '#csam' | '#spam' | '#phishing' | '#none' +export type RuleEventType = + | 'addRule' + | 'updateRule' + | 'removeRule' + | (string & {}) +export type RulePatternType = 'domain' | 'url' | (string & {}) +export type RuleActionType = 'block' | 'warn' | 'whitelist' | (string & {}) +export type RuleReasonType = + | 'csam' + | 'spam' + | 'phishing' + | 'none' + | (string & {}) export interface SafelinkRule { id: number @@ -32,5 +42,12 @@ export interface SafelinkRule { comment?: string } +export interface SafelinkCursor { + id: GeneratedAlways + cursor: string + createdAt: Date +} + export type LinkEntry = Selectable -export type RuleEntry = Selectable +export type SafelinkRuleEntry = Selectable +export type SafelinkCursorEntry = Selectable diff --git a/bskylink/src/html/linkRedirectContents.ts b/bskylink/src/html/linkRedirectContents.ts index f387963ea4..f1bcdbb91e 100644 --- a/bskylink/src/html/linkRedirectContents.ts +++ b/bskylink/src/html/linkRedirectContents.ts @@ -1,8 +1,7 @@ import escapeHTML from 'escape-html' -import {type Hole, html} from 'uhtml' -export function linkRedirectContents(link: string): Hole { - return html` +export function linkRedirectContents(link: string): string { + return ` diff --git a/bskylink/src/html/linkWarningContents.ts b/bskylink/src/html/linkWarningContents.ts index e4585d88af..31449c3c86 100644 --- a/bskylink/src/html/linkWarningContents.ts +++ b/bskylink/src/html/linkWarningContents.ts @@ -1,6 +1,5 @@ import escapeHTML from 'escape-html' import {type Request} from 'express' -import {type Hole, html} from 'uhtml' export function linkWarningContents( req: Request, @@ -8,35 +7,38 @@ export function linkWarningContents( type: 'warn' | 'block' link: string }, -): Hole { - return html` +): string { + const continueButton = + opts.type === 'warn' + ? `${req.__('Continue Anyway')}` + : '' + + return `
⚠️

- ${opts.type === 'warn' - ? req.__('Potentially Dangerous Link') - : req.__('Blocked Link')} + ${ + opts.type === 'warn' + ? req.__('Potentially Dangerous Link') + : req.__('Blocked Link') + }

- ${opts.type === 'warn' - ? req.__( - 'This link may be malicious. You should proceed at your own risk.', - ) - : req.__( - 'This link has been identified as malicious and has blocked for your safety.', - )} + ${ + opts.type === 'warn' + ? req.__( + 'This link may be malicious. You should proceed at your own risk.', + ) + : req.__( + 'This link has been identified as malicious and has blocked for your safety.', + ) + }

${escapeHTML(opts.link)}

- ${opts.type === 'warn' - ? html`${req.__('Continue Anyway')}` - : null} - ${req.__('Return to Bluesky')} + ${continueButton} + ${req.__('Return to Bluesky')}
` } diff --git a/bskylink/src/html/linkWarningLayout.ts b/bskylink/src/html/linkWarningLayout.ts index 2b86ac382d..2d53610193 100644 --- a/bskylink/src/html/linkWarningLayout.ts +++ b/bskylink/src/html/linkWarningLayout.ts @@ -1,11 +1,10 @@ import escapeHTML from 'escape-html' -import {type Hole, html} from 'uhtml' export function linkWarningLayout( title: string, - containerContents: Hole, -): Hole { - return html` + containerContents: string, +): string { + return ` @@ -23,7 +22,6 @@ export function linkWarningLayout( padding: 0; box-sizing: border-box; } - body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, @@ -35,25 +33,21 @@ export function linkWarningLayout( 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; @@ -61,7 +55,6 @@ export function linkWarningLayout( margin-bottom: 24px; padding: 0 20px; } - .blocked-site { background-color: #f7f9fa; border-radius: 12px; @@ -70,7 +63,6 @@ export function linkWarningLayout( text-align: left; word-break: break-all; } - .site-name { font-size: 16px; font-weight: 500; @@ -80,7 +72,6 @@ export function linkWarningLayout( display: block; text-align: center; } - .site-url { font-size: 14px; color: #536471; @@ -88,7 +79,6 @@ export function linkWarningLayout( display: block; text-align: center; } - .button { border: none; border-radius: 24px; @@ -100,23 +90,18 @@ export function linkWarningLayout( 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; diff --git a/bskylink/src/routes/redirect.ts b/bskylink/src/routes/redirect.ts index 6a99153f6a..1ed2b0755a 100644 --- a/bskylink/src/routes/redirect.ts +++ b/bskylink/src/routes/redirect.ts @@ -2,7 +2,6 @@ import assert from 'node:assert' import {DAY, SECOND} from '@atproto/common' import {type Express} from 'express' -import {type Hole} from 'uhtml' import {type AppContext} from '../context.js' import {linkRedirectContents} from '../html/linkRedirectContents.js' @@ -48,18 +47,17 @@ export default function (ctx: AppContext, app: Express) { res.status(200) res.type('html') - let hole: Hole | undefined + let html: string | undefined if (ctx.cfg.service.safelinkEnabled) { const rule = await ctx.safelinkClient.tryFindRule(link) - if (rule !== 'ok') { switch (rule.action) { - case '#whitelist': + case 'whitelist': redirectLogger.info(`Whitelist rule matched for ${rule.url}`) break - case '#block': - hole = linkWarningLayout( + case 'block': + html = linkWarningLayout( 'Blocked Link Warning', linkWarningContents(req, { type: 'block', @@ -69,8 +67,8 @@ export default function (ctx: AppContext, app: Express) { res.setHeader('Cache-Control', 'no-store') redirectLogger.info(`Block rule matched for ${rule.url}`) break - case '#warn': - hole = linkWarningLayout( + case 'warn': + html = linkWarningLayout( 'Malicious Link Warning', linkWarningContents(req, { type: 'warn', @@ -88,12 +86,12 @@ export default function (ctx: AppContext, app: Express) { } } - // If there is no hole defined yet, we will create a redirect hole - if (!hole) { - hole = linkRedirectContents(url.href) + // If there is no html defined yet, we will create a redirect html + if (!html) { + html = linkRedirectContents(url.href) } - return res.end(String(hole)) + return res.end(html) }), ) } diff --git a/bskylink/yarn.lock b/bskylink/yarn.lock index 4d463ec091..e360a5529a 100644 --- a/bskylink/yarn.lock +++ b/bskylink/yarn.lock @@ -98,11 +98,6 @@ dependencies: make-plural "^7.0.0" -"@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" @@ -136,18 +131,6 @@ 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" @@ -281,11 +264,6 @@ 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" @@ -329,41 +307,6 @@ 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" @@ -374,11 +317,6 @@ 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" @@ -488,11 +426,6 @@ 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" @@ -540,21 +473,6 @@ 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" @@ -1159,27 +1077,6 @@ 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"