diff --git a/packages/nostr/.eslintrc.cjs b/packages/nostr/.eslintrc.cjs new file mode 100644 index 0000000..1e571d8 --- /dev/null +++ b/packages/nostr/.eslintrc.cjs @@ -0,0 +1,12 @@ +module.exports = { + extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + parser: "@typescript-eslint/parser", + plugins: ["@typescript-eslint"], + root: true, + ignorePatterns: ["dist/"], + env: { + browser: true, + node: true, + mocha: true, + }, +} diff --git a/packages/nostr/docker-compose.yaml b/packages/nostr/docker-compose.yaml new file mode 100644 index 0000000..32f5cb5 --- /dev/null +++ b/packages/nostr/docker-compose.yaml @@ -0,0 +1,6 @@ +version: "3.1" +services: + relay: + image: scsibug/nostr-rs-relay + ports: + - 12648:8080 diff --git a/packages/nostr/package.json b/packages/nostr/package.json index bc8b9d3..7282da4 100644 --- a/packages/nostr/package.json +++ b/packages/nostr/package.json @@ -5,15 +5,26 @@ "types": "dist/src/index.d.ts", "scripts": { "build": "tsc", - "watch": "tsc -w" + "watch": "tsc -w", + "test": "ts-mocha --type-check -j 1 test/*.ts", + "lint": "eslint ." }, "devDependencies": { + "@types/expect": "^24.3.0", + "@types/mocha": "^10.0.1", + "@typescript-eslint/eslint-plugin": "^5.53.0", + "@typescript-eslint/parser": "^5.53.0", + "eslint": "^8.34.0", + "mocha": "^10.2.0", + "ts-mocha": "^10.0.0", "typescript": "^4.9.5" }, "prettier": { "semi": false }, "dependencies": { + "@noble/secp256k1": "^1.7.1", + "bech32": "^2.0.0", "isomorphic-ws": "^5.0.0", "ws": "^8.12.1" } diff --git a/packages/nostr/src/client/conn.ts b/packages/nostr/src/client/conn.ts index a18138c..78c1a10 100644 --- a/packages/nostr/src/client/conn.ts +++ b/packages/nostr/src/client/conn.ts @@ -1,7 +1,8 @@ import { ProtocolError } from "../error" import { Filters, SubscriptionId } from "." -import { formatOutgoingMessage, parseIncomingMessage, RawEvent } from "../raw" -import { Event } from "../event" +import { RawEvent, SignedEvent } from "../event" +import WebSocket from "ws" +import { unixTimestamp } from "../util" /** * The connection to a relay. This is the lowest layer of the nostr protocol. @@ -23,8 +24,8 @@ export class Conn { // different, and the NIP-44 stuff should be handled by Nostr. #pending: OutgoingMessage[] = [] - readonly #msgCallbacks: IncomingMessageCallback[] = [] - readonly #errorCallbacks: ConnErrorCallback[] = [] + #msgCallback?: IncomingMessageCallback + #errorCallback?: ErrorCallback get url(): string { return this.#socket.url @@ -34,27 +35,24 @@ export class Conn { this.#socket = new WebSocket(endpoint) // Handle incoming messages. - this.#socket.addEventListener("message", (msgData) => { + this.#socket.addEventListener("message", async (msgData) => { const value = msgData.data.valueOf() // Validate and parse the message. if (typeof value !== "string") { const err = new ProtocolError(`invalid message data: ${value}`) - for (const cb of this.#errorCallbacks) { - cb(err) - } + this.#errorCallback?.(err) return } try { - const msg = parseIncomingMessage(value) - for (const cb of this.#msgCallbacks) { - cb(msg) - } + const msg = await parseIncomingMessage(value) + this.#msgCallback?.(msg) } catch (err) { if (err instanceof ProtocolError) { - for (const cb of this.#errorCallbacks) { - cb(err) - } + this.#errorCallback?.(err) } else { + // TODO Not sure if this is the best idea. + // Investigate what WebSocket does if the callback throws? + // Either way it seems like the best idea is to have `onError` called on all types of errors throw err } } @@ -69,12 +67,16 @@ export class Conn { }) } - onMessage(cb: IncomingMessageCallback): void { - this.#msgCallbacks.push(cb) - } - - onError(cb: ConnErrorCallback): void { - this.#errorCallbacks.push(cb) + on(on: "message", cb: IncomingMessageCallback): void + on(on: "error", cb: ErrorCallback): void + on(on: "message" | "error", cb: IncomingMessageCallback | ErrorCallback) { + if (on === "message") { + this.#msgCallback = cb as IncomingMessageCallback + } else if (on === "error") { + this.#errorCallback = cb as ErrorCallback + } else { + throw new Error(`unexpected input: ${on}`) + } } send(msg: OutgoingMessage): void { @@ -82,7 +84,7 @@ export class Conn { this.#pending.push(msg) return } - this.#socket.send(formatOutgoingMessage(msg)) + this.#socket.send(serializeOutgoingMessage(msg)) } close(): void { @@ -106,7 +108,7 @@ export const enum IncomingKind { export interface IncomingEvent { kind: IncomingKind.Event subscriptionId: SubscriptionId - event: Event + signed: SignedEvent raw: RawEvent } @@ -123,13 +125,13 @@ export interface IncomingNotice { */ export type OutgoingMessage = | OutgoingEvent - | OutgoingSubscription - | OutgoingUnsubscription + | OutgoingOpenSubscription + | OutgoingCloseSubscription export const enum OutgoingKind { Event, - Subscription, - Unsubscription, + OpenSubscription, + CloseSubscription, } /** @@ -137,25 +139,137 @@ export const enum OutgoingKind { */ export interface OutgoingEvent { kind: OutgoingKind.Event - event: Event + event: SignedEvent | RawEvent } /** - * Outgoing "REQ" message, representing a subscription. + * Outgoing "REQ" message, which opens a subscription. */ -export interface OutgoingSubscription { - kind: OutgoingKind.Subscription +export interface OutgoingOpenSubscription { + kind: OutgoingKind.OpenSubscription id: SubscriptionId filters: Filters[] } /** - * Outgoing "CLOSE" message, representing an unsubscription. + * Outgoing "CLOSE" message, which closes a subscription. */ -export interface OutgoingUnsubscription { - kind: OutgoingKind.Unsubscription +export interface OutgoingCloseSubscription { + kind: OutgoingKind.CloseSubscription id: SubscriptionId } type IncomingMessageCallback = (message: IncomingMessage) => unknown -type ConnErrorCallback = (error: ProtocolError) => unknown +type ErrorCallback = (error: ProtocolError) => unknown + +interface RawFilters { + ids?: string[] + authors?: string[] + kinds?: number[] + ["#e"]?: string[] + ["#p"]?: string[] + since?: number + until?: number + limit?: number +} + +async function parseIncomingMessage(data: string): Promise { + const json = parseJson(data) + if (!(json instanceof Array)) { + throw new ProtocolError(`incoming message is not an array: ${data}`) + } + if (json.length === 0) { + throw new ProtocolError(`incoming message is an empty array: ${data}`) + } + if (json[0] === "EVENT") { + if (typeof json[1] !== "string") { + throw new ProtocolError( + `second element of "EVENT" should be a string, but wasn't: ${data}` + ) + } + if (typeof json[2] !== "object") { + throw new ProtocolError( + `second element of "EVENT" should be an object, but wasn't: ${data}` + ) + } + const raw = parseEventData(json[2]) + return { + kind: IncomingKind.Event, + subscriptionId: new SubscriptionId(json[1]), + signed: await SignedEvent.verify(raw), + raw, + } + } + if (json[0] === "NOTICE") { + if (typeof json[1] !== "string") { + throw new ProtocolError( + `second element of "NOTICE" should be a string, but wasn't: ${data}` + ) + } + return { + kind: IncomingKind.Notice, + notice: json[1], + } + } + throw new ProtocolError(`unknown incoming message: ${data}`) +} + +function serializeOutgoingMessage(msg: OutgoingMessage): string { + if (msg.kind === OutgoingKind.Event) { + const raw = + msg.event instanceof SignedEvent ? msg.event.serialize() : msg.event + return JSON.stringify(["EVENT", raw]) + } else if (msg.kind === OutgoingKind.OpenSubscription) { + return JSON.stringify([ + "REQ", + msg.id.toString(), + ...serializeFilters(msg.filters), + ]) + } else if (msg.kind === OutgoingKind.CloseSubscription) { + return JSON.stringify(["CLOSE", msg.id.toString()]) + } else { + throw new Error(`invalid message: ${JSON.stringify(msg)}`) + } +} + +function serializeFilters(filters: Filters[]): RawFilters[] { + if (filters.length === 0) { + return [{}] + } + return filters.map((filter) => ({ + ids: filter.ids?.map((id) => id.toString()), + authors: filter.authors?.map((author) => author.toString()), + kinds: filter.kinds?.map((kind) => kind), + ["#e"]: filter.eventTags?.map((e) => e.toString()), + ["#p"]: filter.pubkeyTags?.map((p) => p.toString()), + since: filter.since !== undefined ? unixTimestamp(filter.since) : undefined, + until: filter.until !== undefined ? unixTimestamp(filter.until) : undefined, + limit: filter.limit, + })) +} + +function parseEventData(json: object): RawEvent { + if ( + typeof json["id"] !== "string" || + typeof json["pubkey"] !== "string" || + typeof json["created_at"] !== "number" || + typeof json["kind"] !== "number" || + !(json["tags"] instanceof Array) || + !json["tags"].every( + (x) => x instanceof Array && x.every((y) => typeof y === "string") + ) || + typeof json["content"] !== "string" || + typeof json["sig"] !== "string" + ) { + throw new ProtocolError(`invalid event: ${JSON.stringify(json)}`) + } + return json as RawEvent +} + +function parseJson(data: string) { + try { + return JSON.parse(data) + } catch (e) { + throw new ProtocolError(`invalid event json: ${data}`) + } +} diff --git a/packages/nostr/src/client/index.ts b/packages/nostr/src/client/index.ts index 5ac5b31..7ec88d8 100644 --- a/packages/nostr/src/client/index.ts +++ b/packages/nostr/src/client/index.ts @@ -1,14 +1,8 @@ import { ProtocolError } from "../error" -import { - EventId, - Event, - serializeId as serializeEventId, - EventKind, -} from "../event" -import { PublicKey } from "../keypair" +import { EventId, Event, EventKind, SignedEvent, RawEvent } from "../event" +import { PrivateKey, PublicKey } from "../keypair" import { Conn, IncomingKind, OutgoingKind } from "./conn" import * as secp from "@noble/secp256k1" -import { RawEvent } from "../raw" /** * A nostr client. @@ -18,62 +12,53 @@ export class Nostr { /** * Open connections to relays. */ - readonly #conns: Map< - string, - { - conn: Conn - /** - * Has this connection been authenticated via NIP-44 AUTH? - */ - auth: boolean - /** - * Should this connection be used for receiving messages? - */ - read: boolean - /** - * Should this connection be used for publishing events? - */ - write: boolean - } - > + readonly #conns: Map = new Map() /** * Mapping of subscription IDs to corresponding filters. */ readonly #subscriptions: Map = new Map() - readonly #eventCallbacks: EventCallback[] = [] - readonly #noticeCallbacks: NoticeCallback[] = [] - readonly #errorCallbacks: ErrorCallback[] = [] + #eventCallback?: EventCallback + #noticeCallback?: NoticeCallback + #errorCallback?: ErrorCallback /** * Add a new callback for received events. */ - onEvent(cb: EventCallback): void { - this.#eventCallbacks.push(cb) - } - + on(on: "event", cb: EventCallback | undefined | null): void /** * Add a new callback for received notices. */ - onNotice(cb: NoticeCallback): void { - this.#noticeCallbacks.push(cb) - } - + on(on: "notice", cb: NoticeCallback | undefined | null): void /** * Add a new callback for errors. */ - onError(cb: ErrorCallback): void { - this.#errorCallbacks.push(cb) + on(on: "error", cb: ErrorCallback | undefined | null): void + // TODO Also add on: ("subscribed", subscriptionId) which checks "OK"/"NOTICE" and makes a callback? + // TODO Also add on: ("sent", eventId) which checks "OK"/"NOTICE" and makes a callback? + on( + on: "event" | "notice" | "error", + cb: EventCallback | NoticeCallback | ErrorCallback | undefined | null + ) { + if (on === "event") { + this.#eventCallback = (cb as EventCallback) ?? undefined + } else if (on === "notice") { + this.#noticeCallback = (cb as NoticeCallback) ?? undefined + } else if (on === "error") { + this.#errorCallback = (cb as ErrorCallback) ?? undefined + } else { + throw new Error(`unexpected input: ${on}`) + } } /** - * Connect and start communicating with a relay. This method recreates all existing + * Open a connection and start communicating with a relay. This method recreates all existing * subscriptions on the new relay as well. If there is already an existing connection, * this method will only update it with the new options, and an exception will be thrown * if no options are specified. */ - connect(url: URL | string, opts?: { read?: boolean; write?: boolean }): void { + open(url: URL | string, opts?: { read?: boolean; write?: boolean }): void { // If the connection already exists, update the options. const existingConn = this.#conns.get(url.toString()) if (existingConn !== undefined) { @@ -95,43 +80,34 @@ export class Nostr { const conn = new Conn(url) // Handle messages on this connection. - conn.onMessage(async (msg) => { + conn.on("message", async (msg) => { if (msg.kind === IncomingKind.Event) { - for (const cb of this.#eventCallbacks) { - cb( - { - event: msg.event, - eventId: await serializeEventId(msg.raw), - subscriptionId: msg.subscriptionId, - raw: msg.raw, - }, - this - ) - } + this.#eventCallback?.( + { + signed: msg.signed, + subscriptionId: msg.subscriptionId, + raw: msg.raw, + }, + this + ) } else if (msg.kind === IncomingKind.Notice) { - for (const cb of this.#noticeCallbacks) { - cb(msg.notice, this) - } + this.#noticeCallback?.(msg.notice, this) } else { const err = new ProtocolError(`invalid message ${msg}`) - for (const cb of this.#errorCallbacks) { - cb(err, this) - } + this.#errorCallback?.(err, this) } }) // Forward connection errors to the error callbacks. - conn.onError((err) => { - for (const cb of this.#errorCallbacks) { - cb(err, this) - } + conn.on("error", (err) => { + this.#errorCallback?.(err, this) }) // Resend existing subscriptions to this connection. for (const [key, filters] of this.#subscriptions.entries()) { const subscriptionId = new SubscriptionId(key) conn.send({ - kind: OutgoingKind.Subscription, + kind: OutgoingKind.OpenSubscription, id: subscriptionId, filters, }) @@ -146,12 +122,23 @@ export class Nostr { } /** - * Disconnect from a relay. If there is no open connection to this relay, an exception is thrown. + * Close connections to relays. + * + * @param url If specified, only close the connection to this relay. If the connection does + * not exist, an exception will be thrown. If this parameter is not specified, all connections + * will be closed. * * TODO There needs to be a way to check connection state. isOpen(), isReady(), isClosing() maybe? * Because of how WebSocket states work this isn't as simple as it seems. */ - disconnect(url: URL | string): void { + close(url?: URL | string): void { + if (url === undefined) { + for (const { conn } of this.#conns.values()) { + conn.close() + } + this.#conns.clear() + return + } const c = this.#conns.get(url.toString()) if (c === undefined) { throw new Error(`connection to ${url} doesn't exist`) @@ -176,16 +163,15 @@ export class Nostr { */ subscribe( filters: Filters[], - subscriptionId?: SubscriptionId + subscriptionId: SubscriptionId = SubscriptionId.random() ): SubscriptionId { - subscriptionId ??= SubscriptionId.random() this.#subscriptions.set(subscriptionId.toString(), filters) for (const { conn, read } of this.#conns.values()) { if (!read) { continue } conn.send({ - kind: OutgoingKind.Subscription, + kind: OutgoingKind.OpenSubscription, id: subscriptionId, filters, }) @@ -207,7 +193,7 @@ export class Nostr { continue } conn.send({ - kind: OutgoingKind.Unsubscription, + kind: OutgoingKind.CloseSubscription, id: subscriptionId, }) } @@ -216,11 +202,39 @@ export class Nostr { /** * Publish an event. */ - async publish(event: Event): Promise { + async publish(event: SignedEvent): Promise + async publish(event: RawEvent): Promise + // TODO This will need to change when I add NIP-44 AUTH support - the key should be optional + async publish(event: Event, key: PrivateKey): Promise + async publish( + event: SignedEvent | RawEvent | Event, + key?: PrivateKey + ): Promise { + // Validate the parameters. + if (event instanceof SignedEvent || "sig" in event) { + if (key !== undefined) { + throw new Error( + "when calling publish with a SignedEvent, private key should not be specified" + ) + } + } else { + if (key === undefined) { + throw new Error( + "publish called with an unsigned Event, private key must be specified" + ) + } + if (event.pubkey.toString() !== key.pubkey.toString()) { + throw new Error("invalid private key") + } + } + for (const { conn, write } of this.#conns.values()) { if (!write) { continue } + if (!(event instanceof SignedEvent) && !("sig" in event)) { + event = await SignedEvent.sign(event, key) + } conn.send({ kind: OutgoingKind.Event, event, @@ -229,6 +243,22 @@ export class Nostr { } } +interface ConnState { + conn: Conn + /** + * Has this connection been authenticated via NIP-44 AUTH? + */ + auth: boolean + /** + * Should this connection be used for receiving events? + */ + read: boolean + /** + * Should this connection be used for publishing events? + */ + write: boolean +} + /** * A string uniquely identifying a client subscription. */ @@ -240,7 +270,7 @@ export class SubscriptionId { } static random(): SubscriptionId { - return new SubscriptionId(secp.utils.randomBytes(32).toString()) + return new SubscriptionId(secp.utils.bytesToHex(secp.utils.randomBytes(32))) } toString() { @@ -290,8 +320,7 @@ export type NoticeCallback = (notice: string, nostr: Nostr) => unknown export type ErrorCallback = (error: ProtocolError, nostr: Nostr) => unknown export interface EventParams { - event: Event - eventId: EventId + signed: SignedEvent subscriptionId: SubscriptionId raw: RawEvent } diff --git a/packages/nostr/src/event.ts b/packages/nostr/src/event.ts index c41178c..5dd4e02 100644 --- a/packages/nostr/src/event.ts +++ b/packages/nostr/src/event.ts @@ -1,8 +1,7 @@ import { ProtocolError } from "./error" -import { RawEvent } from "./raw" import * as secp from "@noble/secp256k1" -import { PublicKey } from "./keypair" -import { parseHex } from "./util" +import { PublicKey, PrivateKey } from "./keypair" +import { unixTimestamp } from "./util" // TODO This file is missing proper documentation // TODO Add remaining event types @@ -34,7 +33,7 @@ interface EventCommon { export interface SetMetadataEvent extends EventCommon { kind: EventKind.SetMetadata - userMetadata: UserMetadata + content: UserMetadata } export interface UserMetadata { @@ -45,109 +44,232 @@ export interface UserMetadata { export interface TextNoteEvent extends EventCommon { kind: EventKind.TextNote - note: string + content: string } export interface UnknownEvent extends EventCommon { kind: Exclude } -export async function createEvent(raw: RawEvent): Promise { - const pubkey = new PublicKey(raw.pubkey) - const createdAt = new Date(raw.created_at * 1000) - const event = { - pubkey, - createdAt, - } - await checkSignature(raw, event) - return ( - createSetMetadataEvent(raw, event) ?? - createTextNodeEvent(raw, event) ?? - createUnknownEvent(raw, event) - ) -} - -function createSetMetadataEvent( - raw: RawEvent, - event: EventCommon -): SetMetadataEvent | undefined { - if (raw.kind !== EventKind.SetMetadata) { - return undefined - } - const userMetadata = parseJson(raw.content) - if ( - typeof userMetadata["name"] !== "string" || - typeof userMetadata["about"] !== "string" || - typeof userMetadata["picture"] !== "string" - ) { - throw new ProtocolError(`invalid user metadata: ${userMetadata}`) - } - return { - ...event, - kind: EventKind.SetMetadata, - userMetadata, - } -} - -function createTextNodeEvent( - raw: RawEvent, - event: EventCommon -): TextNoteEvent | undefined { - if (raw.kind !== EventKind.TextNote) { - return undefined - } - return { - ...event, - kind: EventKind.TextNote, - note: raw.content, - } -} - -function createUnknownEvent(raw: RawEvent, event: EventCommon): UnknownEvent { - return { - ...event, - kind: raw.kind, - } -} - +// TODO Doc comment export class EventId { #hex: string - constructor(hex: string | Uint8Array) { - this.#hex = parseHex(hex) - if (this.#hex.length !== 128) { - throw new ProtocolError(`invalid event id: ${this.#hex}`) + static async create(event: Event | RawEvent): Promise { + // It's not defined whether JSON.stringify produces a string with whitespace stripped. + // Building the JSON string manually as follows ensures that there's no whitespace. + // In hindsight using JSON as a data format for hashing and signing is not the best + // design decision. + if ("id" in event) { + // Raw event. + const serializedTags = `[${event.tags + .map((tag) => `[${tag.map((v) => `"${v}"`).join(",")}]`) + .join(",")}]` + const serialized = `[0,"${event.pubkey}",${event.created_at},${event.kind},${serializedTags},"${event.content}"]` + const hash = await secp.utils.sha256( + Uint8Array.from(charCodes(serialized)) + ) + return new EventId(secp.utils.bytesToHex(hash).toLowerCase()) + } else { + // Not a raw event. + const tags = serializeEventTags(event) + const content = serializeEventContent(event) + const serializedTags = `[${tags + .map((tag) => `[${tag.map((v) => `"${v}"`).join(",")}]`) + .join(",")}]` + const serialized = `[0,"${event.pubkey}",${unixTimestamp( + event.createdAt + )},${event.kind},${serializedTags},"${content}"]` + const hash = await secp.utils.sha256( + Uint8Array.from(charCodes(serialized)) + ) + return new EventId(secp.utils.bytesToHex(hash).toLowerCase()) } } + private constructor(hex: string) { + this.#hex = hex + } + toString(): string { return this.#hex } } -async function checkSignature( - raw: RawEvent, - event: EventCommon -): Promise { - const id = serializeId(raw) - const bytes = await secp.schnorr.sign(id.toString(), event.pubkey.toString()) - const hex = secp.utils.bytesToHex(bytes).toLowerCase() - if (hex.toString() !== raw.sig) { - throw new ProtocolError("invalid signature: ${hex}") +/** + * A signed event. Provides access to the event data, ID, and signature. + */ +export class SignedEvent { + #event: Readonly + #eventId: EventId + #signature: string + + /** + * Sign an event using the specified private key. The private key must match the + * public key from the event. + */ + static async sign(event: Event, key: PrivateKey): Promise { + const id = await EventId.create(event) + const sig = secp.utils + .bytesToHex(await secp.schnorr.sign(id.toString(), key.hexDangerous())) + .toLowerCase() + return new SignedEvent(event, id, sig) + } + + /** + * Verify the signature of a raw event. Throw a `ProtocolError` if the signature + * is invalid. + */ + static async verify(raw: RawEvent): Promise { + const id = await EventId.create(raw) + if (id.toString() !== raw.id) { + throw new ProtocolError(`invalid event id: ${raw.id}, expected ${id}`) + } + if (!(await secp.schnorr.verify(raw.sig, id.toString(), raw.pubkey))) { + throw new ProtocolError(`invalid signature: ${raw.sig}`) + } + return new SignedEvent(parseEvent(raw), id, raw.sig) + } + + private constructor(event: Event, eventId: EventId, signature: string) { + this.#event = cloneEvent(event) + this.#eventId = eventId + this.#signature = signature + } + + /** + * Event ID. + */ + get eventId(): EventId { + return this.#eventId + } + + /** + * Event data. + */ + get event(): Event { + return cloneEvent(this.#event) + } + + /** + * Event signature. + */ + get signature(): string { + return this.#signature + } + + /** + * Serialize the event into its raw format. + */ + serialize(): RawEvent { + const { event, eventId: id, signature } = this + const tags = serializeEventTags(event) + const content = serializeEventContent(event) + return { + id: id.toString(), + pubkey: event.pubkey.toString(), + created_at: unixTimestamp(event.createdAt), + kind: event.kind, + tags, + content, + sig: signature, + } } } -export async function serializeId(raw: RawEvent): Promise { - // It's not defined whether JSON.stringify produces a string with whitespace stripped. - // Building the JSON string manually this way ensures that there's no whitespace. - // In hindsight using JSON as a data format for hashing and signing is not the best - // design decision. - const serializedTags = `[${raw.tags - .map((tag) => `[${tag.map((v) => `"${v}"`).join(",")}]`) - .join(",")}]` - const serialized = `[0,"${raw.pubkey}",${raw.created_at},${raw.kind},${serializedTags},"${raw.content}"]` - const hash = await secp.utils.sha256(Uint8Array.from(charCodes(serialized))) - return new EventId(secp.utils.bytesToHex(hash).toLowerCase()) +export interface RawEvent { + id: string + pubkey: string + created_at: number + kind: number + tags: string[][] + content: string + sig: string +} + +/** + * Parse an event from its raw format. + */ +function parseEvent(raw: RawEvent): Event { + const pubkey = new PublicKey(raw.pubkey) + const createdAt = new Date(raw.created_at * 1000) + const event = { + pubkey, + createdAt, + } + + if (raw.kind === EventKind.SetMetadata) { + const userMetadata = parseJson(raw.content) + if ( + typeof userMetadata["name"] !== "string" || + typeof userMetadata["about"] !== "string" || + typeof userMetadata["picture"] !== "string" + ) { + throw new ProtocolError(`invalid user metadata: ${userMetadata}`) + } + return { + ...event, + kind: EventKind.SetMetadata, + content: userMetadata, + } + } + + if (raw.kind === EventKind.TextNote) { + return { + ...event, + kind: EventKind.TextNote, + content: raw.content, + } + } + + return { + ...event, + kind: raw.kind, + } +} + +function serializeEventTags(_event: Event): string[][] { + // TODO As I add different event kinds, this will change + return [] +} + +function serializeEventContent(event: Event): string { + if (event.kind === EventKind.SetMetadata) { + return JSON.stringify(event.content) + } else if (event.kind === EventKind.TextNote) { + return event.content + } else { + return "" + } +} + +function cloneEvent(event: Event): Event { + const common = { + createdAt: structuredClone(event.createdAt), + pubkey: new PublicKey(event.pubkey.toString()), + } + if (event.kind === EventKind.SetMetadata) { + return { + kind: EventKind.SetMetadata, + content: { + about: event.content.about, + name: event.content.name, + picture: event.content.picture, + }, + ...common, + } + } else if (event.kind === EventKind.TextNote) { + return { + kind: EventKind.TextNote, + content: event.content, + ...common, + } + } else { + return { + kind: event.kind, + ...common, + } + } } function parseJson(data: string) { @@ -159,16 +281,7 @@ function parseJson(data: string) { } function* charCodes(data: string): Iterable { - for (let i = 0; i < length; i++) { + for (let i = 0; i < data.length; i++) { yield data.charCodeAt(i) } } - -// TODO This is an example of how this API can be used, remove this later -function isItNice(e: Event): void { - if (e.kind === EventKind.SetMetadata) { - console.log(e.userMetadata) - } else if (e.kind === EventKind.TextNote) { - console.log(e.note) - } -} diff --git a/packages/nostr/src/keypair.ts b/packages/nostr/src/keypair.ts index 0a77d1c..c43b699 100644 --- a/packages/nostr/src/keypair.ts +++ b/packages/nostr/src/keypair.ts @@ -1,5 +1,6 @@ +import { bech32 } from "bech32" import { ProtocolError } from "./error" -import { parseHex } from "./util" +import * as secp from "@noble/secp256k1" /** * A 32-byte secp256k1 public key. @@ -7,10 +8,13 @@ import { parseHex } from "./util" export class PublicKey { #hex: string - constructor(hex: string | Uint8Array) { - this.#hex = parseHex(hex) + /** + * Expects the key encoded as an npub-prefixed bech32 string, hex string, or byte buffer. + */ + constructor(key: string | Uint8Array) { + this.#hex = parseKey(key, "npub1") if (this.#hex.length !== 64) { - throw new ProtocolError(`invalid pubkey: ${hex}`) + throw new ProtocolError(`invalid pubkey: ${key}`) } } @@ -25,14 +29,51 @@ export class PublicKey { export class PrivateKey { #hex: string - constructor(hex: string | Uint8Array) { - this.#hex = parseHex(hex) + /** + * Expects the key encoded as an nsec-prefixed bech32 string, hex string, or byte buffer. + */ + constructor(key: string | Uint8Array) { + this.#hex = parseKey(key, "nsec1") if (this.#hex.length !== 64) { throw new ProtocolError(`invalid private key: ${this.#hex}`) } } - toString(): string { + get pubkey(): PublicKey { + return new PublicKey(secp.schnorr.getPublicKey(this.#hex)) + } + + /** + * The hex representation of the private key. Use with caution! + */ + hexDangerous(): string { return this.#hex } } + +/** + * Parse a key into its hex representation. + */ +function parseKey(key: string | Uint8Array, bechPrefix: string): string { + if (typeof key === "string") { + // Is the key encoded in bech32? + if (key.startsWith(bechPrefix)) { + const { words } = bech32.decode(key) + const bytes = Uint8Array.from(bech32.fromWords(words)) + return secp.utils.bytesToHex(bytes).toLowerCase() + } + // If not, it must be lowercase hex. + const valid = "0123456789abcdef" + if (key.length % 2 != 0) { + throw new ProtocolError(`invalid lowercase hex string: ${key}`) + } + for (const c of key) { + if (!valid.includes(c)) { + throw new ProtocolError(`invalid lowercase hex string: ${key}`) + } + } + return key + } else { + return secp.utils.bytesToHex(key).toLowerCase() + } +} diff --git a/packages/nostr/src/raw.ts b/packages/nostr/src/raw.ts deleted file mode 100644 index d6c8a7b..0000000 --- a/packages/nostr/src/raw.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Types defining data in the format sent over the wire. - */ - -import { ProtocolError } from "./error" -import { IncomingMessage, OutgoingMessage } from "./client/conn" - -export interface RawEvent { - id: string - pubkey: string - created_at: number - kind: number - tags: string[][] - content: string - sig: string -} - -interface RawFilters { - ids: string[] - authors: string[] - kinds: number[] - ["#e"]: string[] - ["#p"]: string[] - since: number - until: number - limit: number -} - -type RawIncomingMessage = ["EVENT", string, RawEvent] | ["NOTICE", string] - -type RawOutgoingMessage = - | ["EVENT", RawEvent] - | ["REQ", string, RawFilters] - | ["CLOSE", string] - -export function parseIncomingMessage(msg: string): IncomingMessage { - throw new Error("todo") -} - -export function formatOutgoingMessage(msg: OutgoingMessage): string { - throw new Error("todo") -} - -function parseRawEvent(data: string): RawEvent { - const json = parseJson(data) - if ( - typeof json["id"] !== "string" || - typeof json["pubkey"] !== "string" || - typeof json["created_at"] !== "number" || - typeof json["kind"] !== "number" || - !(json["tags"] instanceof Array) || - !json["tags"].every( - (x) => x instanceof Array && x.every((y) => typeof y === "string") - ) || - typeof json["content"] !== "string" || - typeof json["sig"] !== "string" - ) { - throw new ProtocolError(`invalid event: ${data}`) - } - return json -} - -function parseJson(data: string) { - try { - return JSON.parse(data) - } catch (e) { - throw new ProtocolError(`invalid event json: ${data}`) - } -} diff --git a/packages/nostr/src/util.ts b/packages/nostr/src/util.ts index 6336476..5f1858e 100644 --- a/packages/nostr/src/util.ts +++ b/packages/nostr/src/util.ts @@ -1,22 +1,6 @@ -import * as secp from "@noble/secp256k1" -import { ProtocolError } from "./error" - /** - * Check that the input is a valid lowercase hex string. + * Calculate the unix timestamp (seconds since epoch) of the `Date`. */ -export function parseHex(hex: string | Uint8Array): string { - if (typeof hex === "string") { - const valid = "0123456789abcdef" - if (hex.length % 2 != 0) { - throw new ProtocolError(`invalid hex string: ${hex}`) - } - for (const c of hex) { - if (!valid.includes(c)) { - throw new ProtocolError(`invalid hex string: ${hex}`) - } - } - return hex - } else { - return secp.utils.bytesToHex(hex).toLowerCase() - } +export function unixTimestamp(date: Date): number { + return Math.floor(date.getTime() / 1000) } diff --git a/packages/nostr/test/simple-communication.ts b/packages/nostr/test/simple-communication.ts new file mode 100644 index 0000000..af4041c --- /dev/null +++ b/packages/nostr/test/simple-communication.ts @@ -0,0 +1,55 @@ +import { Nostr } from "../src/client" +import { EventKind } from "../src/event" +import { PrivateKey } from "../src/keypair" +import assert from "assert" + +// TODO Switch out the relay implementation and see if the issue persists + +describe("single event communication", function () { + it("ok", function (done) { + const secret = new PrivateKey( + "nsec1xlu55y6fqfgrq448xslt6a8j2rh7lj08hyhgs94ryq04yf6surwsjl0kzh" + ) + const pubkey = secret.pubkey + + const timestamp = new Date() + const note = "hello world" + + const publisher = new Nostr() + publisher.open("ws://localhost:12648") + const subscriber = new Nostr() + subscriber.open("ws://localhost:12648") + + subscriber.on("event", ({ signed: { event } }) => { + assert.equal(event.kind, EventKind.TextNote) + assert.equal(event.pubkey.toString(), pubkey.toString()) + assert.equal(event.createdAt.toString(), timestamp.toString()) + if (event.kind === EventKind.TextNote) { + assert.equal(event.content, note) + } + + // There is a bug with the nostr relay used for testing where if the publish and + // subscribe happen at the same time, the same event might end up being broadcast twice. + // To prevent reacting to the same event and calling done() twice, remove the callback + // for future events. + subscriber.on("event", null) + + publisher.close() + subscriber.close() + + done() + }) + + subscriber.subscribe([]) + + publisher.publish( + { + kind: EventKind.TextNote, + createdAt: timestamp, + content: note, + pubkey, + }, + secret + ) + }) +}) diff --git a/packages/nostr/tsconfig.json b/packages/nostr/tsconfig.json index a637053..f6cff62 100644 --- a/packages/nostr/tsconfig.json +++ b/packages/nostr/tsconfig.json @@ -6,7 +6,9 @@ "sourceMap": true, "composite": true, "outDir": "dist", - "moduleResolution": "node" + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true }, "include": ["src"] } diff --git a/yarn.lock b/yarn.lock index 8d61c3d..044d804 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1432,6 +1432,13 @@ dependencies: jest-get-type "^29.4.2" +"@jest/expect-utils@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.4.3.tgz#95ce4df62952f071bcd618225ac7c47eaa81431e" + integrity sha512-/6JWbkxHOP8EoS8jeeTd9dTfc9Uawi+43oLKHfp6zzux3U2hqOOVnV3ai4RpDYHOccL6g+5nrxpoc8DmJxtXVQ== + dependencies: + jest-get-type "^29.4.3" + "@jest/fake-timers@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" @@ -1498,6 +1505,13 @@ dependencies: "@sinclair/typebox" "^0.25.16" +"@jest/schemas@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.3.tgz#39cf1b8469afc40b6f5a2baaa146e332c4151788" + integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg== + dependencies: + "@sinclair/typebox" "^0.25.16" + "@jest/source-map@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" @@ -1593,6 +1607,18 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" +"@jest/types@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.4.3.tgz#9069145f4ef09adf10cec1b2901b2d390031431f" + integrity sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA== + dependencies: + "@jest/schemas" "^29.4.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -1667,7 +1693,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ== -"@noble/secp256k1@^1.7.0": +"@noble/secp256k1@^1.7.0", "@noble/secp256k1@^1.7.1": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" integrity sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw== @@ -2033,6 +2059,13 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== +"@types/expect@^24.3.0": + version "24.3.0" + resolved "https://registry.yarnpkg.com/@types/expect/-/expect-24.3.0.tgz#d7cab8b3c10c2d92a0cbb31981feceb81d3486f1" + integrity sha512-aq5Z+YFBz5o2b6Sp1jigx5nsmoZMK5Ceurjwy6PZmRv7dEi1jLtkARfvB1ME+OXJUG+7TZUDcv3WoCr/aor6dQ== + dependencies: + expect "*" + "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": version "4.17.33" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz#de35d30a9d637dc1450ad18dd583d75d5733d543" @@ -2140,6 +2173,11 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== +"@types/mocha@^10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.1.tgz#2f4f65bb08bc368ac39c96da7b2f09140b26851b" + integrity sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q== + "@types/ms@*": version "0.7.31" resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" @@ -2319,6 +2357,22 @@ semver "^7.3.7" tsutils "^3.21.0" +"@typescript-eslint/eslint-plugin@^5.53.0": + version "5.53.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.53.0.tgz#24b8b4a952f3c615fe070e3c461dd852b5056734" + integrity sha512-alFpFWNucPLdUOySmXCJpzr6HKC3bu7XooShWM+3w/EL6J2HIoB2PFxpLnq4JauWVk6DiVeNKzQlFEaE+X9sGw== + dependencies: + "@typescript-eslint/scope-manager" "5.53.0" + "@typescript-eslint/type-utils" "5.53.0" + "@typescript-eslint/utils" "5.53.0" + debug "^4.3.4" + grapheme-splitter "^1.0.4" + ignore "^5.2.0" + natural-compare-lite "^1.4.0" + regexpp "^3.2.0" + semver "^7.3.7" + tsutils "^3.21.0" + "@typescript-eslint/experimental-utils@^5.0.0": version "5.51.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.51.0.tgz#936124843a9221863f027a08063b737578838bea" @@ -2336,6 +2390,16 @@ "@typescript-eslint/typescript-estree" "5.51.0" debug "^4.3.4" +"@typescript-eslint/parser@^5.53.0": + version "5.53.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.53.0.tgz#a1f2b9ae73b83181098747e96683f1b249ecab52" + integrity sha512-MKBw9i0DLYlmdOb3Oq/526+al20AJZpANdT6Ct9ffxcV8nKCHz63t/S0IhlTFNsBIHJv+GY5SFJ0XfqVeydQrQ== + dependencies: + "@typescript-eslint/scope-manager" "5.53.0" + "@typescript-eslint/types" "5.53.0" + "@typescript-eslint/typescript-estree" "5.53.0" + debug "^4.3.4" + "@typescript-eslint/scope-manager@5.51.0": version "5.51.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz#ad3e3c2ecf762d9a4196c0fbfe19b142ac498990" @@ -2344,6 +2408,14 @@ "@typescript-eslint/types" "5.51.0" "@typescript-eslint/visitor-keys" "5.51.0" +"@typescript-eslint/scope-manager@5.53.0": + version "5.53.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.53.0.tgz#42b54f280e33c82939275a42649701024f3fafef" + integrity sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w== + dependencies: + "@typescript-eslint/types" "5.53.0" + "@typescript-eslint/visitor-keys" "5.53.0" + "@typescript-eslint/type-utils@5.51.0": version "5.51.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz#7af48005531700b62a20963501d47dfb27095988" @@ -2354,11 +2426,26 @@ debug "^4.3.4" tsutils "^3.21.0" +"@typescript-eslint/type-utils@5.53.0": + version "5.53.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.53.0.tgz#41665449935ba9b4e6a1ba6e2a3f4b2c31d6cf97" + integrity sha512-HO2hh0fmtqNLzTAme/KnND5uFNwbsdYhCZghK2SoxGp3Ifn2emv+hi0PBUjzzSh0dstUIFqOj3bp0AwQlK4OWw== + dependencies: + "@typescript-eslint/typescript-estree" "5.53.0" + "@typescript-eslint/utils" "5.53.0" + debug "^4.3.4" + tsutils "^3.21.0" + "@typescript-eslint/types@5.51.0": version "5.51.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.51.0.tgz#e7c1622f46c7eea7e12bbf1edfb496d4dec37c90" integrity sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw== +"@typescript-eslint/types@5.53.0": + version "5.53.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.53.0.tgz#f79eca62b97e518ee124086a21a24f3be267026f" + integrity sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A== + "@typescript-eslint/typescript-estree@5.51.0": version "5.51.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz#0ec8170d7247a892c2b21845b06c11eb0718f8de" @@ -2372,6 +2459,19 @@ semver "^7.3.7" tsutils "^3.21.0" +"@typescript-eslint/typescript-estree@5.53.0": + version "5.53.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.53.0.tgz#bc651dc28cf18ab248ecd18a4c886c744aebd690" + integrity sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w== + dependencies: + "@typescript-eslint/types" "5.53.0" + "@typescript-eslint/visitor-keys" "5.53.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + "@typescript-eslint/utils@5.51.0", "@typescript-eslint/utils@^5.43.0": version "5.51.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.51.0.tgz#074f4fabd5b12afe9c8aa6fdee881c050f8b4d47" @@ -2386,6 +2486,20 @@ eslint-utils "^3.0.0" semver "^7.3.7" +"@typescript-eslint/utils@5.53.0": + version "5.53.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.53.0.tgz#e55eaad9d6fffa120575ffaa530c7e802f13bce8" + integrity sha512-VUOOtPv27UNWLxFwQK/8+7kvxVC+hPHNsJjzlJyotlaHjLSIgOCKj9I0DBUjwOOA64qjBwx5afAPjksqOxMO0g== + dependencies: + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.53.0" + "@typescript-eslint/types" "5.53.0" + "@typescript-eslint/typescript-estree" "5.53.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + semver "^7.3.7" + "@typescript-eslint/visitor-keys@5.51.0": version "5.51.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz#c0147dd9a36c0de758aaebd5b48cae1ec59eba87" @@ -2394,6 +2508,14 @@ "@typescript-eslint/types" "5.51.0" eslint-visitor-keys "^3.3.0" +"@typescript-eslint/visitor-keys@5.53.0": + version "5.53.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.53.0.tgz#8a5126623937cdd909c30d8fa72f79fa56cc1a9f" + integrity sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w== + dependencies: + "@typescript-eslint/types" "5.53.0" + eslint-visitor-keys "^3.3.0" + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" @@ -2655,6 +2777,11 @@ ajv@^8.0.0, ajv@^8.6.0, ajv@^8.8.0: require-from-string "^2.0.2" uri-js "^4.2.2" +ansi-colors@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + ansi-escapes@^4.2.1, ansi-escapes@^4.3.0, ansi-escapes@^4.3.1: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" @@ -2801,6 +2928,11 @@ array.prototype.tosorted@^1.1.1: es-shim-unscopables "^1.0.0" get-intrinsic "^1.1.3" +arrify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== + asap@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -3152,6 +3284,11 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.18.1, browserslist@^4.21.3, browserslist@^4.21.4: version "4.21.5" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" @@ -3169,7 +3306,7 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" -buffer-from@^1.0.0: +buffer-from@^1.0.0, buffer-from@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== @@ -3228,7 +3365,7 @@ camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.2.0, camelcase@^6.2.1: +camelcase@^6.0.0, camelcase@^6.2.0, camelcase@^6.2.1: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -3290,7 +3427,7 @@ check-types@^11.1.1: resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.2.2.tgz#7afc0b6a860d686885062f2dba888ba5710335b4" integrity sha512-HBiYvXvn9Z70Z88XKjz3AEKd4HJhBXsa3j7xFnITAzoS8+q6eIGi8qDB8FKPBAjtuxjI/zFpwuiCb8oDtKOYrA== -chokidar@^3.4.2, chokidar@^3.5.3: +chokidar@3.5.3, chokidar@^3.4.2, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -3807,7 +3944,7 @@ debug@2.6.9, debug@^2.6.0: dependencies: ms "2.0.0" -debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -3821,6 +3958,11 @@ debug@^3.2.7: dependencies: ms "^2.1.1" +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + decimal.js@^10.2.1: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" @@ -3973,6 +4115,21 @@ diff-sequences@^29.4.2: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.2.tgz#711fe6bd8a5869fe2539cee4a5152425ff671fda" integrity sha512-R6P0Y6PrsH3n4hUXxL3nns0rbRk6Q33js3ygJBeEpbzLzgcNuJ61+u0RXasFpTKISw99TxUzFnumSnRLsjhLaw== +diff-sequences@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" + integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== + +diff@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + +diff@^3.1.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + diff@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" @@ -4287,6 +4444,11 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== +escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -4297,11 +4459,6 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - escodegen@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" @@ -4485,7 +4642,7 @@ eslint-webpack-plugin@^3.1.1: normalize-path "^3.0.0" schema-utils "^4.0.0" -eslint@^8.3.0: +eslint@^8.3.0, eslint@^8.34.0: version "8.34.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.34.0.tgz#fe0ab0ef478104c1f9ebc5537e303d25a8fb22d6" integrity sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg== @@ -4628,6 +4785,17 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== +expect@*: + version "29.4.3" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.4.3.tgz#5e47757316df744fe3b8926c3ae8a3ebdafff7fe" + integrity sha512-uC05+Q7eXECFpgDrHdXA4k2rpMyStAYPItEDLyQDo5Ta7fVkJnNA/4zh/OIVkVVNZ1oOK1PipQoyNjuZ6sz6Dg== + dependencies: + "@jest/expect-utils" "^29.4.3" + jest-get-type "^29.4.3" + jest-matcher-utils "^29.4.3" + jest-message-util "^29.4.3" + jest-util "^29.4.3" + expect@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" @@ -4794,6 +4962,14 @@ find-cache-dir@^3.3.1: make-dir "^3.0.2" pkg-dir "^4.1.0" +find-up@5.0.0, find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + find-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" @@ -4809,14 +4985,6 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -4825,6 +4993,11 @@ flat-cache@^3.0.4: flatted "^3.1.0" rimraf "^3.0.2" +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + flatted@^3.1.0: version "3.2.7" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" @@ -5000,6 +5173,18 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== +glob@7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -5144,7 +5329,7 @@ hast-util-whitespace@^2.0.0: resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz#0ec64e257e6fc216c7d14c8a1b74d27d650b4557" integrity sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng== -he@^1.2.0: +he@1.2.0, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== @@ -5571,6 +5756,11 @@ is-path-inside@^3.0.3: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + is-plain-obj@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" @@ -5656,6 +5846,11 @@ is-typedarray@^1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + is-weakmap@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" @@ -5857,6 +6052,16 @@ jest-diff@^29.4.2: jest-get-type "^29.4.2" pretty-format "^29.4.2" +jest-diff@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.4.3.tgz#42f4eb34d0bf8c0fb08b0501069b87e8e84df347" + integrity sha512-YB+ocenx7FZ3T5O9lMVMeLYV4265socJKtkwgk/6YUz/VsEzYDkiMuMhWzZmxm3wDRQvayJu/PjkjjSkjoHsCA== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.4.3" + jest-get-type "^29.4.3" + pretty-format "^29.4.3" + jest-docblock@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" @@ -5910,6 +6115,11 @@ jest-get-type@^29.4.2: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.2.tgz#7cb63f154bca8d8f57364d01614477d466fa43fe" integrity sha512-vERN30V5i2N6lqlFu4ljdTqQAgrkTFMC9xaIIfOPYBw04pufjXRty5RuXBiB1d72tGbURa/UgoiHB90ruOSivg== +jest-get-type@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" + integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== + jest-haste-map@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" @@ -5981,6 +6191,16 @@ jest-matcher-utils@^29.4.2: jest-get-type "^29.4.2" pretty-format "^29.4.2" +jest-matcher-utils@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.4.3.tgz#ea68ebc0568aebea4c4213b99f169ff786df96a0" + integrity sha512-TTciiXEONycZ03h6R6pYiZlSkvYgT0l8aa49z/DLSGYjex4orMUcafuLXYyyEDWB1RKglq00jzwY00Ei7yFNVg== + dependencies: + chalk "^4.0.0" + jest-diff "^29.4.3" + jest-get-type "^29.4.3" + pretty-format "^29.4.3" + jest-message-util@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" @@ -6026,6 +6246,21 @@ jest-message-util@^29.4.2: slash "^3.0.0" stack-utils "^2.0.3" +jest-message-util@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.4.3.tgz#65b5280c0fdc9419503b49d4f48d4999d481cb5b" + integrity sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.4.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.4.3" + slash "^3.0.0" + stack-utils "^2.0.3" + jest-mock@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" @@ -6201,6 +6436,18 @@ jest-util@^29.4.2: graceful-fs "^4.2.9" picomatch "^2.2.3" +jest-util@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.4.3.tgz#851a148e23fc2b633c55f6dad2e45d7f4579f496" + integrity sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q== + dependencies: + "@jest/types" "^29.4.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + jest-validate@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" @@ -6304,6 +6551,13 @@ js-sha3@0.8.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-yaml@4.1.0, js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" @@ -6312,13 +6566,6 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - jsdom@^16.6.0: version "16.7.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" @@ -6394,7 +6641,7 @@ json-stable-stringify@^1.0.1: dependencies: jsonify "^0.0.1" -json5@^1.0.1: +json5@^1.0.1, json5@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== @@ -6614,6 +6861,14 @@ lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +log-symbols@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + log-update@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" @@ -6666,6 +6921,11 @@ make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: dependencies: semver "^6.0.0" +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + makeerror@1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" @@ -7025,6 +7285,13 @@ minimalistic-assert@^1.0.0: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== +minimatch@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" + integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== + dependencies: + brace-expansion "^2.0.1" + minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -7044,13 +7311,40 @@ minimist@^1.2.0, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -mkdirp@~0.5.1: +mkdirp@^0.5.1, mkdirp@~0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== dependencies: minimist "^1.2.6" +mocha@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.2.0.tgz#1fd4a7c32ba5ac372e03a17eef435bd00e5c68b8" + integrity sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg== + dependencies: + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.3" + debug "4.3.4" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.2.0" + he "1.2.0" + js-yaml "4.1.0" + log-symbols "4.1.0" + minimatch "5.0.1" + ms "2.1.3" + nanoid "3.3.3" + serialize-javascript "6.0.0" + strip-json-comments "3.1.1" + supports-color "8.1.1" + workerpool "6.2.1" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + mri@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" @@ -7086,6 +7380,11 @@ nano-time@1.0.0: dependencies: big-integer "^1.6.16" +nanoid@3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" + integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== + nanoid@^3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" @@ -8136,6 +8435,15 @@ pretty-format@^29.0.0, pretty-format@^29.4.2: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-format@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.4.3.tgz#25500ada21a53c9e8423205cf0337056b201244c" + integrity sha512-cvpcHTc42lcsvOOAzd3XuNWTcvk1Jmnzqeu+WsOuiPmxUJTnkbAcFNsRKvEpBEUFVUgy/GTZLulZDcDEi+CIlA== + dependencies: + "@jest/schemas" "^29.4.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -8941,6 +9249,13 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" +serialize-javascript@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + serialize-javascript@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" @@ -9323,7 +9638,7 @@ strip-final-newline@^3.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: +strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -9348,6 +9663,13 @@ stylehacks@^5.1.1: browserslist "^4.21.4" postcss-selector-parser "^6.0.4" +supports-color@8.1.1, supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -9362,13 +9684,6 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - supports-hyperlinks@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" @@ -9602,6 +9917,29 @@ tryer@^1.0.1: resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== +ts-mocha@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/ts-mocha/-/ts-mocha-10.0.0.tgz#41a8d099ac90dbbc64b06976c5025ffaebc53cb9" + integrity sha512-VRfgDO+iiuJFlNB18tzOfypJ21xn2xbuZyDvJvqpTbWgkAgD17ONGr8t+Tl8rcBtOBdjXp5e/Rk+d39f7XBHRw== + dependencies: + ts-node "7.0.1" + optionalDependencies: + tsconfig-paths "^3.5.0" + +ts-node@7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-7.0.1.tgz#9562dc2d1e6d248d24bc55f773e3f614337d9baf" + integrity sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw== + dependencies: + arrify "^1.0.0" + buffer-from "^1.1.0" + diff "^3.1.0" + make-error "^1.1.1" + minimist "^1.2.0" + mkdirp "^0.5.1" + source-map-support "^0.5.6" + yn "^2.0.0" + tsconfig-paths@^3.14.1: version "3.14.1" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" @@ -9612,6 +9950,16 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" +tsconfig-paths@^3.5.0: + version "3.14.2" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" + integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + tslib@^1.8.1: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -10372,6 +10720,11 @@ workbox-window@6.5.4: "@types/trusted-types" "^2.0.2" workbox-core "6.5.4" +workerpool@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" + integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== + wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" @@ -10460,12 +10813,27 @@ yaml@^2.1.3: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.2.1.tgz#3014bf0482dcd15147aa8e56109ce8632cd60ce4" integrity sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw== +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + yargs-parser@^20.2.2: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs@^16.2.0: +yargs-unparser@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== + dependencies: + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" + +yargs@16.2.0, yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== @@ -10478,6 +10846,11 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yn@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a" + integrity sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"