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',
|
||||
'no-console': '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
|
||||
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-argument': 'off',
|
||||
'@typescript-eslint/no-unsafe-call': 'off',
|
||||
'@typescript-eslint/no-unsafe-member-access': '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/no-misused-promises': 'off',
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-inferrable-types': 'off',
|
||||
|
|
|
@ -121,7 +121,7 @@ export const generateValidator =
|
|||
|
||||
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.
|
||||
// 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/`, '')
|
||||
}
|
||||
|
||||
export function makeGetObjectAsId(activity: Activity): Function {
|
||||
export function makeGetObjectAsId(activity: Activity) {
|
||||
return () => {
|
||||
let url: any = null
|
||||
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 () => {
|
||||
let url: any = null
|
||||
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)
|
||||
}
|
||||
|
||||
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 = `
|
||||
SELECT *
|
||||
FROM objects
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import type { Env } from 'wildebeest/consumer/src'
|
||||
|
||||
const CACHE_DO_NAME = 'cachev1'
|
||||
|
||||
export interface Cache {
|
||||
|
@ -5,7 +7,7 @@ export interface Cache {
|
|||
put<T>(key: string, value: T): Promise<void>
|
||||
}
|
||||
|
||||
export function cacheFromEnv(env: any): Cache {
|
||||
export function cacheFromEnv(env: Env): Cache {
|
||||
return {
|
||||
async get<T>(key: string): Promise<T | null> {
|
||||
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 { WebPushResult } from 'wildebeest/backend/src/webpush/webpushinfos'
|
||||
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 type { Cache } from 'wildebeest/backend/src/cache'
|
||||
|
||||
|
@ -24,10 +28,10 @@ export async function createNotification(
|
|||
VALUES (?, ?, ?, ?)
|
||||
RETURNING id
|
||||
`
|
||||
const row: { id: string } = await db
|
||||
const row = await db
|
||||
.prepare(query)
|
||||
.bind(type, actor.id.toString(), fromActor.id.toString(), obj.id.toString())
|
||||
.first()
|
||||
.first<{ id: string }>()
|
||||
return row.id
|
||||
}
|
||||
|
||||
|
@ -39,7 +43,7 @@ export async function insertFollowNotification(db: D1Database, actor: Actor, fro
|
|||
VALUES (?, ?, ?)
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -187,7 +191,7 @@ export async function getNotifications(db: D1Database, actor: Actor, domain: str
|
|||
`
|
||||
|
||||
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) {
|
||||
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++) {
|
||||
const result = results[i] as any
|
||||
const result = results[i]
|
||||
const properties = JSON.parse(result.properties)
|
||||
const notifFromActorId = new URL(result.notif_from_actor_id)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { MastodonAccount } from 'wildebeest/backend/src/types/account'
|
||||
import type { MastodonStatus } from 'wildebeest/backend/src/types/status'
|
||||
import type { ObjectsRow } from './objects'
|
||||
|
||||
export type NotificationType =
|
||||
| 'mention'
|
||||
|
@ -20,3 +21,12 @@ export type Notification = {
|
|||
account: MastodonAccount
|
||||
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.
|
||||
|
||||
const key2 = key.replace('[]', '')
|
||||
if (out[key2] === undefined) {
|
||||
out[key2] = []
|
||||
}
|
||||
out[key2].push(value)
|
||||
const outArr: unknown[] = (out[key2] ??= [])
|
||||
outArr.push(value)
|
||||
} else {
|
||||
out[key] = value
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// @ts-nocheck
|
||||
// Copyright 2012 Joyent, Inc. All rights reserved.
|
||||
|
||||
import { HEADER, HttpSignatureError, InvalidAlgorithmError, validateAlgorithm } from './utils'
|
||||
|
@ -61,6 +60,9 @@ export type ParsedSignature = {
|
|||
keyId: string
|
||||
signingString: string
|
||||
algorithm: string
|
||||
scheme: string
|
||||
params: Record<string, string | string[] | number>
|
||||
opaque: string
|
||||
}
|
||||
|
||||
///--- Exported API
|
||||
|
@ -141,10 +143,14 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
|
|||
let tmpName = ''
|
||||
let tmpValue = ''
|
||||
|
||||
const parsed = {
|
||||
const parsed: ParsedSignature = {
|
||||
scheme: authz === request.headers.get(HEADER.SIG) ? 'Signature' : '',
|
||||
params: {},
|
||||
signingString: '',
|
||||
signature: '',
|
||||
keyId: '',
|
||||
algorithm: '',
|
||||
opaque: '',
|
||||
}
|
||||
|
||||
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 (request.headers.has('x-date')) {
|
||||
parsed.params.headers = ['x-date']
|
||||
parsedHeaders = ['x-date']
|
||||
} else {
|
||||
parsed.params.headers = ['date']
|
||||
parsedHeaders = ['date']
|
||||
}
|
||||
} else {
|
||||
parsed.params.headers = parsed.params.headers.split(' ')
|
||||
} else if (typeof parsed.params.headers === 'string') {
|
||||
parsedHeaders = parsed.params.headers.split(' ')
|
||||
}
|
||||
|
||||
// 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 (['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')
|
||||
}
|
||||
|
||||
// Check the algorithm against the official list
|
||||
try {
|
||||
validateAlgorithm(parsed.params.algorithm, 'rsa')
|
||||
validateAlgorithm(parsed.params.algorithm as string, 'rsa')
|
||||
} catch (e) {
|
||||
if (e instanceof InvalidAlgorithmError)
|
||||
throw new InvalidParamsError(parsed.params.algorithm + ' is not ' + 'supported')
|
||||
|
@ -267,9 +275,9 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
|
|||
}
|
||||
|
||||
// Build the signingString
|
||||
for (i = 0; i < parsed.params.headers.length; i++) {
|
||||
const h = parsed.params.headers[i].toLowerCase()
|
||||
parsed.params.headers[i] = h
|
||||
for (i = 0; i < parsedHeaders.length; i++) {
|
||||
const h = parsedHeaders[i].toLowerCase()
|
||||
parsedHeaders[i] = h
|
||||
|
||||
if (h === 'request-line') {
|
||||
if (!options.strict) {
|
||||
|
@ -292,6 +300,7 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
|
|||
} else if (h === '(opaque)') {
|
||||
const opaque = parsed.params.opaque
|
||||
if (opaque === undefined) {
|
||||
//@ts-expect-error -- authzHeaderName doesn't exist TOFIX
|
||||
throw new MissingHeaderError('opaque param was not in the ' + authzHeaderName + ' header')
|
||||
}
|
||||
parsed.signingString += '(opaque): ' + opaque
|
||||
|
@ -305,17 +314,17 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
|
|||
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
|
||||
let date
|
||||
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')) {
|
||||
date = new Date(request.headers.get('x-date') as string)
|
||||
} else {
|
||||
date = new Date(request.headers.date)
|
||||
date = new Date(request.headers.get('date') as string)
|
||||
}
|
||||
const now = new Date()
|
||||
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)
|
||||
if (skew > options.clockSkew) {
|
||||
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
|
||||
if (expiredSince > options.clockSkew) {
|
||||
throw new ExpiredRequestError(
|
||||
|
@ -352,14 +361,17 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
|
|||
headers.forEach(function (hdr) {
|
||||
// Remember that we already checked any headers in the params
|
||||
// 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')
|
||||
}
|
||||
})
|
||||
|
||||
parsed.params.algorithm = parsed.params.algorithm.toLowerCase()
|
||||
parsed.algorithm = parsed.params.algorithm.toUpperCase()
|
||||
parsed.keyId = parsed.params.keyId
|
||||
parsed.opaque = parsed.params.opaque
|
||||
parsed.signature = parsed.params.signature
|
||||
const algorithm = parsed.params.algorithm as string
|
||||
parsed.params.algorithm = algorithm.toLowerCase()
|
||||
parsed.algorithm = algorithm.toUpperCase()
|
||||
parsed.keyId = parsed.params.keyId as string
|
||||
parsed.opaque = parsed.params.opaque as string
|
||||
parsed.signature = parsed.params.signature as string
|
||||
parsed.params.headers = parsedHeaders
|
||||
return parsed
|
||||
}
|
||||
|
|
|
@ -21,16 +21,17 @@ describe('ActivityPub', () => {
|
|||
beforeEach(() => {
|
||||
receivedActivity = null
|
||||
|
||||
globalThis.fetch = async (input: any) => {
|
||||
if (input.url === `https://${domain}/ap/users/sven2/inbox`) {
|
||||
assert.equal(input.method, 'POST')
|
||||
const data = await input.json()
|
||||
globalThis.fetch = async (input: RequestInfo) => {
|
||||
const request = new Request(input)
|
||||
if (request.url === `https://${domain}/ap/users/sven2/inbox`) {
|
||||
assert.equal(request.method, 'POST')
|
||||
const data = await request.json()
|
||||
receivedActivity = data
|
||||
console.log({ receivedActivity })
|
||||
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
|
||||
.prepare(`SELECT target_actor_id, state FROM actor_following WHERE actor_id=?`)
|
||||
.bind(actor2.id.toString())
|
||||
.first()
|
||||
.first<{
|
||||
target_actor_id: object
|
||||
state: string
|
||||
}>()
|
||||
assert(row)
|
||||
assert.equal(row.target_actor_id.toString(), actor.id.toString())
|
||||
assert.equal(row.state, 'accepted')
|
||||
|
||||
assert(receivedActivity)
|
||||
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.type, activity.type)
|
||||
})
|
||||
|
@ -144,7 +148,11 @@ describe('ActivityPub', () => {
|
|||
|
||||
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.actor_id.toString(), actor.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)
|
||||
|
||||
// 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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -6,6 +6,7 @@ import { cacheObject, getObjectById } from 'wildebeest/backend/src/activitypub/o
|
|||
import { addFollowing } from 'wildebeest/backend/src/mastodon/follow'
|
||||
import * as activityHandler from 'wildebeest/backend/src/activitypub/activities/handle'
|
||||
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
|
||||
import { ObjectsRow } from 'wildebeest/backend/src/types/objects'
|
||||
|
||||
const adminEmail = 'admin@example.com'
|
||||
const domain = 'cloudflare.com'
|
||||
|
@ -29,7 +30,10 @@ describe('ActivityPub', () => {
|
|||
}
|
||||
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.object_id.toString(), note.id.toString())
|
||||
})
|
||||
|
@ -48,7 +52,11 @@ describe('ActivityPub', () => {
|
|||
}
|
||||
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.equal(entry.type, 'reblog')
|
||||
assert.equal(entry.actor_id.toString(), actorA.id.toString())
|
||||
|
@ -71,7 +79,7 @@ describe('ActivityPub', () => {
|
|||
}
|
||||
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.object_id.toString(), note.id.toString())
|
||||
})
|
||||
|
@ -90,7 +98,11 @@ describe('ActivityPub', () => {
|
|||
}
|
||||
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.actor_id.toString(), actorA.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)
|
||||
|
||||
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.object_id.toString(), note.id.toString())
|
||||
})
|
||||
|
@ -145,7 +160,10 @@ describe('ActivityPub', () => {
|
|||
const row = await db
|
||||
.prepare(`SELECT target_actor_id, state FROM actor_following WHERE actor_id=?`)
|
||||
.bind(actor.id.toString())
|
||||
.first()
|
||||
.first<{
|
||||
target_actor_id: string
|
||||
state: string
|
||||
}>()
|
||||
assert(row)
|
||||
assert.equal(row.target_actor_id, 'https://' + domain + '/ap/users/sven2')
|
||||
assert.equal(row.state, 'accepted')
|
||||
|
@ -204,7 +222,7 @@ describe('ActivityPub', () => {
|
|||
|
||||
const entry = await db
|
||||
.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)
|
||||
assert.equal(properties.content, 'test note')
|
||||
})
|
||||
|
@ -241,7 +259,10 @@ describe('ActivityPub', () => {
|
|||
}
|
||||
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)
|
||||
})
|
||||
|
||||
|
@ -263,7 +284,11 @@ describe('ActivityPub', () => {
|
|||
}
|
||||
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.equal(entry.type, 'mention')
|
||||
assert.equal(entry.actor_id.toString(), actorA.id.toString())
|
||||
|
@ -303,7 +328,11 @@ describe('ActivityPub', () => {
|
|||
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())
|
||||
|
||||
const obj: any = await getObjectById(db, entry.object_id)
|
||||
|
@ -319,7 +348,7 @@ describe('ActivityPub', () => {
|
|||
const db = await makeDB()
|
||||
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||
|
||||
const activity: any = {
|
||||
const activity = {
|
||||
type: 'Create',
|
||||
actor: actor.id.toString(),
|
||||
to: ['some actor'],
|
||||
|
@ -332,7 +361,7 @@ describe('ActivityPub', () => {
|
|||
}
|
||||
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')
|
||||
})
|
||||
|
||||
|
@ -355,7 +384,7 @@ describe('ActivityPub', () => {
|
|||
|
||||
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)
|
||||
assert.equal(
|
||||
content,
|
||||
|
@ -454,7 +483,7 @@ describe('ActivityPub', () => {
|
|||
const updatedObject = await db
|
||||
.prepare('SELECT * FROM objects WHERE original_object_id=?')
|
||||
.bind(object.id)
|
||||
.first()
|
||||
.first<ObjectsRow>()
|
||||
assert(updatedObject)
|
||||
assert.equal(JSON.parse(updatedObject.properties).content, newObject.content)
|
||||
})
|
||||
|
@ -500,7 +529,10 @@ describe('ActivityPub', () => {
|
|||
}
|
||||
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.equal(object.type, 'Note')
|
||||
assert.equal(object.original_actor_id, remoteActorId)
|
||||
|
@ -508,7 +540,7 @@ describe('ActivityPub', () => {
|
|||
const outbox_object = await db
|
||||
.prepare('SELECT * FROM outbox_objects WHERE actor_id=?')
|
||||
.bind(remoteActorId)
|
||||
.first()
|
||||
.first<{ actor_id: string }>()
|
||||
assert(outbox_object)
|
||||
assert.equal(outbox_object.actor_id, remoteActorId)
|
||||
})
|
||||
|
|
|
@ -26,6 +26,17 @@ async function generateVAPIDKeys(): Promise<JWK> {
|
|||
|
||||
describe('Mastodon APIs', () => {
|
||||
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 () => {
|
||||
const env = {
|
||||
INSTANCE_TITLE: 'a',
|
||||
|
@ -39,7 +50,7 @@ describe('Mastodon APIs', () => {
|
|||
assertJSON(res)
|
||||
|
||||
{
|
||||
const data = await res.json<any>()
|
||||
const data = await res.json<Data>()
|
||||
assert.equal(data.rules.length, 0)
|
||||
assert.equal(data.uri, domain)
|
||||
assert.equal(data.title, 'a')
|
||||
|
@ -77,7 +88,7 @@ describe('Mastodon APIs', () => {
|
|||
assertJSON(res)
|
||||
|
||||
{
|
||||
const data = await res.json<any>()
|
||||
const data = await res.json<Data>()
|
||||
assert.equal(data.rules.length, 0)
|
||||
assert.equal(data.domain, domain)
|
||||
assert.equal(data.title, 'a')
|
||||
|
@ -248,7 +259,7 @@ describe('Mastodon APIs', () => {
|
|||
const res = await subscription.handlePostRequest(db, req, connectedActor, client.id, vapidKeys)
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -169,7 +169,7 @@ describe('Mastodon APIs', () => {
|
|||
globalThis.fetch = async (input: RequestInfo, data: any) => {
|
||||
if (input === 'https://api.cloudflare.com/client/v4/accounts/CF_ACCOUNT_ID/images/v1') {
|
||||
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(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
|
@ -333,7 +333,7 @@ describe('Mastodon APIs', () => {
|
|||
assert.equal(data.following_count, 2)
|
||||
assert.equal(data.statuses_count, 1)
|
||||
assert(isUrlValid(data.url))
|
||||
assert(data.url.includes(domain))
|
||||
assert((data.url as string).includes(domain))
|
||||
})
|
||||
|
||||
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
|
||||
// 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)
|
||||
})
|
||||
|
||||
|
@ -640,7 +640,7 @@ describe('Mastodon APIs', () => {
|
|||
const db = await makeDB()
|
||||
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') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
|
@ -718,7 +718,7 @@ describe('Mastodon APIs', () => {
|
|||
|
||||
test('get local actor followers', async () => {
|
||||
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(
|
||||
JSON.stringify({
|
||||
id: 'https://example.com/actor',
|
||||
|
@ -746,7 +746,7 @@ describe('Mastodon APIs', () => {
|
|||
|
||||
test('get local actor following', async () => {
|
||||
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(
|
||||
JSON.stringify({
|
||||
id: 'https://example.com/foo',
|
||||
|
@ -776,7 +776,7 @@ describe('Mastodon APIs', () => {
|
|||
const db = await makeDB()
|
||||
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') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
|
@ -952,11 +952,9 @@ describe('Mastodon APIs', () => {
|
|||
beforeEach(() => {
|
||||
receivedActivity = null
|
||||
|
||||
globalThis.fetch = async (input: any) => {
|
||||
if (
|
||||
input.toString() ===
|
||||
'https://' + domain + '/.well-known/webfinger?resource=acct%3Aactor%40' + domain + ''
|
||||
) {
|
||||
globalThis.fetch = async (input: RequestInfo) => {
|
||||
const request = new Request(input)
|
||||
if (request.url === 'https://' + domain + '/.well-known/webfinger?resource=acct%3Aactor%40' + domain + '') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
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(
|
||||
JSON.stringify({
|
||||
id: `https://${domain}/ap/users/actor`,
|
||||
|
@ -980,13 +978,13 @@ describe('Mastodon APIs', () => {
|
|||
)
|
||||
}
|
||||
|
||||
if (input.url === 'https://example.com/inbox') {
|
||||
assert.equal(input.method, 'POST')
|
||||
receivedActivity = await input.json()
|
||||
if (request.url === 'https://example.com/inbox') {
|
||||
assert.equal(request.method, 'POST')
|
||||
receivedActivity = await request.json()
|
||||
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.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=?`)
|
||||
.bind(actor.id.toString())
|
||||
.first()
|
||||
|
@ -1036,7 +1038,7 @@ describe('Mastodon APIs', () => {
|
|||
const row = await db
|
||||
.prepare(`SELECT count(*) as count FROM actor_following WHERE actor_id=?`)
|
||||
.bind(actor.id.toString())
|
||||
.first()
|
||||
.first<{ count: number }>()
|
||||
assert(row)
|
||||
assert.equal(row.count, 0)
|
||||
})
|
||||
|
|
|
@ -12,8 +12,9 @@ const domain = 'cloudflare.com'
|
|||
describe('Mastodon APIs', () => {
|
||||
describe('media', () => {
|
||||
test('upload image creates object', async () => {
|
||||
globalThis.fetch = async (input: any) => {
|
||||
if (input.url.toString() === 'https://api.cloudflare.com/client/v4/accounts/testaccountid/images/v1') {
|
||||
globalThis.fetch = async (input: RequestInfo) => {
|
||||
const request = new Request(input)
|
||||
if (request.url.toString() === 'https://api.cloudflare.com/client/v4/accounts/testaccountid/images/v1') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
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()
|
||||
|
|
|
@ -117,7 +117,7 @@ describe('Mastodon APIs', () => {
|
|||
|
||||
globalThis.fetch = async (input: RequestInfo, data: any) => {
|
||||
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'])
|
||||
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 { TEST_JWT, ACCESS_CERTS } from '../test-data'
|
||||
import { strict as assert } from 'node:assert/strict'
|
||||
import { Actor } from 'wildebeest/backend/src/activitypub/actors'
|
||||
|
||||
const userKEK = 'test_kek3'
|
||||
const accessDomain = 'access.com'
|
||||
|
@ -100,7 +101,7 @@ describe('Mastodon APIs', () => {
|
|||
)
|
||||
|
||||
// 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)
|
||||
})
|
||||
|
||||
|
@ -126,7 +127,9 @@ describe('Mastodon APIs', () => {
|
|||
const location = res.headers.get('location')
|
||||
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)
|
||||
|
||||
assert.equal(actor.email, 'a@cloudflare.com')
|
||||
|
@ -134,7 +137,7 @@ describe('Mastodon APIs', () => {
|
|||
assert.equal(properties.name, 'name')
|
||||
assert(isUrlValid(actor.id))
|
||||
// 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 () => {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { strict as assert } from 'node:assert/strict'
|
|||
import { insertReply } from 'wildebeest/backend/src/mastodon/reply'
|
||||
import { getMentions } from 'wildebeest/backend/src/mastodon/status'
|
||||
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 * as statuses from 'wildebeest/functions/api/v1/statuses'
|
||||
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 { addFollowing, acceptFollowing } from 'wildebeest/backend/src/mastodon/follow'
|
||||
import { MessageType } from 'wildebeest/backend/src/types/queue'
|
||||
import { MastodonStatus } from 'wildebeest/backend/src/types'
|
||||
|
||||
const userKEK = 'test_kek4'
|
||||
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
||||
|
@ -60,9 +61,9 @@ describe('Mastodon APIs', () => {
|
|||
assert.equal(res.status, 200)
|
||||
assertJSON(res)
|
||||
|
||||
const data = await res.json<any>()
|
||||
assert(data.uri.includes('example.com'))
|
||||
assert(data.uri.includes(data.id))
|
||||
const data = await res.json<MastodonStatus>()
|
||||
assert((data.uri as unknown as string).includes('example.com'))
|
||||
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
|
||||
assert(data.created_at !== undefined)
|
||||
assert(data.account !== undefined)
|
||||
|
@ -84,7 +85,7 @@ describe('Mastodon APIs', () => {
|
|||
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.original_actor_id.toString(), actor.id.toString())
|
||||
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)
|
||||
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)
|
||||
})
|
||||
|
||||
|
@ -180,7 +181,7 @@ describe('Mastodon APIs', () => {
|
|||
})
|
||||
|
||||
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) => {
|
||||
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(deliveredNote)
|
||||
assert.equal(deliveredNote.type, 'Create')
|
||||
assert.equal(deliveredNote.actor, `https://${domain}/ap/users/sven`)
|
||||
assert.equal(deliveredNote.object.attributedTo, `https://${domain}/ap/users/sven`)
|
||||
assert.equal(deliveredNote.object.type, 'Note')
|
||||
assert(deliveredNote.object.to.includes(activities.PUBLIC_GROUP))
|
||||
assert.equal(deliveredNote.object.cc.length, 1)
|
||||
assert.equal((deliveredNote as { type: string }).type, 'Create')
|
||||
assert.equal((deliveredNote as { actor: string }).actor, `https://${domain}/ap/users/sven`)
|
||||
assert.equal(
|
||||
(deliveredNote as { object: { attributedTo: string } }).object.attributedTo,
|
||||
`https://${domain}/ap/users/sven`
|
||||
)
|
||||
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 () => {
|
||||
|
@ -302,15 +306,16 @@ describe('Mastodon APIs', () => {
|
|||
)
|
||||
.run()
|
||||
|
||||
globalThis.fetch = async (input: any) => {
|
||||
if (input.url === actor.id.toString() + '/inbox') {
|
||||
assert.equal(input.method, 'POST')
|
||||
const body = await input.json()
|
||||
globalThis.fetch = async (input: RequestInfo) => {
|
||||
const request = new Request(input)
|
||||
if (request.url === actor.id.toString() + '/inbox') {
|
||||
assert.equal(request.method, 'POST')
|
||||
const body = await request.json()
|
||||
deliveredActivity = body
|
||||
return new Response()
|
||||
}
|
||||
|
||||
throw new Error('unexpected request to ' + JSON.stringify(input))
|
||||
throw new Error('unexpected request to ' + request.url)
|
||||
}
|
||||
|
||||
const connectedActor: any = actor
|
||||
|
@ -336,7 +341,7 @@ describe('Mastodon APIs', () => {
|
|||
const data = await res.json<any>()
|
||||
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.object_id, note.id.toString())
|
||||
})
|
||||
|
@ -469,7 +474,7 @@ describe('Mastodon APIs', () => {
|
|||
const data = await res.json<any>()
|
||||
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.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)
|
||||
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.object_id, note.id.toString())
|
||||
})
|
||||
|
@ -513,15 +518,16 @@ describe('Mastodon APIs', () => {
|
|||
)
|
||||
.run()
|
||||
|
||||
globalThis.fetch = async (input: any) => {
|
||||
if (input.url === 'https://cloudflare.com/ap/users/sven/inbox') {
|
||||
assert.equal(input.method, 'POST')
|
||||
const body = await input.json()
|
||||
globalThis.fetch = async (input: RequestInfo) => {
|
||||
const request = new Request(input)
|
||||
if (request.url === 'https://cloudflare.com/ap/users/sven/inbox') {
|
||||
assert.equal(request.method, 'POST')
|
||||
const body = await request.json()
|
||||
deliveredActivity = body
|
||||
return new Response()
|
||||
}
|
||||
|
||||
throw new Error('unexpected request to ' + JSON.stringify(input))
|
||||
throw new Error('unexpected request to ' + request.url)
|
||||
}
|
||||
|
||||
const connectedActor: any = actor
|
||||
|
@ -588,13 +594,16 @@ describe('Mastodon APIs', () => {
|
|||
`
|
||||
)
|
||||
.bind(data.id)
|
||||
.first()
|
||||
.first<{ inReplyTo: string }>()
|
||||
assert(row !== undefined)
|
||||
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.equal(row.actor_id, actor.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)
|
||||
assert.equal(res.status, 400)
|
||||
const data = await res.json<any>()
|
||||
const data = await res.json<{ error: string }>()
|
||||
assert(data.error.includes('Limit exceeded'))
|
||||
})
|
||||
|
||||
|
@ -644,7 +653,7 @@ describe('Mastodon APIs', () => {
|
|||
|
||||
const res = await statuses.handleRequest(req, db, actor, userKEK, queue, cache)
|
||||
assert.equal(res.status, 400)
|
||||
const data = await res.json<any>()
|
||||
const data = await res.json<{ error: string }>()
|
||||
assert(data.error.includes('Limit exceeded'))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -18,7 +18,7 @@ export function isUrlValid(s: string) {
|
|||
return url.protocol === 'https:'
|
||||
}
|
||||
|
||||
export async function makeDB(): Promise<any> {
|
||||
export async function makeDB(): Promise<D1Database> {
|
||||
const db = new Database(':memory:')
|
||||
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++) {
|
||||
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) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ContextData } from 'wildebeest/backend/src/types/context'
|
||||
import { cors } from 'wildebeest/backend/src/utils/cors'
|
||||
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 { VAPIDPublicKey } from 'wildebeest/backend/src/mastodon/subscription'
|
||||
import { getVAPIDKeys } from 'wildebeest/backend/src/config'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// 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 { getPersonById } from 'wildebeest/backend/src/activitypub/actors'
|
||||
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=?
|
||||
`
|
||||
|
||||
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 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 type { CreateRequest } from 'wildebeest/backend/src/mastodon/subscription'
|
||||
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 { VAPIDPublicKey } from 'wildebeest/backend/src/mastodon/subscription'
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue