wildebeest/backend/src/mastodon/subscription.ts

152 wiersze
4.1 KiB
TypeScript

import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import { b64ToUrlEncoded, exportPublicKeyPair } from 'wildebeest/backend/src/webpush/util'
import { Client } from './client'
export type PushSubscription = {
endpoint: string
keys: {
p256dh: string
auth: string
}
}
export interface CreateRequest {
subscription: PushSubscription
data: {
alerts: {
mention?: boolean
status?: boolean
reblog?: boolean
follow?: boolean
follow_request?: boolean
favourite?: boolean
poll?: boolean
update?: boolean
admin_sign_up?: boolean
admin_report?: boolean
}
policy: string
}
}
export type Subscription = {
// While the spec says to use a string as id (https://docs.joinmastodon.org/entities/WebPushSubscription/#id), Mastodon's android app decided to violate that (https://github.com/mastodon/mastodon-android/blob/master/mastodon/src/main/java/org/joinmastodon/android/model/PushSubscription.java#LL11).
id: number
gateway: PushSubscription
alerts: {
mention: boolean
status: boolean
reblog: boolean
follow: boolean
follow_request: boolean
favourite: boolean
poll: boolean
update: boolean
admin_sign_up: boolean
admin_report: boolean
}
policy: string
}
export async function createSubscription(
db: D1Database,
actor: Actor,
client: Client,
req: CreateRequest
): Promise<Subscription> {
const query = `
INSERT INTO subscriptions (actor_id, client_id, endpoint, key_p256dh, key_auth, alert_mention, alert_status, alert_reblog, alert_follow, alert_follow_request, alert_favourite, alert_poll, alert_update, alert_admin_sign_up, alert_admin_report, policy)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
RETURNING *
`
const row = await db
.prepare(query)
.bind(
actor.id.toString(),
client.id,
req.subscription.endpoint,
req.subscription.keys.p256dh,
req.subscription.keys.auth,
req.data.alerts.mention === false ? 0 : 1,
req.data.alerts.status === false ? 0 : 1,
req.data.alerts.reblog === false ? 0 : 1,
req.data.alerts.follow === false ? 0 : 1,
req.data.alerts.follow_request === false ? 0 : 1,
req.data.alerts.favourite === false ? 0 : 1,
req.data.alerts.poll === false ? 0 : 1,
req.data.alerts.update === false ? 0 : 1,
req.data.alerts.admin_sign_up === false ? 0 : 1,
req.data.alerts.admin_report === false ? 0 : 1,
req.data.policy
)
.first<any>()
return subscriptionFromRow(row)
}
export async function getSubscription(db: D1Database, actor: Actor, client: Client): Promise<Subscription | null> {
const query = `
SELECT * FROM subscriptions WHERE actor_id=? AND client_id=?
`
const { success, error, results } = await db.prepare(query).bind(actor.id.toString(), client.id).all()
if (!success) {
throw new Error('SQL error: ' + error)
}
if (!results || results.length === 0) {
return null
}
const row: any = results[0]
return subscriptionFromRow(row)
}
export async function getSubscriptionForAllClients(db: D1Database, actor: Actor): Promise<Array<Subscription>> {
const query = `
SELECT * FROM subscriptions WHERE actor_id=? ORDER BY cdate DESC LIMIT 5
`
const { success, error, results } = await db.prepare(query).bind(actor.id.toString()).all()
if (!success) {
throw new Error('SQL error: ' + error)
}
if (!results) {
return []
}
return results.map(subscriptionFromRow)
}
function subscriptionFromRow(row: any): Subscription {
return {
id: row.id,
gateway: {
endpoint: row.endpoint,
keys: {
p256dh: row.key_p256dh,
auth: row.key_auth,
},
},
alerts: {
mention: row.alert_mention === 1,
status: row.alert_status === 1,
reblog: row.alert_reblog === 1,
follow: row.alert_follow === 1,
follow_request: row.alert_follow_request === 1,
favourite: row.alert_favourite === 1,
poll: row.alert_poll === 1,
update: row.alert_update === 1,
admin_sign_up: row.alert_admin_sign_up === 1,
admin_report: row.alert_admin_report === 1,
},
policy: row.policy,
}
}
export function VAPIDPublicKey(keys: JWK): string {
return b64ToUrlEncoded(exportPublicKeyPair(keys))
}