kopia lustrzana https://github.com/cloudflare/wildebeest
improve be (+functions) linting:
- enable no-unsafe-call for be/functions linting - enable backend await-thenable eslint rule - enable backend no-misused-promises eslint rulepull/75/head
rodzic
c15f6bec5e
commit
3738c7dda1
|
@ -18,6 +18,10 @@ module.exports = {
|
||||||
'@typescript-eslint/no-unused-vars': 'error',
|
'@typescript-eslint/no-unused-vars': 'error',
|
||||||
'no-console': 'off',
|
'no-console': 'off',
|
||||||
'no-constant-condition': 'off',
|
'no-constant-condition': 'off',
|
||||||
|
'@typescript-eslint/require-await': 'off',
|
||||||
|
'@typescript-eslint/no-unsafe-call': 'error',
|
||||||
|
'@typescript-eslint/await-thenable': 'error',
|
||||||
|
'@typescript-eslint/no-misused-promises': 'error',
|
||||||
/*
|
/*
|
||||||
Note: the following rules have been set to off so that linting
|
Note: the following rules have been set to off so that linting
|
||||||
can pass with the current code, but we need to gradually
|
can pass with the current code, but we need to gradually
|
||||||
|
@ -25,13 +29,9 @@ module.exports = {
|
||||||
*/
|
*/
|
||||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||||
'@typescript-eslint/no-unsafe-argument': 'off',
|
'@typescript-eslint/no-unsafe-argument': 'off',
|
||||||
'@typescript-eslint/no-unsafe-call': 'off',
|
|
||||||
'@typescript-eslint/no-unsafe-member-access': 'off',
|
'@typescript-eslint/no-unsafe-member-access': 'off',
|
||||||
'@typescript-eslint/restrict-plus-operands': 'off',
|
'@typescript-eslint/restrict-plus-operands': 'off',
|
||||||
'@typescript-eslint/await-thenable': 'off',
|
|
||||||
'@typescript-eslint/require-await': 'off',
|
|
||||||
'@typescript-eslint/restrict-template-expressions': 'off',
|
'@typescript-eslint/restrict-template-expressions': 'off',
|
||||||
'@typescript-eslint/no-misused-promises': 'off',
|
|
||||||
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
|
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
'@typescript-eslint/no-inferrable-types': 'off',
|
'@typescript-eslint/no-inferrable-types': 'off',
|
||||||
|
|
|
@ -121,7 +121,7 @@ export const generateValidator =
|
||||||
|
|
||||||
const unroundedSecondsSinceEpoch = Date.now() / 1000
|
const unroundedSecondsSinceEpoch = Date.now() / 1000
|
||||||
|
|
||||||
const payloadObj = JSON.parse(textDecoder.decode(base64URLDecode(payload)))
|
const payloadObj = JSON.parse(textDecoder.decode(base64URLDecode(payload))) as JWTPayload
|
||||||
|
|
||||||
// For testing disable JWT checks.
|
// For testing disable JWT checks.
|
||||||
// Ideally we match the production behavior in testing but that
|
// Ideally we match the production behavior in testing but that
|
||||||
|
|
|
@ -29,7 +29,7 @@ function extractID(domain: string, s: string | URL): string {
|
||||||
return s.toString().replace(`https://${domain}/ap/users/`, '')
|
return s.toString().replace(`https://${domain}/ap/users/`, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeGetObjectAsId(activity: Activity): Function {
|
export function makeGetObjectAsId(activity: Activity) {
|
||||||
return () => {
|
return () => {
|
||||||
let url: any = null
|
let url: any = null
|
||||||
if (activity.object.id !== undefined) {
|
if (activity.object.id !== undefined) {
|
||||||
|
@ -55,7 +55,7 @@ export function makeGetObjectAsId(activity: Activity): Function {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeGetActorAsId(activity: Activity): Function {
|
export function makeGetActorAsId(activity: Activity) {
|
||||||
return () => {
|
return () => {
|
||||||
let url: any = null
|
let url: any = null
|
||||||
if (activity.actor.id !== undefined) {
|
if (activity.actor.id !== undefined) {
|
||||||
|
|
|
@ -154,7 +154,7 @@ export async function getObjectByMastodonId(db: D1Database, id: UUID): Promise<O
|
||||||
return getObjectBy(db, 'mastodon_id', id)
|
return getObjectBy(db, 'mastodon_id', id)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getObjectBy(db: D1Database, key: string, value: string): Promise<Object | null> {
|
export async function getObjectBy(db: D1Database, key: string, value: string) {
|
||||||
const query = `
|
const query = `
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM objects
|
FROM objects
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import type { Env } from 'wildebeest/consumer/src'
|
||||||
|
|
||||||
const CACHE_DO_NAME = 'cachev1'
|
const CACHE_DO_NAME = 'cachev1'
|
||||||
|
|
||||||
export interface Cache {
|
export interface Cache {
|
||||||
|
@ -5,7 +7,7 @@ export interface Cache {
|
||||||
put<T>(key: string, value: T): Promise<void>
|
put<T>(key: string, value: T): Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cacheFromEnv(env: any): Cache {
|
export function cacheFromEnv(env: Env): Cache {
|
||||||
return {
|
return {
|
||||||
async get<T>(key: string): Promise<T | null> {
|
async get<T>(key: string): Promise<T | null> {
|
||||||
const id = env.DO_CACHE.idFromName(CACHE_DO_NAME)
|
const id = env.DO_CACHE.idFromName(CACHE_DO_NAME)
|
||||||
|
|
|
@ -8,7 +8,11 @@ import { getPersonById } from 'wildebeest/backend/src/activitypub/actors'
|
||||||
import type { WebPushInfos, WebPushMessage } from 'wildebeest/backend/src/webpush/webpushinfos'
|
import type { WebPushInfos, WebPushMessage } from 'wildebeest/backend/src/webpush/webpushinfos'
|
||||||
import { WebPushResult } from 'wildebeest/backend/src/webpush/webpushinfos'
|
import { WebPushResult } from 'wildebeest/backend/src/webpush/webpushinfos'
|
||||||
import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
|
import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
|
||||||
import type { NotificationType, Notification } from 'wildebeest/backend/src/types/notification'
|
import type {
|
||||||
|
NotificationType,
|
||||||
|
Notification,
|
||||||
|
NotificationsQueryResult,
|
||||||
|
} from 'wildebeest/backend/src/types/notification'
|
||||||
import { getSubscriptionForAllClients } from 'wildebeest/backend/src/mastodon/subscription'
|
import { getSubscriptionForAllClients } from 'wildebeest/backend/src/mastodon/subscription'
|
||||||
import type { Cache } from 'wildebeest/backend/src/cache'
|
import type { Cache } from 'wildebeest/backend/src/cache'
|
||||||
|
|
||||||
|
@ -24,10 +28,10 @@ export async function createNotification(
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
`
|
`
|
||||||
const row: { id: string } = await db
|
const row = await db
|
||||||
.prepare(query)
|
.prepare(query)
|
||||||
.bind(type, actor.id.toString(), fromActor.id.toString(), obj.id.toString())
|
.bind(type, actor.id.toString(), fromActor.id.toString(), obj.id.toString())
|
||||||
.first()
|
.first<{ id: string }>()
|
||||||
return row.id
|
return row.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +43,7 @@ export async function insertFollowNotification(db: D1Database, actor: Actor, fro
|
||||||
VALUES (?, ?, ?)
|
VALUES (?, ?, ?)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
`
|
`
|
||||||
const row: { id: string } = await db.prepare(query).bind(type, actor.id.toString(), fromActor.id.toString()).first()
|
const row = await db.prepare(query).bind(type, actor.id.toString(), fromActor.id.toString()).first<{ id: string }>()
|
||||||
return row.id
|
return row.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +191,7 @@ export async function getNotifications(db: D1Database, actor: Actor, domain: str
|
||||||
`
|
`
|
||||||
|
|
||||||
const stmt = db.prepare(query).bind(actor.id.toString())
|
const stmt = db.prepare(query).bind(actor.id.toString())
|
||||||
const { results, success, error } = await stmt.all()
|
const { results, success, error } = await stmt.all<NotificationsQueryResult>()
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new Error('SQL error: ' + error)
|
throw new Error('SQL error: ' + error)
|
||||||
}
|
}
|
||||||
|
@ -198,7 +202,7 @@ export async function getNotifications(db: D1Database, actor: Actor, domain: str
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0, len = results.length; i < len; i++) {
|
for (let i = 0, len = results.length; i < len; i++) {
|
||||||
const result = results[i] as any
|
const result = results[i]
|
||||||
const properties = JSON.parse(result.properties)
|
const properties = JSON.parse(result.properties)
|
||||||
const notifFromActorId = new URL(result.notif_from_actor_id)
|
const notifFromActorId = new URL(result.notif_from_actor_id)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import type { MastodonAccount } from 'wildebeest/backend/src/types/account'
|
import type { MastodonAccount } from 'wildebeest/backend/src/types/account'
|
||||||
import type { MastodonStatus } from 'wildebeest/backend/src/types/status'
|
import type { MastodonStatus } from 'wildebeest/backend/src/types/status'
|
||||||
|
import type { ObjectsRow } from './objects'
|
||||||
|
|
||||||
export type NotificationType =
|
export type NotificationType =
|
||||||
| 'mention'
|
| 'mention'
|
||||||
|
@ -20,3 +21,12 @@ export type Notification = {
|
||||||
account: MastodonAccount
|
account: MastodonAccount
|
||||||
status?: MastodonStatus
|
status?: MastodonStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NotificationsQueryResult extends ObjectsRow {
|
||||||
|
type: NotificationType
|
||||||
|
original_actor_id: URL
|
||||||
|
notif_from_actor_id: URL
|
||||||
|
notif_cdate: string
|
||||||
|
notif_id: URL
|
||||||
|
from_actor_id: string
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface ObjectsRow {
|
||||||
|
properties: string
|
||||||
|
mastodon_id: string
|
||||||
|
id: URL
|
||||||
|
cdate: string
|
||||||
|
}
|
|
@ -26,10 +26,8 @@ export async function readBody<T>(request: Request): Promise<T> {
|
||||||
// The `key[]` notiation is used when sending an array of values.
|
// The `key[]` notiation is used when sending an array of values.
|
||||||
|
|
||||||
const key2 = key.replace('[]', '')
|
const key2 = key.replace('[]', '')
|
||||||
if (out[key2] === undefined) {
|
const outArr: unknown[] = (out[key2] ??= [])
|
||||||
out[key2] = []
|
outArr.push(value)
|
||||||
}
|
|
||||||
out[key2].push(value)
|
|
||||||
} else {
|
} else {
|
||||||
out[key] = value
|
out[key] = value
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// @ts-nocheck
|
|
||||||
// Copyright 2012 Joyent, Inc. All rights reserved.
|
// Copyright 2012 Joyent, Inc. All rights reserved.
|
||||||
|
|
||||||
import { HEADER, HttpSignatureError, InvalidAlgorithmError, validateAlgorithm } from './utils'
|
import { HEADER, HttpSignatureError, InvalidAlgorithmError, validateAlgorithm } from './utils'
|
||||||
|
@ -61,6 +60,9 @@ export type ParsedSignature = {
|
||||||
keyId: string
|
keyId: string
|
||||||
signingString: string
|
signingString: string
|
||||||
algorithm: string
|
algorithm: string
|
||||||
|
scheme: string
|
||||||
|
params: Record<string, string | string[] | number>
|
||||||
|
opaque: string
|
||||||
}
|
}
|
||||||
|
|
||||||
///--- Exported API
|
///--- Exported API
|
||||||
|
@ -141,10 +143,14 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
|
||||||
let tmpName = ''
|
let tmpName = ''
|
||||||
let tmpValue = ''
|
let tmpValue = ''
|
||||||
|
|
||||||
const parsed = {
|
const parsed: ParsedSignature = {
|
||||||
scheme: authz === request.headers.get(HEADER.SIG) ? 'Signature' : '',
|
scheme: authz === request.headers.get(HEADER.SIG) ? 'Signature' : '',
|
||||||
params: {},
|
params: {},
|
||||||
signingString: '',
|
signingString: '',
|
||||||
|
signature: '',
|
||||||
|
keyId: '',
|
||||||
|
algorithm: '',
|
||||||
|
opaque: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < authz.length; i++) {
|
for (i = 0; i < authz.length; i++) {
|
||||||
|
@ -234,14 +240,16 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let parsedHeaders: string[] = []
|
||||||
|
|
||||||
if (!parsed.params.headers || parsed.params.headers === '') {
|
if (!parsed.params.headers || parsed.params.headers === '') {
|
||||||
if (request.headers.has('x-date')) {
|
if (request.headers.has('x-date')) {
|
||||||
parsed.params.headers = ['x-date']
|
parsedHeaders = ['x-date']
|
||||||
} else {
|
} else {
|
||||||
parsed.params.headers = ['date']
|
parsedHeaders = ['date']
|
||||||
}
|
}
|
||||||
} else {
|
} else if (typeof parsed.params.headers === 'string') {
|
||||||
parsed.params.headers = parsed.params.headers.split(' ')
|
parsedHeaders = parsed.params.headers.split(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minimally validate the parsed object
|
// Minimally validate the parsed object
|
||||||
|
@ -253,13 +261,13 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
|
||||||
|
|
||||||
if (!parsed.params.signature) throw new InvalidHeaderError('signature was not specified')
|
if (!parsed.params.signature) throw new InvalidHeaderError('signature was not specified')
|
||||||
|
|
||||||
if (['date', 'x-date', '(created)'].every((hdr) => parsed.params.headers.indexOf(hdr) < 0)) {
|
if (['date', 'x-date', '(created)'].every((hdr) => parsedHeaders.indexOf(hdr) < 0)) {
|
||||||
throw new MissingHeaderError('no signed date header')
|
throw new MissingHeaderError('no signed date header')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the algorithm against the official list
|
// Check the algorithm against the official list
|
||||||
try {
|
try {
|
||||||
validateAlgorithm(parsed.params.algorithm, 'rsa')
|
validateAlgorithm(parsed.params.algorithm as string, 'rsa')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof InvalidAlgorithmError)
|
if (e instanceof InvalidAlgorithmError)
|
||||||
throw new InvalidParamsError(parsed.params.algorithm + ' is not ' + 'supported')
|
throw new InvalidParamsError(parsed.params.algorithm + ' is not ' + 'supported')
|
||||||
|
@ -267,9 +275,9 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the signingString
|
// Build the signingString
|
||||||
for (i = 0; i < parsed.params.headers.length; i++) {
|
for (i = 0; i < parsedHeaders.length; i++) {
|
||||||
const h = parsed.params.headers[i].toLowerCase()
|
const h = parsedHeaders[i].toLowerCase()
|
||||||
parsed.params.headers[i] = h
|
parsedHeaders[i] = h
|
||||||
|
|
||||||
if (h === 'request-line') {
|
if (h === 'request-line') {
|
||||||
if (!options.strict) {
|
if (!options.strict) {
|
||||||
|
@ -292,6 +300,7 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
|
||||||
} else if (h === '(opaque)') {
|
} else if (h === '(opaque)') {
|
||||||
const opaque = parsed.params.opaque
|
const opaque = parsed.params.opaque
|
||||||
if (opaque === undefined) {
|
if (opaque === undefined) {
|
||||||
|
//@ts-expect-error -- authzHeaderName doesn't exist TOFIX
|
||||||
throw new MissingHeaderError('opaque param was not in the ' + authzHeaderName + ' header')
|
throw new MissingHeaderError('opaque param was not in the ' + authzHeaderName + ' header')
|
||||||
}
|
}
|
||||||
parsed.signingString += '(opaque): ' + opaque
|
parsed.signingString += '(opaque): ' + opaque
|
||||||
|
@ -305,17 +314,17 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
|
||||||
parsed.signingString += h + ': ' + value
|
parsed.signingString += h + ': ' + value
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i + 1 < parsed.params.headers.length) parsed.signingString += '\n'
|
if (i + 1 < parsedHeaders.length) parsed.signingString += '\n'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check against the constraints
|
// Check against the constraints
|
||||||
let date
|
let date
|
||||||
let skew
|
let skew
|
||||||
if (request.headers.date || request.headers.has('x-date')) {
|
if (request.headers.get('date') || request.headers.has('x-date')) {
|
||||||
if (request.headers.has('x-date')) {
|
if (request.headers.has('x-date')) {
|
||||||
date = new Date(request.headers.get('x-date') as string)
|
date = new Date(request.headers.get('x-date') as string)
|
||||||
} else {
|
} else {
|
||||||
date = new Date(request.headers.date)
|
date = new Date(request.headers.get('date') as string)
|
||||||
}
|
}
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
skew = Math.abs(now.getTime() - date.getTime())
|
skew = Math.abs(now.getTime() - date.getTime())
|
||||||
|
@ -325,7 +334,7 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsed.params.created) {
|
if (parsed.params.created && typeof parsed.params.created === 'number') {
|
||||||
skew = parsed.params.created - Math.floor(Date.now() / 1000)
|
skew = parsed.params.created - Math.floor(Date.now() / 1000)
|
||||||
if (skew > options.clockSkew) {
|
if (skew > options.clockSkew) {
|
||||||
throw new ExpiredRequestError(
|
throw new ExpiredRequestError(
|
||||||
|
@ -340,7 +349,7 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsed.params.expires) {
|
if (parsed.params.expires && typeof parsed.params.expires === 'number') {
|
||||||
const expiredSince = Math.floor(Date.now() / 1000) - parsed.params.expires
|
const expiredSince = Math.floor(Date.now() / 1000) - parsed.params.expires
|
||||||
if (expiredSince > options.clockSkew) {
|
if (expiredSince > options.clockSkew) {
|
||||||
throw new ExpiredRequestError(
|
throw new ExpiredRequestError(
|
||||||
|
@ -352,14 +361,17 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
|
||||||
headers.forEach(function (hdr) {
|
headers.forEach(function (hdr) {
|
||||||
// Remember that we already checked any headers in the params
|
// Remember that we already checked any headers in the params
|
||||||
// were in the request, so if this passes we're good.
|
// were in the request, so if this passes we're good.
|
||||||
if (parsed.params.headers.indexOf(hdr.toLowerCase()) < 0)
|
if (parsedHeaders.indexOf(hdr.toLowerCase()) < 0) {
|
||||||
throw new MissingHeaderError(hdr + ' was not a signed header')
|
throw new MissingHeaderError(hdr + ' was not a signed header')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
parsed.params.algorithm = parsed.params.algorithm.toLowerCase()
|
const algorithm = parsed.params.algorithm as string
|
||||||
parsed.algorithm = parsed.params.algorithm.toUpperCase()
|
parsed.params.algorithm = algorithm.toLowerCase()
|
||||||
parsed.keyId = parsed.params.keyId
|
parsed.algorithm = algorithm.toUpperCase()
|
||||||
parsed.opaque = parsed.params.opaque
|
parsed.keyId = parsed.params.keyId as string
|
||||||
parsed.signature = parsed.params.signature
|
parsed.opaque = parsed.params.opaque as string
|
||||||
|
parsed.signature = parsed.params.signature as string
|
||||||
|
parsed.params.headers = parsedHeaders
|
||||||
return parsed
|
return parsed
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,16 +21,17 @@ describe('ActivityPub', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
receivedActivity = null
|
receivedActivity = null
|
||||||
|
|
||||||
globalThis.fetch = async (input: any) => {
|
globalThis.fetch = async (input: RequestInfo) => {
|
||||||
if (input.url === `https://${domain}/ap/users/sven2/inbox`) {
|
const request = new Request(input)
|
||||||
assert.equal(input.method, 'POST')
|
if (request.url === `https://${domain}/ap/users/sven2/inbox`) {
|
||||||
const data = await input.json()
|
assert.equal(request.method, 'POST')
|
||||||
|
const data = await request.json()
|
||||||
receivedActivity = data
|
receivedActivity = data
|
||||||
console.log({ receivedActivity })
|
console.log({ receivedActivity })
|
||||||
return new Response('')
|
return new Response('')
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('unexpected request to ' + input.url)
|
throw new Error('unexpected request to ' + request.url)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -51,14 +52,17 @@ describe('ActivityPub', () => {
|
||||||
const row = await db
|
const row = await db
|
||||||
.prepare(`SELECT target_actor_id, state FROM actor_following WHERE actor_id=?`)
|
.prepare(`SELECT target_actor_id, state FROM actor_following WHERE actor_id=?`)
|
||||||
.bind(actor2.id.toString())
|
.bind(actor2.id.toString())
|
||||||
.first()
|
.first<{
|
||||||
|
target_actor_id: object
|
||||||
|
state: string
|
||||||
|
}>()
|
||||||
assert(row)
|
assert(row)
|
||||||
assert.equal(row.target_actor_id.toString(), actor.id.toString())
|
assert.equal(row.target_actor_id.toString(), actor.id.toString())
|
||||||
assert.equal(row.state, 'accepted')
|
assert.equal(row.state, 'accepted')
|
||||||
|
|
||||||
assert(receivedActivity)
|
assert(receivedActivity)
|
||||||
assert.equal(receivedActivity.type, 'Accept')
|
assert.equal(receivedActivity.type, 'Accept')
|
||||||
assert.equal(receivedActivity.actor.toString(), actor.id.toString())
|
assert.equal((receivedActivity.actor as object).toString(), actor.id.toString())
|
||||||
assert.equal(receivedActivity.object.actor, activity.actor)
|
assert.equal(receivedActivity.object.actor, activity.actor)
|
||||||
assert.equal(receivedActivity.object.type, activity.type)
|
assert.equal(receivedActivity.object.type, activity.type)
|
||||||
})
|
})
|
||||||
|
@ -144,7 +148,11 @@ describe('ActivityPub', () => {
|
||||||
|
|
||||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||||
|
|
||||||
const entry = await db.prepare('SELECT * FROM actor_notifications').first()
|
const entry = await db.prepare('SELECT * FROM actor_notifications').first<{
|
||||||
|
type: string
|
||||||
|
actor_id: object
|
||||||
|
from_actor_id: object
|
||||||
|
}>()
|
||||||
assert.equal(entry.type, 'follow')
|
assert.equal(entry.type, 'follow')
|
||||||
assert.equal(entry.actor_id.toString(), actor.id.toString())
|
assert.equal(entry.actor_id.toString(), actor.id.toString())
|
||||||
assert.equal(entry.from_actor_id.toString(), actor2.id.toString())
|
assert.equal(entry.from_actor_id.toString(), actor2.id.toString())
|
||||||
|
@ -167,7 +175,7 @@ describe('ActivityPub', () => {
|
||||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||||
|
|
||||||
// Even if we followed multiple times, only one row should be present.
|
// Even if we followed multiple times, only one row should be present.
|
||||||
const { count } = await db.prepare(`SELECT count(*) as count FROM actor_following`).first()
|
const { count } = await db.prepare(`SELECT count(*) as count FROM actor_following`).first<{ count: number }>()
|
||||||
assert.equal(count, 1)
|
assert.equal(count, 1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { cacheObject, getObjectById } from 'wildebeest/backend/src/activitypub/o
|
||||||
import { addFollowing } from 'wildebeest/backend/src/mastodon/follow'
|
import { addFollowing } from 'wildebeest/backend/src/mastodon/follow'
|
||||||
import * as activityHandler from 'wildebeest/backend/src/activitypub/activities/handle'
|
import * as activityHandler from 'wildebeest/backend/src/activitypub/activities/handle'
|
||||||
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
|
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
|
||||||
|
import { ObjectsRow } from 'wildebeest/backend/src/types/objects'
|
||||||
|
|
||||||
const adminEmail = 'admin@example.com'
|
const adminEmail = 'admin@example.com'
|
||||||
const domain = 'cloudflare.com'
|
const domain = 'cloudflare.com'
|
||||||
|
@ -29,7 +30,10 @@ describe('ActivityPub', () => {
|
||||||
}
|
}
|
||||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||||
|
|
||||||
const entry = await db.prepare('SELECT * FROM actor_reblogs').first()
|
const entry = await db.prepare('SELECT * FROM actor_reblogs').first<{
|
||||||
|
actor_id: URL
|
||||||
|
object_id: URL
|
||||||
|
}>()
|
||||||
assert.equal(entry.actor_id.toString(), actorB.id.toString())
|
assert.equal(entry.actor_id.toString(), actorB.id.toString())
|
||||||
assert.equal(entry.object_id.toString(), note.id.toString())
|
assert.equal(entry.object_id.toString(), note.id.toString())
|
||||||
})
|
})
|
||||||
|
@ -48,7 +52,11 @@ describe('ActivityPub', () => {
|
||||||
}
|
}
|
||||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||||
|
|
||||||
const entry = await db.prepare('SELECT * FROM actor_notifications').first()
|
const entry = await db.prepare('SELECT * FROM actor_notifications').first<{
|
||||||
|
type: string
|
||||||
|
actor_id: URL
|
||||||
|
from_actor_id: URL
|
||||||
|
}>()
|
||||||
assert(entry)
|
assert(entry)
|
||||||
assert.equal(entry.type, 'reblog')
|
assert.equal(entry.type, 'reblog')
|
||||||
assert.equal(entry.actor_id.toString(), actorA.id.toString())
|
assert.equal(entry.actor_id.toString(), actorA.id.toString())
|
||||||
|
@ -71,7 +79,7 @@ describe('ActivityPub', () => {
|
||||||
}
|
}
|
||||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||||
|
|
||||||
const entry = await db.prepare('SELECT * FROM actor_favourites').first()
|
const entry = await db.prepare('SELECT * FROM actor_favourites').first<{ actor_id: URL; object_id: URL }>()
|
||||||
assert.equal(entry.actor_id.toString(), actorB.id.toString())
|
assert.equal(entry.actor_id.toString(), actorB.id.toString())
|
||||||
assert.equal(entry.object_id.toString(), note.id.toString())
|
assert.equal(entry.object_id.toString(), note.id.toString())
|
||||||
})
|
})
|
||||||
|
@ -90,7 +98,11 @@ describe('ActivityPub', () => {
|
||||||
}
|
}
|
||||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||||
|
|
||||||
const entry = await db.prepare('SELECT * FROM actor_notifications').first()
|
const entry = await db.prepare('SELECT * FROM actor_notifications').first<{
|
||||||
|
type: string
|
||||||
|
actor_id: URL
|
||||||
|
from_actor_id: URL
|
||||||
|
}>()
|
||||||
assert.equal(entry.type, 'favourite')
|
assert.equal(entry.type, 'favourite')
|
||||||
assert.equal(entry.actor_id.toString(), actorA.id.toString())
|
assert.equal(entry.actor_id.toString(), actorA.id.toString())
|
||||||
assert.equal(entry.from_actor_id.toString(), actorB.id.toString())
|
assert.equal(entry.from_actor_id.toString(), actorB.id.toString())
|
||||||
|
@ -110,7 +122,10 @@ describe('ActivityPub', () => {
|
||||||
}
|
}
|
||||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||||
|
|
||||||
const entry = await db.prepare('SELECT * FROM actor_favourites').first()
|
const entry = await db.prepare('SELECT * FROM actor_favourites').first<{
|
||||||
|
actor_id: URL
|
||||||
|
object_id: URL
|
||||||
|
}>()
|
||||||
assert.equal(entry.actor_id.toString(), actorB.id.toString())
|
assert.equal(entry.actor_id.toString(), actorB.id.toString())
|
||||||
assert.equal(entry.object_id.toString(), note.id.toString())
|
assert.equal(entry.object_id.toString(), note.id.toString())
|
||||||
})
|
})
|
||||||
|
@ -145,7 +160,10 @@ describe('ActivityPub', () => {
|
||||||
const row = await db
|
const row = await db
|
||||||
.prepare(`SELECT target_actor_id, state FROM actor_following WHERE actor_id=?`)
|
.prepare(`SELECT target_actor_id, state FROM actor_following WHERE actor_id=?`)
|
||||||
.bind(actor.id.toString())
|
.bind(actor.id.toString())
|
||||||
.first()
|
.first<{
|
||||||
|
target_actor_id: string
|
||||||
|
state: string
|
||||||
|
}>()
|
||||||
assert(row)
|
assert(row)
|
||||||
assert.equal(row.target_actor_id, 'https://' + domain + '/ap/users/sven2')
|
assert.equal(row.target_actor_id, 'https://' + domain + '/ap/users/sven2')
|
||||||
assert.equal(row.state, 'accepted')
|
assert.equal(row.state, 'accepted')
|
||||||
|
@ -204,7 +222,7 @@ describe('ActivityPub', () => {
|
||||||
|
|
||||||
const entry = await db
|
const entry = await db
|
||||||
.prepare('SELECT objects.* FROM inbox_objects INNER JOIN objects ON objects.id=inbox_objects.object_id')
|
.prepare('SELECT objects.* FROM inbox_objects INNER JOIN objects ON objects.id=inbox_objects.object_id')
|
||||||
.first()
|
.first<ObjectsRow>()
|
||||||
const properties = JSON.parse(entry.properties)
|
const properties = JSON.parse(entry.properties)
|
||||||
assert.equal(properties.content, 'test note')
|
assert.equal(properties.content, 'test note')
|
||||||
})
|
})
|
||||||
|
@ -241,7 +259,10 @@ describe('ActivityPub', () => {
|
||||||
}
|
}
|
||||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||||
|
|
||||||
const entry = await db.prepare('SELECT * FROM outbox_objects WHERE actor_id=?').bind(remoteActorId).first()
|
const entry = await db
|
||||||
|
.prepare('SELECT * FROM outbox_objects WHERE actor_id=?')
|
||||||
|
.bind(remoteActorId)
|
||||||
|
.first<{ actor_id: string }>()
|
||||||
assert.equal(entry.actor_id, remoteActorId)
|
assert.equal(entry.actor_id, remoteActorId)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -263,7 +284,11 @@ describe('ActivityPub', () => {
|
||||||
}
|
}
|
||||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||||
|
|
||||||
const entry = await db.prepare('SELECT * FROM actor_notifications').first()
|
const entry = await db.prepare('SELECT * FROM actor_notifications').first<{
|
||||||
|
type: string
|
||||||
|
actor_id: URL
|
||||||
|
from_actor_id: URL
|
||||||
|
}>()
|
||||||
assert(entry)
|
assert(entry)
|
||||||
assert.equal(entry.type, 'mention')
|
assert.equal(entry.type, 'mention')
|
||||||
assert.equal(entry.actor_id.toString(), actorA.id.toString())
|
assert.equal(entry.actor_id.toString(), actorA.id.toString())
|
||||||
|
@ -303,7 +328,11 @@ describe('ActivityPub', () => {
|
||||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
const entry = await db.prepare('SELECT * FROM actor_replies').first()
|
const entry = await db.prepare('SELECT * FROM actor_replies').first<{
|
||||||
|
actor_id: string
|
||||||
|
object_id: string
|
||||||
|
in_reply_to_object_id: string
|
||||||
|
}>()
|
||||||
assert.equal(entry.actor_id, actor.id.toString().toString())
|
assert.equal(entry.actor_id, actor.id.toString().toString())
|
||||||
|
|
||||||
const obj: any = await getObjectById(db, entry.object_id)
|
const obj: any = await getObjectById(db, entry.object_id)
|
||||||
|
@ -319,7 +348,7 @@ describe('ActivityPub', () => {
|
||||||
const db = await makeDB()
|
const db = await makeDB()
|
||||||
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||||
|
|
||||||
const activity: any = {
|
const activity = {
|
||||||
type: 'Create',
|
type: 'Create',
|
||||||
actor: actor.id.toString(),
|
actor: actor.id.toString(),
|
||||||
to: ['some actor'],
|
to: ['some actor'],
|
||||||
|
@ -332,7 +361,7 @@ describe('ActivityPub', () => {
|
||||||
}
|
}
|
||||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||||
|
|
||||||
const row = await db.prepare('SELECT * FROM outbox_objects').first()
|
const row = await db.prepare('SELECT * FROM outbox_objects').first<{ target: string }>()
|
||||||
assert.equal(row.target, 'some actor')
|
assert.equal(row.target, 'some actor')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -355,7 +384,7 @@ describe('ActivityPub', () => {
|
||||||
|
|
||||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||||
|
|
||||||
const row = await db.prepare(`SELECT * from objects`).first()
|
const row = await db.prepare(`SELECT * from objects`).first<ObjectsRow>()
|
||||||
const { content, name } = JSON.parse(row.properties)
|
const { content, name } = JSON.parse(row.properties)
|
||||||
assert.equal(
|
assert.equal(
|
||||||
content,
|
content,
|
||||||
|
@ -454,7 +483,7 @@ describe('ActivityPub', () => {
|
||||||
const updatedObject = await db
|
const updatedObject = await db
|
||||||
.prepare('SELECT * FROM objects WHERE original_object_id=?')
|
.prepare('SELECT * FROM objects WHERE original_object_id=?')
|
||||||
.bind(object.id)
|
.bind(object.id)
|
||||||
.first()
|
.first<ObjectsRow>()
|
||||||
assert(updatedObject)
|
assert(updatedObject)
|
||||||
assert.equal(JSON.parse(updatedObject.properties).content, newObject.content)
|
assert.equal(JSON.parse(updatedObject.properties).content, newObject.content)
|
||||||
})
|
})
|
||||||
|
@ -500,7 +529,10 @@ describe('ActivityPub', () => {
|
||||||
}
|
}
|
||||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||||
|
|
||||||
const object = await db.prepare('SELECT * FROM objects').first()
|
const object = await db.prepare('SELECT * FROM objects').first<{
|
||||||
|
type: string
|
||||||
|
original_actor_id: string
|
||||||
|
}>()
|
||||||
assert(object)
|
assert(object)
|
||||||
assert.equal(object.type, 'Note')
|
assert.equal(object.type, 'Note')
|
||||||
assert.equal(object.original_actor_id, remoteActorId)
|
assert.equal(object.original_actor_id, remoteActorId)
|
||||||
|
@ -508,7 +540,7 @@ describe('ActivityPub', () => {
|
||||||
const outbox_object = await db
|
const outbox_object = await db
|
||||||
.prepare('SELECT * FROM outbox_objects WHERE actor_id=?')
|
.prepare('SELECT * FROM outbox_objects WHERE actor_id=?')
|
||||||
.bind(remoteActorId)
|
.bind(remoteActorId)
|
||||||
.first()
|
.first<{ actor_id: string }>()
|
||||||
assert(outbox_object)
|
assert(outbox_object)
|
||||||
assert.equal(outbox_object.actor_id, remoteActorId)
|
assert.equal(outbox_object.actor_id, remoteActorId)
|
||||||
})
|
})
|
||||||
|
|
|
@ -26,6 +26,17 @@ async function generateVAPIDKeys(): Promise<JWK> {
|
||||||
|
|
||||||
describe('Mastodon APIs', () => {
|
describe('Mastodon APIs', () => {
|
||||||
describe('instance', () => {
|
describe('instance', () => {
|
||||||
|
type Data = {
|
||||||
|
rules: unknown[]
|
||||||
|
uri: string
|
||||||
|
title: string
|
||||||
|
email: string
|
||||||
|
description: string
|
||||||
|
version: string
|
||||||
|
domain: string
|
||||||
|
contact: { email: string }
|
||||||
|
}
|
||||||
|
|
||||||
test('return the instance infos v1', async () => {
|
test('return the instance infos v1', async () => {
|
||||||
const env = {
|
const env = {
|
||||||
INSTANCE_TITLE: 'a',
|
INSTANCE_TITLE: 'a',
|
||||||
|
@ -39,7 +50,7 @@ describe('Mastodon APIs', () => {
|
||||||
assertJSON(res)
|
assertJSON(res)
|
||||||
|
|
||||||
{
|
{
|
||||||
const data = await res.json<any>()
|
const data = await res.json<Data>()
|
||||||
assert.equal(data.rules.length, 0)
|
assert.equal(data.rules.length, 0)
|
||||||
assert.equal(data.uri, domain)
|
assert.equal(data.uri, domain)
|
||||||
assert.equal(data.title, 'a')
|
assert.equal(data.title, 'a')
|
||||||
|
@ -77,7 +88,7 @@ describe('Mastodon APIs', () => {
|
||||||
assertJSON(res)
|
assertJSON(res)
|
||||||
|
|
||||||
{
|
{
|
||||||
const data = await res.json<any>()
|
const data = await res.json<Data>()
|
||||||
assert.equal(data.rules.length, 0)
|
assert.equal(data.rules.length, 0)
|
||||||
assert.equal(data.domain, domain)
|
assert.equal(data.domain, domain)
|
||||||
assert.equal(data.title, 'a')
|
assert.equal(data.title, 'a')
|
||||||
|
@ -248,7 +259,7 @@ describe('Mastodon APIs', () => {
|
||||||
const res = await subscription.handlePostRequest(db, req, connectedActor, client.id, vapidKeys)
|
const res = await subscription.handlePostRequest(db, req, connectedActor, client.id, vapidKeys)
|
||||||
assert.equal(res.status, 200)
|
assert.equal(res.status, 200)
|
||||||
|
|
||||||
const { count } = await db.prepare('SELECT count(*) as count FROM subscriptions').first()
|
const { count } = await db.prepare('SELECT count(*) as count FROM subscriptions').first<{ count: number }>()
|
||||||
assert.equal(count, 1)
|
assert.equal(count, 1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -169,7 +169,7 @@ describe('Mastodon APIs', () => {
|
||||||
globalThis.fetch = async (input: RequestInfo, data: any) => {
|
globalThis.fetch = async (input: RequestInfo, data: any) => {
|
||||||
if (input === 'https://api.cloudflare.com/client/v4/accounts/CF_ACCOUNT_ID/images/v1') {
|
if (input === 'https://api.cloudflare.com/client/v4/accounts/CF_ACCOUNT_ID/images/v1') {
|
||||||
assert.equal(data.method, 'POST')
|
assert.equal(data.method, 'POST')
|
||||||
const file: any = data.body.get('file')
|
const file: any = (data.body as { get: (str: string) => any }).get('file')
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
success: true,
|
success: true,
|
||||||
|
@ -333,7 +333,7 @@ describe('Mastodon APIs', () => {
|
||||||
assert.equal(data.following_count, 2)
|
assert.equal(data.following_count, 2)
|
||||||
assert.equal(data.statuses_count, 1)
|
assert.equal(data.statuses_count, 1)
|
||||||
assert(isUrlValid(data.url))
|
assert(isUrlValid(data.url))
|
||||||
assert(data.url.includes(domain))
|
assert((data.url as string).includes(domain))
|
||||||
})
|
})
|
||||||
|
|
||||||
test('get local actor statuses', async () => {
|
test('get local actor statuses', async () => {
|
||||||
|
@ -561,7 +561,7 @@ describe('Mastodon APIs', () => {
|
||||||
|
|
||||||
// Statuses were imported locally and once was a reblog of an already
|
// Statuses were imported locally and once was a reblog of an already
|
||||||
// existing local object.
|
// existing local object.
|
||||||
const row = await db.prepare(`SELECT count(*) as count FROM objects`).first()
|
const row: { count: number } = await db.prepare(`SELECT count(*) as count FROM objects`).first()
|
||||||
assert.equal(row.count, 2)
|
assert.equal(row.count, 2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -640,7 +640,7 @@ describe('Mastodon APIs', () => {
|
||||||
const db = await makeDB()
|
const db = await makeDB()
|
||||||
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
|
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
|
||||||
|
|
||||||
globalThis.fetch = async (input: any) => {
|
globalThis.fetch = async (input: RequestInfo) => {
|
||||||
if (input.toString() === 'https://example.com/.well-known/webfinger?resource=acct%3Asven%40example.com') {
|
if (input.toString() === 'https://example.com/.well-known/webfinger?resource=acct%3Asven%40example.com') {
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
@ -718,7 +718,7 @@ describe('Mastodon APIs', () => {
|
||||||
|
|
||||||
test('get local actor followers', async () => {
|
test('get local actor followers', async () => {
|
||||||
globalThis.fetch = async (input: any) => {
|
globalThis.fetch = async (input: any) => {
|
||||||
if (input.toString() === 'https://' + domain + '/ap/users/sven2') {
|
if ((input as object).toString() === 'https://' + domain + '/ap/users/sven2') {
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
id: 'https://example.com/actor',
|
id: 'https://example.com/actor',
|
||||||
|
@ -746,7 +746,7 @@ describe('Mastodon APIs', () => {
|
||||||
|
|
||||||
test('get local actor following', async () => {
|
test('get local actor following', async () => {
|
||||||
globalThis.fetch = async (input: any) => {
|
globalThis.fetch = async (input: any) => {
|
||||||
if (input.toString() === 'https://' + domain + '/ap/users/sven2') {
|
if ((input as object).toString() === 'https://' + domain + '/ap/users/sven2') {
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
id: 'https://example.com/foo',
|
id: 'https://example.com/foo',
|
||||||
|
@ -776,7 +776,7 @@ describe('Mastodon APIs', () => {
|
||||||
const db = await makeDB()
|
const db = await makeDB()
|
||||||
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
|
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
|
||||||
|
|
||||||
globalThis.fetch = async (input: any) => {
|
globalThis.fetch = async (input: RequestInfo) => {
|
||||||
if (input.toString() === 'https://example.com/.well-known/webfinger?resource=acct%3Asven%40example.com') {
|
if (input.toString() === 'https://example.com/.well-known/webfinger?resource=acct%3Asven%40example.com') {
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
@ -952,11 +952,9 @@ describe('Mastodon APIs', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
receivedActivity = null
|
receivedActivity = null
|
||||||
|
|
||||||
globalThis.fetch = async (input: any) => {
|
globalThis.fetch = async (input: RequestInfo) => {
|
||||||
if (
|
const request = new Request(input)
|
||||||
input.toString() ===
|
if (request.url === 'https://' + domain + '/.well-known/webfinger?resource=acct%3Aactor%40' + domain + '') {
|
||||||
'https://' + domain + '/.well-known/webfinger?resource=acct%3Aactor%40' + domain + ''
|
|
||||||
) {
|
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
links: [
|
links: [
|
||||||
|
@ -970,7 +968,7 @@ describe('Mastodon APIs', () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.toString() === 'https://social.com/sven') {
|
if (request.url === 'https://social.com/sven') {
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
id: `https://${domain}/ap/users/actor`,
|
id: `https://${domain}/ap/users/actor`,
|
||||||
|
@ -980,13 +978,13 @@ describe('Mastodon APIs', () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.url === 'https://example.com/inbox') {
|
if (request.url === 'https://example.com/inbox') {
|
||||||
assert.equal(input.method, 'POST')
|
assert.equal(request.method, 'POST')
|
||||||
receivedActivity = await input.json()
|
receivedActivity = await request.json()
|
||||||
return new Response('')
|
return new Response('')
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('unexpected request to ' + input)
|
throw new Error('unexpected request to ' + request.url)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1005,7 +1003,11 @@ describe('Mastodon APIs', () => {
|
||||||
assert(receivedActivity)
|
assert(receivedActivity)
|
||||||
assert.equal(receivedActivity.type, 'Follow')
|
assert.equal(receivedActivity.type, 'Follow')
|
||||||
|
|
||||||
const row = await db
|
const row: {
|
||||||
|
target_actor_acct: string
|
||||||
|
target_actor_id: string
|
||||||
|
state: string
|
||||||
|
} = await db
|
||||||
.prepare(`SELECT target_actor_acct, target_actor_id, state FROM actor_following WHERE actor_id=?`)
|
.prepare(`SELECT target_actor_acct, target_actor_id, state FROM actor_following WHERE actor_id=?`)
|
||||||
.bind(actor.id.toString())
|
.bind(actor.id.toString())
|
||||||
.first()
|
.first()
|
||||||
|
@ -1036,7 +1038,7 @@ describe('Mastodon APIs', () => {
|
||||||
const row = await db
|
const row = await db
|
||||||
.prepare(`SELECT count(*) as count FROM actor_following WHERE actor_id=?`)
|
.prepare(`SELECT count(*) as count FROM actor_following WHERE actor_id=?`)
|
||||||
.bind(actor.id.toString())
|
.bind(actor.id.toString())
|
||||||
.first()
|
.first<{ count: number }>()
|
||||||
assert(row)
|
assert(row)
|
||||||
assert.equal(row.count, 0)
|
assert.equal(row.count, 0)
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,8 +12,9 @@ const domain = 'cloudflare.com'
|
||||||
describe('Mastodon APIs', () => {
|
describe('Mastodon APIs', () => {
|
||||||
describe('media', () => {
|
describe('media', () => {
|
||||||
test('upload image creates object', async () => {
|
test('upload image creates object', async () => {
|
||||||
globalThis.fetch = async (input: any) => {
|
globalThis.fetch = async (input: RequestInfo) => {
|
||||||
if (input.url.toString() === 'https://api.cloudflare.com/client/v4/accounts/testaccountid/images/v1') {
|
const request = new Request(input)
|
||||||
|
if (request.url.toString() === 'https://api.cloudflare.com/client/v4/accounts/testaccountid/images/v1') {
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
success: true,
|
success: true,
|
||||||
|
@ -24,7 +25,7 @@ describe('Mastodon APIs', () => {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
throw new Error('unexpected request to ' + input.url)
|
throw new Error('unexpected request to ' + request.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = await makeDB()
|
const db = await makeDB()
|
||||||
|
|
|
@ -117,7 +117,7 @@ describe('Mastodon APIs', () => {
|
||||||
|
|
||||||
globalThis.fetch = async (input: RequestInfo, data: any) => {
|
globalThis.fetch = async (input: RequestInfo, data: any) => {
|
||||||
if (input === 'https://push.com') {
|
if (input === 'https://push.com') {
|
||||||
assert(data.headers['Authorization'].includes('WebPush'))
|
assert((data.headers['Authorization'] as string).includes('WebPush'))
|
||||||
|
|
||||||
const cryptoKeyHeader = parseCryptoKey(data.headers['Crypto-Key'])
|
const cryptoKeyHeader = parseCryptoKey(data.headers['Crypto-Key'])
|
||||||
assert(cryptoKeyHeader.dh)
|
assert(cryptoKeyHeader.dh)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import * as oauth_token from 'wildebeest/functions/oauth/token'
|
||||||
import { isUrlValid, makeDB, assertCORS, assertJSON, createTestClient } from '../utils'
|
import { isUrlValid, makeDB, assertCORS, assertJSON, createTestClient } from '../utils'
|
||||||
import { TEST_JWT, ACCESS_CERTS } from '../test-data'
|
import { TEST_JWT, ACCESS_CERTS } from '../test-data'
|
||||||
import { strict as assert } from 'node:assert/strict'
|
import { strict as assert } from 'node:assert/strict'
|
||||||
|
import { Actor } from 'wildebeest/backend/src/activitypub/actors'
|
||||||
|
|
||||||
const userKEK = 'test_kek3'
|
const userKEK = 'test_kek3'
|
||||||
const accessDomain = 'access.com'
|
const accessDomain = 'access.com'
|
||||||
|
@ -100,7 +101,7 @@ describe('Mastodon APIs', () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
// actor isn't created yet
|
// actor isn't created yet
|
||||||
const { count } = await db.prepare('SELECT count(*) as count FROM actors').first()
|
const { count } = await db.prepare('SELECT count(*) as count FROM actors').first<{ count: number }>()
|
||||||
assert.equal(count, 0)
|
assert.equal(count, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -126,7 +127,9 @@ describe('Mastodon APIs', () => {
|
||||||
const location = res.headers.get('location')
|
const location = res.headers.get('location')
|
||||||
assert.equal(location, 'https://redirect.com/a')
|
assert.equal(location, 'https://redirect.com/a')
|
||||||
|
|
||||||
const actor = await db.prepare('SELECT * FROM actors').first()
|
const actor = await db
|
||||||
|
.prepare('SELECT * FROM actors')
|
||||||
|
.first<{ properties: string; email: string; id: string } & Actor>()
|
||||||
const properties = JSON.parse(actor.properties)
|
const properties = JSON.parse(actor.properties)
|
||||||
|
|
||||||
assert.equal(actor.email, 'a@cloudflare.com')
|
assert.equal(actor.email, 'a@cloudflare.com')
|
||||||
|
@ -134,7 +137,7 @@ describe('Mastodon APIs', () => {
|
||||||
assert.equal(properties.name, 'name')
|
assert.equal(properties.name, 'name')
|
||||||
assert(isUrlValid(actor.id))
|
assert(isUrlValid(actor.id))
|
||||||
// ensure that we generate a correct key pairs for the user
|
// ensure that we generate a correct key pairs for the user
|
||||||
assert((await getSigningKey(userKEK, db, actor)) instanceof CryptoKey)
|
assert((await getSigningKey(userKEK, db, actor as Actor)) instanceof CryptoKey)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('token error on unknown client', async () => {
|
test('token error on unknown client', async () => {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { strict as assert } from 'node:assert/strict'
|
||||||
import { insertReply } from 'wildebeest/backend/src/mastodon/reply'
|
import { insertReply } from 'wildebeest/backend/src/mastodon/reply'
|
||||||
import { getMentions } from 'wildebeest/backend/src/mastodon/status'
|
import { getMentions } from 'wildebeest/backend/src/mastodon/status'
|
||||||
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
|
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
|
||||||
import { createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note'
|
import { createPublicNote, Note } from 'wildebeest/backend/src/activitypub/objects/note'
|
||||||
import { createImage } from 'wildebeest/backend/src/activitypub/objects/image'
|
import { createImage } from 'wildebeest/backend/src/activitypub/objects/image'
|
||||||
import * as statuses from 'wildebeest/functions/api/v1/statuses'
|
import * as statuses from 'wildebeest/functions/api/v1/statuses'
|
||||||
import * as statuses_get from 'wildebeest/functions/api/v1/statuses/[id]'
|
import * as statuses_get from 'wildebeest/functions/api/v1/statuses/[id]'
|
||||||
|
@ -16,6 +16,7 @@ import { isUrlValid, makeDB, assertJSON, streamToArrayBuffer, makeQueue, makeCac
|
||||||
import * as activities from 'wildebeest/backend/src/activitypub/activities'
|
import * as activities from 'wildebeest/backend/src/activitypub/activities'
|
||||||
import { addFollowing, acceptFollowing } from 'wildebeest/backend/src/mastodon/follow'
|
import { addFollowing, acceptFollowing } from 'wildebeest/backend/src/mastodon/follow'
|
||||||
import { MessageType } from 'wildebeest/backend/src/types/queue'
|
import { MessageType } from 'wildebeest/backend/src/types/queue'
|
||||||
|
import { MastodonStatus } from 'wildebeest/backend/src/types'
|
||||||
|
|
||||||
const userKEK = 'test_kek4'
|
const userKEK = 'test_kek4'
|
||||||
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
||||||
|
@ -60,9 +61,9 @@ describe('Mastodon APIs', () => {
|
||||||
assert.equal(res.status, 200)
|
assert.equal(res.status, 200)
|
||||||
assertJSON(res)
|
assertJSON(res)
|
||||||
|
|
||||||
const data = await res.json<any>()
|
const data = await res.json<MastodonStatus>()
|
||||||
assert(data.uri.includes('example.com'))
|
assert((data.uri as unknown as string).includes('example.com'))
|
||||||
assert(data.uri.includes(data.id))
|
assert((data.uri as unknown as string).includes(data.id))
|
||||||
// Required fields from https://github.com/mastodon/mastodon-android/blob/master/mastodon/src/main/java/org/joinmastodon/android/model/Status.java
|
// Required fields from https://github.com/mastodon/mastodon-android/blob/master/mastodon/src/main/java/org/joinmastodon/android/model/Status.java
|
||||||
assert(data.created_at !== undefined)
|
assert(data.created_at !== undefined)
|
||||||
assert(data.account !== undefined)
|
assert(data.account !== undefined)
|
||||||
|
@ -84,7 +85,7 @@ describe('Mastodon APIs', () => {
|
||||||
FROM objects
|
FROM objects
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
.first()
|
.first<{ content: string; original_actor_id: URL; original_object_id: unknown }>()
|
||||||
assert.equal(row.content, 'my status <p>evil</p>') // note the sanitization
|
assert.equal(row.content, 'my status <p>evil</p>') // note the sanitization
|
||||||
assert.equal(row.original_actor_id.toString(), actor.id.toString())
|
assert.equal(row.original_actor_id.toString(), actor.id.toString())
|
||||||
assert.equal(row.original_object_id, null)
|
assert.equal(row.original_object_id, null)
|
||||||
|
@ -138,7 +139,7 @@ describe('Mastodon APIs', () => {
|
||||||
const res = await statuses.handleRequest(req, db, connectedActor, userKEK, queue, cache)
|
const res = await statuses.handleRequest(req, db, connectedActor, userKEK, queue, cache)
|
||||||
assert.equal(res.status, 200)
|
assert.equal(res.status, 200)
|
||||||
|
|
||||||
const row = await db.prepare(`SELECT count(*) as count FROM outbox_objects`).first()
|
const row = await db.prepare(`SELECT count(*) as count FROM outbox_objects`).first<{ count: number }>()
|
||||||
assert.equal(row.count, 1)
|
assert.equal(row.count, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -180,7 +181,7 @@ describe('Mastodon APIs', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test('create new status with mention delivers ActivityPub Note', async () => {
|
test('create new status with mention delivers ActivityPub Note', async () => {
|
||||||
let deliveredNote: any = null
|
let deliveredNote: Note | null = null
|
||||||
|
|
||||||
globalThis.fetch = async (input: RequestInfo, data: any) => {
|
globalThis.fetch = async (input: RequestInfo, data: any) => {
|
||||||
if (input.toString() === 'https://remote.com/.well-known/webfinger?resource=acct%3Asven%40remote.com') {
|
if (input.toString() === 'https://remote.com/.well-known/webfinger?resource=acct%3Asven%40remote.com') {
|
||||||
|
@ -246,12 +247,15 @@ describe('Mastodon APIs', () => {
|
||||||
assert.equal(res.status, 200)
|
assert.equal(res.status, 200)
|
||||||
|
|
||||||
assert(deliveredNote)
|
assert(deliveredNote)
|
||||||
assert.equal(deliveredNote.type, 'Create')
|
assert.equal((deliveredNote as { type: string }).type, 'Create')
|
||||||
assert.equal(deliveredNote.actor, `https://${domain}/ap/users/sven`)
|
assert.equal((deliveredNote as { actor: string }).actor, `https://${domain}/ap/users/sven`)
|
||||||
assert.equal(deliveredNote.object.attributedTo, `https://${domain}/ap/users/sven`)
|
assert.equal(
|
||||||
assert.equal(deliveredNote.object.type, 'Note')
|
(deliveredNote as { object: { attributedTo: string } }).object.attributedTo,
|
||||||
assert(deliveredNote.object.to.includes(activities.PUBLIC_GROUP))
|
`https://${domain}/ap/users/sven`
|
||||||
assert.equal(deliveredNote.object.cc.length, 1)
|
)
|
||||||
|
assert.equal((deliveredNote as { object: { type: string } }).object.type, 'Note')
|
||||||
|
assert((deliveredNote as { object: { to: string[] } }).object.to.includes(activities.PUBLIC_GROUP))
|
||||||
|
assert.equal((deliveredNote as { object: { cc: string[] } }).object.cc.length, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('create new status with image', async () => {
|
test('create new status with image', async () => {
|
||||||
|
@ -302,15 +306,16 @@ describe('Mastodon APIs', () => {
|
||||||
)
|
)
|
||||||
.run()
|
.run()
|
||||||
|
|
||||||
globalThis.fetch = async (input: any) => {
|
globalThis.fetch = async (input: RequestInfo) => {
|
||||||
if (input.url === actor.id.toString() + '/inbox') {
|
const request = new Request(input)
|
||||||
assert.equal(input.method, 'POST')
|
if (request.url === actor.id.toString() + '/inbox') {
|
||||||
const body = await input.json()
|
assert.equal(request.method, 'POST')
|
||||||
|
const body = await request.json()
|
||||||
deliveredActivity = body
|
deliveredActivity = body
|
||||||
return new Response()
|
return new Response()
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('unexpected request to ' + JSON.stringify(input))
|
throw new Error('unexpected request to ' + request.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
const connectedActor: any = actor
|
const connectedActor: any = actor
|
||||||
|
@ -336,7 +341,7 @@ describe('Mastodon APIs', () => {
|
||||||
const data = await res.json<any>()
|
const data = await res.json<any>()
|
||||||
assert.equal(data.favourited, true)
|
assert.equal(data.favourited, true)
|
||||||
|
|
||||||
const row = await db.prepare(`SELECT * FROM actor_favourites`).first()
|
const row = await db.prepare(`SELECT * FROM actor_favourites`).first<{ actor_id: string; object_id: string }>()
|
||||||
assert.equal(row.actor_id, actor.id.toString())
|
assert.equal(row.actor_id, actor.id.toString())
|
||||||
assert.equal(row.object_id, note.id.toString())
|
assert.equal(row.object_id, note.id.toString())
|
||||||
})
|
})
|
||||||
|
@ -469,7 +474,7 @@ describe('Mastodon APIs', () => {
|
||||||
const data = await res.json<any>()
|
const data = await res.json<any>()
|
||||||
assert.equal(data.reblogged, true)
|
assert.equal(data.reblogged, true)
|
||||||
|
|
||||||
const row = await db.prepare(`SELECT * FROM actor_reblogs`).first()
|
const row = await db.prepare(`SELECT * FROM actor_reblogs`).first<{ actor_id: string; object_id: string }>()
|
||||||
assert.equal(row.actor_id, actor.id.toString())
|
assert.equal(row.actor_id, actor.id.toString())
|
||||||
assert.equal(row.object_id, note.id.toString())
|
assert.equal(row.object_id, note.id.toString())
|
||||||
})
|
})
|
||||||
|
@ -486,7 +491,7 @@ describe('Mastodon APIs', () => {
|
||||||
const res = await statuses_reblog.handleRequest(db, note.mastodonId!, connectedActor, userKEK, queue, domain)
|
const res = await statuses_reblog.handleRequest(db, note.mastodonId!, connectedActor, userKEK, queue, domain)
|
||||||
assert.equal(res.status, 200)
|
assert.equal(res.status, 200)
|
||||||
|
|
||||||
const row = await db.prepare(`SELECT * FROM outbox_objects`).first()
|
const row = await db.prepare(`SELECT * FROM outbox_objects`).first<{ actor_id: string; object_id: string }>()
|
||||||
assert.equal(row.actor_id, actor.id.toString())
|
assert.equal(row.actor_id, actor.id.toString())
|
||||||
assert.equal(row.object_id, note.id.toString())
|
assert.equal(row.object_id, note.id.toString())
|
||||||
})
|
})
|
||||||
|
@ -513,15 +518,16 @@ describe('Mastodon APIs', () => {
|
||||||
)
|
)
|
||||||
.run()
|
.run()
|
||||||
|
|
||||||
globalThis.fetch = async (input: any) => {
|
globalThis.fetch = async (input: RequestInfo) => {
|
||||||
if (input.url === 'https://cloudflare.com/ap/users/sven/inbox') {
|
const request = new Request(input)
|
||||||
assert.equal(input.method, 'POST')
|
if (request.url === 'https://cloudflare.com/ap/users/sven/inbox') {
|
||||||
const body = await input.json()
|
assert.equal(request.method, 'POST')
|
||||||
|
const body = await request.json()
|
||||||
deliveredActivity = body
|
deliveredActivity = body
|
||||||
return new Response()
|
return new Response()
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('unexpected request to ' + JSON.stringify(input))
|
throw new Error('unexpected request to ' + request.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
const connectedActor: any = actor
|
const connectedActor: any = actor
|
||||||
|
@ -588,13 +594,16 @@ describe('Mastodon APIs', () => {
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
.bind(data.id)
|
.bind(data.id)
|
||||||
.first()
|
.first<{ inReplyTo: string }>()
|
||||||
assert(row !== undefined)
|
assert(row !== undefined)
|
||||||
assert.equal(row.inReplyTo, note.id.toString())
|
assert.equal(row.inReplyTo, note.id.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const row = await db.prepare('select * from actor_replies').first()
|
const row = await db.prepare('select * from actor_replies').first<{
|
||||||
|
actor_id: string
|
||||||
|
in_reply_to_object_id: string
|
||||||
|
}>()
|
||||||
assert(row !== undefined)
|
assert(row !== undefined)
|
||||||
assert.equal(row.actor_id, actor.id.toString())
|
assert.equal(row.actor_id, actor.id.toString())
|
||||||
assert.equal(row.in_reply_to_object_id, note.id.toString())
|
assert.equal(row.in_reply_to_object_id, note.id.toString())
|
||||||
|
@ -619,7 +628,7 @@ describe('Mastodon APIs', () => {
|
||||||
|
|
||||||
const res = await statuses.handleRequest(req, db, actor, userKEK, queue, cache)
|
const res = await statuses.handleRequest(req, db, actor, userKEK, queue, cache)
|
||||||
assert.equal(res.status, 400)
|
assert.equal(res.status, 400)
|
||||||
const data = await res.json<any>()
|
const data = await res.json<{ error: string }>()
|
||||||
assert(data.error.includes('Limit exceeded'))
|
assert(data.error.includes('Limit exceeded'))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -644,7 +653,7 @@ describe('Mastodon APIs', () => {
|
||||||
|
|
||||||
const res = await statuses.handleRequest(req, db, actor, userKEK, queue, cache)
|
const res = await statuses.handleRequest(req, db, actor, userKEK, queue, cache)
|
||||||
assert.equal(res.status, 400)
|
assert.equal(res.status, 400)
|
||||||
const data = await res.json<any>()
|
const data = await res.json<{ error: string }>()
|
||||||
assert(data.error.includes('Limit exceeded'))
|
assert(data.error.includes('Limit exceeded'))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -18,7 +18,7 @@ export function isUrlValid(s: string) {
|
||||||
return url.protocol === 'https:'
|
return url.protocol === 'https:'
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function makeDB(): Promise<any> {
|
export async function makeDB(): Promise<D1Database> {
|
||||||
const db = new Database(':memory:')
|
const db = new Database(':memory:')
|
||||||
const db2 = new BetaDatabase(db)!
|
const db2 = new BetaDatabase(db)!
|
||||||
|
|
||||||
|
@ -27,10 +27,10 @@ export async function makeDB(): Promise<any> {
|
||||||
|
|
||||||
for (let i = 0, len = migrations.length; i < len; i++) {
|
for (let i = 0, len = migrations.length; i < len; i++) {
|
||||||
const content = await fs.readFile(path.join('migrations', migrations[i]), 'utf-8')
|
const content = await fs.readFile(path.join('migrations', migrations[i]), 'utf-8')
|
||||||
await db.exec(content)
|
db.exec(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
return db2
|
return db2 as unknown as D1Database
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assertCORS(response: Response) {
|
export function assertCORS(response: Response) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ContextData } from 'wildebeest/backend/src/types/context'
|
import { ContextData } from 'wildebeest/backend/src/types/context'
|
||||||
import { cors } from 'wildebeest/backend/src/utils/cors'
|
import { cors } from 'wildebeest/backend/src/utils/cors'
|
||||||
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
|
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
|
||||||
import { Env } from 'wildebeest/backend/src/types/env'
|
import type { Env } from 'wildebeest/backend/src/types/env'
|
||||||
import { createClient } from 'wildebeest/backend/src/mastodon/client'
|
import { createClient } from 'wildebeest/backend/src/mastodon/client'
|
||||||
import { VAPIDPublicKey } from 'wildebeest/backend/src/mastodon/subscription'
|
import { VAPIDPublicKey } from 'wildebeest/backend/src/mastodon/subscription'
|
||||||
import { getVAPIDKeys } from 'wildebeest/backend/src/config'
|
import { getVAPIDKeys } from 'wildebeest/backend/src/config'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// https://docs.joinmastodon.org/methods/notifications/#get-one
|
// https://docs.joinmastodon.org/methods/notifications/#get-one
|
||||||
|
|
||||||
import type { Notification } from 'wildebeest/backend/src/types/notification'
|
import type { Notification, NotificationsQueryResult } from 'wildebeest/backend/src/types/notification'
|
||||||
import { urlToHandle } from 'wildebeest/backend/src/utils/handle'
|
import { urlToHandle } from 'wildebeest/backend/src/utils/handle'
|
||||||
import { getPersonById } from 'wildebeest/backend/src/activitypub/actors'
|
import { getPersonById } from 'wildebeest/backend/src/activitypub/actors'
|
||||||
import { loadExternalMastodonAccount } from 'wildebeest/backend/src/mastodon/account'
|
import { loadExternalMastodonAccount } from 'wildebeest/backend/src/mastodon/account'
|
||||||
|
@ -36,7 +36,7 @@ export async function handleRequest(
|
||||||
WHERE actor_notifications.id=? AND actor_notifications.actor_id=?
|
WHERE actor_notifications.id=? AND actor_notifications.actor_id=?
|
||||||
`
|
`
|
||||||
|
|
||||||
const row: any = await db.prepare(query).bind(id, connectedActor.id.toString()).first()
|
const row = await db.prepare(query).bind(id, connectedActor.id.toString()).first<NotificationsQueryResult>()
|
||||||
|
|
||||||
const from_actor_id = new URL(row.from_actor_id)
|
const from_actor_id = new URL(row.from_actor_id)
|
||||||
const fromActor = await getPersonById(db, from_actor_id)
|
const fromActor = await getPersonById(db, from_actor_id)
|
||||||
|
|
|
@ -6,7 +6,7 @@ import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
|
||||||
import { createSubscription, getSubscription } from 'wildebeest/backend/src/mastodon/subscription'
|
import { createSubscription, getSubscription } from 'wildebeest/backend/src/mastodon/subscription'
|
||||||
import type { CreateRequest } from 'wildebeest/backend/src/mastodon/subscription'
|
import type { CreateRequest } from 'wildebeest/backend/src/mastodon/subscription'
|
||||||
import { ContextData } from 'wildebeest/backend/src/types/context'
|
import { ContextData } from 'wildebeest/backend/src/types/context'
|
||||||
import { Env } from 'wildebeest/backend/src/types/env'
|
import type { Env } from 'wildebeest/backend/src/types/env'
|
||||||
import * as errors from 'wildebeest/backend/src/errors'
|
import * as errors from 'wildebeest/backend/src/errors'
|
||||||
import { VAPIDPublicKey } from 'wildebeest/backend/src/mastodon/subscription'
|
import { VAPIDPublicKey } from 'wildebeest/backend/src/mastodon/subscription'
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue