normalize links

This commit is contained in:
Hailey
2025-08-31 14:55:34 -07:00
parent b6976feac1
commit e2b6452e0c
2 changed files with 89 additions and 20 deletions
-2
View File
@@ -17,8 +17,6 @@ async function main() {
link.ctx.safelinkClient.runFetchEvents()
}
console.log('here')
await link.start()
httpLogger.info('link service is running')
process.on('SIGTERM', async () => {
+89 -18
View File
@@ -10,6 +10,9 @@ import type Database from '../db/index.js'
import {type RulePatternType, type SafelinkRule} from '../db/schema.js'
import {redirectLogger} from '../logger.js'
const SAFELINK_MIN_FETCH_INTERVAL = 1_000
const SAFELINK_MAX_FETCH_INTERVAL = 10_000
export class SafelinkClient {
private domainCache: LRUCache<string, SafelinkRule | 'ok'>
private urlCache: LRUCache<string, SafelinkRule | 'ok'>
@@ -39,37 +42,47 @@ export class SafelinkClient {
}
public async tryFindRule(link: string): Promise<SafelinkRule | 'ok'> {
const u = new URL(link)
u.search = ''
u.hash = ''
let url: string
let domain: string
try {
url = SafelinkClient.normalizeUrl(link)
domain = SafelinkClient.normalizeDomain(link)
} catch (e) {
redirectLogger.error(
{error: e, inputUrl: link},
'failed to normalize looked up link',
)
const d = new URL(u.href)
d.pathname = ''
return 'ok'
}
const urlRule = this.urlCache.get(u.href)
redirectLogger.info(url)
redirectLogger.info(domain)
const urlRule = this.urlCache.get(url)
if (urlRule) {
return urlRule
}
const domainRule = this.domainCache.get(d.href)
const domainRule = this.domainCache.get(domain)
if (domainRule) {
return domainRule
}
try {
const maybeUrlRule = await this.getRule(this.db, u.href, 'url')
this.urlCache.set(u.href, maybeUrlRule)
const maybeUrlRule = await this.getRule(this.db, url, 'url')
this.urlCache.set(url, maybeUrlRule)
return maybeUrlRule
} catch (e) {
this.urlCache.set(u.href, 'ok')
this.urlCache.set(url, 'ok')
}
try {
const maybeDomainRule = await this.getRule(this.db, u.href, 'domain')
this.domainCache.set(d.href, maybeDomainRule)
const maybeDomainRule = await this.getRule(this.db, domain, 'domain')
this.domainCache.set(domain, maybeDomainRule)
return maybeDomainRule
} catch (e) {
this.domainCache.set(d.href, 'ok')
this.domainCache.set(domain, 'ok')
}
return 'ok'
@@ -89,6 +102,20 @@ export class SafelinkClient {
}
private async addRule(db: Database, rule: SafelinkRule) {
try {
if (rule.pattern === 'url') {
rule.url = SafelinkClient.normalizeUrl(rule.url)
} else if (rule.pattern === 'domain') {
rule.url = SafelinkClient.normalizeDomain(rule.url)
}
} catch (e) {
redirectLogger.error(
{error: e, inputUrl: rule.url},
'failed to normalize rule input URL',
)
return
}
db.db
.insertInto('safelink_rule')
.values(rule)
@@ -108,6 +135,20 @@ export class SafelinkClient {
}
private async removeRule(db: Database, rule: SafelinkRule) {
try {
if (rule.pattern === 'url') {
rule.url = SafelinkClient.normalizeUrl(rule.url)
} else if (rule.pattern === 'domain') {
rule.url = SafelinkClient.normalizeDomain(rule.url)
}
} catch (e) {
redirectLogger.error(
{error: e, inputUrl: rule.url},
'failed to normalize rule input URL',
)
return
}
await db.db
.deleteFrom('safelink_rule')
.where('pattern', '=', 'domain')
@@ -133,7 +174,7 @@ export class SafelinkClient {
agent = await this.ozoneAgent.getAgent()
} catch (err) {
redirectLogger.error({error: err}, 'error getting Ozone agent')
setTimeout(() => this.runFetchEvents(), 10_000)
setTimeout(() => this.runFetchEvents(), SAFELINK_MAX_FETCH_INTERVAL)
return
}
@@ -149,12 +190,16 @@ export class SafelinkClient {
{error: err},
'error fetching safelink events from Ozone',
)
setTimeout(() => this.runFetchEvents(), 10_000)
setTimeout(() => this.runFetchEvents(), SAFELINK_MAX_FETCH_INTERVAL)
return
}
if (res.data.cursor === this.cursor) {
setTimeout(() => this.runFetchEvents(), 10_000)
if (res.data.cursor === this.cursor || res.data.events.length === 0) {
redirectLogger.info(
{cursor: res.data.cursor},
'received same cursor from Ozone',
)
setTimeout(() => this.runFetchEvents(), SAFELINK_MAX_FETCH_INTERVAL)
} else {
await this.db.transaction(async db => {
for (const rule of res.data.events) {
@@ -166,9 +211,13 @@ export class SafelinkClient {
}
})
if (res.data.cursor) {
redirectLogger.info(
{cursor: res.data.cursor},
'received new cursor from Ozone',
)
await this.setCursor(res.data.cursor)
}
setTimeout(() => this.runFetchEvents(), 1_000)
setTimeout(() => this.runFetchEvents(), SAFELINK_MIN_FETCH_INTERVAL)
}
}
@@ -200,10 +249,32 @@ export class SafelinkClient {
createdAt: new Date(),
})
.execute()
this.cursor = cursor
} catch (err) {
redirectLogger.error({error: err}, 'failed to update safelink cursor')
}
}
private static normalizeUrl(input: string) {
if (!input.startsWith('https://')) {
input = `https://${input}`
}
const u = new URL(input)
u.hash = ''
let normalized = u.href.replace(/^[^:]+:\/\//, '').toLowerCase()
if (normalized.endsWith('/')) {
normalized = normalized.substring(0, normalized.length - 1)
}
return normalized
}
private static normalizeDomain(input: string) {
if (!input.startsWith('https://')) {
input = `https://${input}`
}
const u = new URL(input)
return u.host.toLowerCase()
}
}
export class OzoneAgent {