Make sure deletes are by the same author
This commit is contained in:
@@ -1150,9 +1150,9 @@ export const randomId = (): string => Math.random().toString().slice(2)
|
|||||||
*/
|
*/
|
||||||
export const sleep = (t: number) => new Promise(resolve => setTimeout(resolve, t))
|
export const sleep = (t: number) => new Promise(resolve => setTimeout(resolve, t))
|
||||||
|
|
||||||
export type PollOptions = {
|
export type PollOptions<T> = {
|
||||||
signal: AbortSignal
|
signal: AbortSignal
|
||||||
condition: () => boolean
|
condition: () => T
|
||||||
interval?: number
|
interval?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1161,17 +1161,21 @@ export type PollOptions = {
|
|||||||
* @param options - PollOptions
|
* @param options - PollOptions
|
||||||
* @returns void Promise
|
* @returns void Promise
|
||||||
*/
|
*/
|
||||||
export const poll = ({interval = 300, condition, signal}: PollOptions) =>
|
export const poll = <T>({interval = 300, condition, signal}: PollOptions<T>) =>
|
||||||
new Promise<void>(resolve => {
|
new Promise<T | void>(resolve => {
|
||||||
const int = setInterval(() => {
|
const int = setInterval(() => {
|
||||||
if (condition()) {
|
const value = condition()
|
||||||
resolve()
|
|
||||||
|
if (value !== undefined) {
|
||||||
|
resolve(value)
|
||||||
clearInterval(int)
|
clearInterval(int)
|
||||||
}
|
}
|
||||||
}, interval)
|
}, interval)
|
||||||
|
|
||||||
if (condition()) {
|
const value = condition()
|
||||||
resolve()
|
|
||||||
|
if (value !== undefined) {
|
||||||
|
resolve(value)
|
||||||
clearInterval(int)
|
clearInterval(int)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1610,6 +1614,12 @@ export const prop =
|
|||||||
(x: Record<string, unknown>) =>
|
(x: Record<string, unknown>) =>
|
||||||
x[k] as T
|
x[k] as T
|
||||||
|
|
||||||
|
/** Returns a function that checks whether the object's property value is in a list */
|
||||||
|
export const propIn =
|
||||||
|
<T>(k: string, xs: T[]) =>
|
||||||
|
(x: Record<string, unknown>) =>
|
||||||
|
xs.includes(x[k] as T)
|
||||||
|
|
||||||
/** Returns a function that adds/updates a property on object */
|
/** Returns a function that adds/updates a property on object */
|
||||||
export const assoc =
|
export const assoc =
|
||||||
<K extends string, T>(k: K, v: T) =>
|
<K extends string, T>(k: K, v: T) =>
|
||||||
|
|||||||
@@ -102,8 +102,13 @@ describe("Repository", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should handle delete events", () => {
|
it("should handle delete events", () => {
|
||||||
const event = createEvent(1)
|
const pubkey = randomHex()
|
||||||
const deleteEvent = createEvent(DELETE, {tags: [["e", event.id]], created_at: now() + 100})
|
const event = createEvent(1, {pubkey})
|
||||||
|
const deleteEvent = createEvent(DELETE, {
|
||||||
|
pubkey,
|
||||||
|
tags: [["e", event.id]],
|
||||||
|
created_at: now() + 100,
|
||||||
|
})
|
||||||
|
|
||||||
repo.publish(event)
|
repo.publish(event)
|
||||||
repo.publish(deleteEvent)
|
repo.publish(deleteEvent)
|
||||||
@@ -112,8 +117,10 @@ describe("Repository", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should handle delete by address", () => {
|
it("should handle delete by address", () => {
|
||||||
const event = createEvent(MUTES)
|
const pubkey = randomHex()
|
||||||
|
const event = createEvent(MUTES, {pubkey})
|
||||||
const deleteEvent = createEvent(DELETE, {
|
const deleteEvent = createEvent(DELETE, {
|
||||||
|
pubkey,
|
||||||
tags: [["a", `10000:${event.pubkey}:`]],
|
tags: [["a", `10000:${event.pubkey}:`]],
|
||||||
created_at: now() + 100,
|
created_at: now() + 100,
|
||||||
})
|
})
|
||||||
@@ -123,6 +130,16 @@ describe("Repository", () => {
|
|||||||
|
|
||||||
expect(repo.isDeletedByAddress(event)).toBe(true)
|
expect(repo.isDeletedByAddress(event)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should not delete events with mismatched pubkeys", () => {
|
||||||
|
const event = createEvent(1)
|
||||||
|
const deleteEvent = createEvent(DELETE, {tags: [["e", event.id]], created_at: now() + 1})
|
||||||
|
|
||||||
|
repo.publish(event)
|
||||||
|
repo.publish(deleteEvent)
|
||||||
|
|
||||||
|
expect(repo.isDeleted(event)).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("expire events", () => {
|
describe("expire events", () => {
|
||||||
@@ -224,8 +241,13 @@ describe("Repository", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should not return deleted events", () => {
|
it("should not return deleted events", () => {
|
||||||
const event = createEvent(1)
|
const pubkey = randomHex()
|
||||||
const deleteEvent = createEvent(DELETE, {tags: [["e", event.id]], created_at: now() + 1})
|
const event = createEvent(1, {pubkey})
|
||||||
|
const deleteEvent = createEvent(DELETE, {
|
||||||
|
pubkey,
|
||||||
|
tags: [["e", event.id]],
|
||||||
|
created_at: now() + 1,
|
||||||
|
})
|
||||||
|
|
||||||
repo.publish(event)
|
repo.publish(event)
|
||||||
repo.publish(deleteEvent)
|
repo.publish(deleteEvent)
|
||||||
|
|||||||
@@ -1,4 +1,17 @@
|
|||||||
import {DAY, Emitter, flatten, pluck, sortBy, inc, uniq, omit, now, range} from "@welshman/lib"
|
import {
|
||||||
|
DAY,
|
||||||
|
Emitter,
|
||||||
|
flatten,
|
||||||
|
pick,
|
||||||
|
pushToMapKey,
|
||||||
|
pluck,
|
||||||
|
sortBy,
|
||||||
|
inc,
|
||||||
|
uniq,
|
||||||
|
omit,
|
||||||
|
now,
|
||||||
|
range,
|
||||||
|
} from "@welshman/lib"
|
||||||
import {
|
import {
|
||||||
DELETE,
|
DELETE,
|
||||||
EPOCH,
|
EPOCH,
|
||||||
@@ -45,7 +58,7 @@ export class Repository extends Emitter {
|
|||||||
eventsByDay = new Map<number, TrustedEvent[]>()
|
eventsByDay = new Map<number, TrustedEvent[]>()
|
||||||
eventsByAuthor = new Map<string, TrustedEvent[]>()
|
eventsByAuthor = new Map<string, TrustedEvent[]>()
|
||||||
eventsByKind = new Map<number, TrustedEvent[]>()
|
eventsByKind = new Map<number, TrustedEvent[]>()
|
||||||
deletes = new Map<string, number>()
|
deletes = new Map<string, {created_at: number; pubkey: string}[]>()
|
||||||
expired = new Map<string, number>()
|
expired = new Map<string, number>()
|
||||||
|
|
||||||
static get() {
|
static get() {
|
||||||
@@ -101,8 +114,12 @@ export class Repository extends Emitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Anything removed via delete or replace has been removed
|
// Anything removed via delete or replace has been removed
|
||||||
for (const id of this.deletes.keys()) {
|
for (const idOrAddress of this.deletes.keys()) {
|
||||||
removed.add(id)
|
const event = this.getEvent(idOrAddress)
|
||||||
|
|
||||||
|
if (event && this.isDeleted(event)) {
|
||||||
|
removed.add(event.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anything expired has been removed
|
// Anything expired has been removed
|
||||||
@@ -211,7 +228,7 @@ export class Repository extends Emitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If our event is newer than what it's replacing, delete the old version
|
// If our event is newer than what it's replacing, delete the old version
|
||||||
this.deletes.set(duplicate.id, event.created_at)
|
pushToMapKey(this.deletes, duplicate.id, pick(["pubkey", "created_at"], event))
|
||||||
|
|
||||||
// Notify listeners that it's been removed
|
// Notify listeners that it's been removed
|
||||||
removed.add(duplicate.id)
|
removed.add(duplicate.id)
|
||||||
@@ -238,7 +255,7 @@ export class Repository extends Emitter {
|
|||||||
// If this is a delete event, the tag value is an id or address. Track when it was
|
// If this is a delete event, the tag value is an id or address. Track when it was
|
||||||
// deleted so that replaceables can be restored.
|
// deleted so that replaceables can be restored.
|
||||||
if (event.kind === DELETE && ["a", "e"].includes(tag[0]) && tag[1]) {
|
if (event.kind === DELETE && ["a", "e"].includes(tag[0]) && tag[1]) {
|
||||||
this.deletes.set(tag[1], Math.max(event.created_at, this.deletes.get(tag[1]) || 0))
|
pushToMapKey(this.deletes, tag[1], pick(["pubkey", "created_at"], event))
|
||||||
|
|
||||||
const deletedEvent = this.getEvent(tag[1])
|
const deletedEvent = this.getEvent(tag[1])
|
||||||
|
|
||||||
@@ -266,12 +283,22 @@ export class Repository extends Emitter {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
isDeletedByAddress = (event: TrustedEvent) =>
|
_isDeleted = (key: string, event: TrustedEvent) => {
|
||||||
(this.deletes.get(getAddress(event)) || 0) > event.created_at
|
for (const {pubkey, created_at} of this.deletes.get(key) || []) {
|
||||||
|
if (pubkey === event.pubkey && created_at > event.created_at) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isDeletedById = (event: TrustedEvent) => (this.deletes.get(event.id) || 0) > event.created_at
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
isDeleted = (event: TrustedEvent) => this.isDeletedByAddress(event) || this.isDeletedById(event)
|
isDeletedByAddress = (event: TrustedEvent) => this._isDeleted(getAddress(event), event)
|
||||||
|
|
||||||
|
isDeletedById = (event: TrustedEvent) => this._isDeleted(event.id, event)
|
||||||
|
|
||||||
|
isDeleted = (event: TrustedEvent) =>
|
||||||
|
this._isDeleted(event.id, event) || this._isDeleted(getAddress(event), event)
|
||||||
|
|
||||||
isExpired = (event: TrustedEvent) => {
|
isExpired = (event: TrustedEvent) => {
|
||||||
const ts = this.expired.get(event.id)
|
const ts = this.expired.get(event.id)
|
||||||
|
|||||||
Reference in New Issue
Block a user