From 26089ef958f27d1bb6bfa45628cfd522133aac28 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Sun, 22 Jan 2023 10:32:18 -0300 Subject: [PATCH] refactor previous commit a little, add fakejson module for simple parsing that doesn't use regex. --- fakejson.test.js | 35 ++++++++++++++++++++++++ fakejson.ts | 26 ++++++++++++++++++ index.ts | 2 ++ relay.ts | 70 +++++++++++++++++++++--------------------------- 4 files changed, 94 insertions(+), 39 deletions(-) create mode 100644 fakejson.test.js create mode 100644 fakejson.ts diff --git a/fakejson.test.js b/fakejson.test.js new file mode 100644 index 0000000..0f64bae --- /dev/null +++ b/fakejson.test.js @@ -0,0 +1,35 @@ +/* eslint-env jest */ + +const {fj} = require('./lib/nostr.cjs') + +test('match id', () => { + expect( + fj.matchEventId( + `["EVENT","nostril-query",{"tags":[],"content":"so did we cut all corners and p2p stuff in order to make a decentralized social network that was fast and worked, but in the end what we got was a lot of very slow clients that can't handle the traffic of one jack dorsey tweet?","sig":"ca62629d189edebb8f0811cfa0ac53015013df5f305dcba3f411ba15cfc4074d8c2d517ee7d9e81c9eb72a7328bfbe31c9122156397565ac55e740404e2b1fe7","id":"fef2a50f7d9d3d5a5f38ee761bc087ec16198d3f0140df6d1e8193abf7c2b146","kind":1,"pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1671150419}]`, + 'fef2a50f7d9d3d5a5f38ee761bc087ec16198d3f0140df6d1e8193abf7c2b146' + ) + ).toBeTruthy() + + expect( + fj.matchEventId( + `["EVENT","nostril-query",{"content":"a bunch of mfs interacted with my post using what I assume were \"likes\": https://nostr.build/i/964.png","created_at":1672506879,"id":"f40bdd0905137ad60482537e260890ab50b0863bf16e67cf9383f203bd26c96f","kind":1,"pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","sig":"8b825d2d4096f0643b18ca39da59ec07a682cd8a3e717f119c845037573d98099f5bea94ec7ddedd5600c8020144a255ed52882a911f7f7ada6d6abb3c0a1eb4","tags":[]}]`, + 'fef2a50f7d9d3d5a5f38ee761bc087ec16198d3f0140df6d1e8193abf7c2b146' + ) + ).toBeFalsy() +}) + +test('match kind', () => { + expect( + fj.matchEventKind( + `["EVENT","nostril-query",{"tags":[],"content":"so did we cut all corners and p2p stuff in order to make a decentralized social network that was fast and worked, but in the end what we got was a lot of very slow clients that can't handle the traffic of one jack dorsey tweet?","sig":"ca62629d189edebb8f0811cfa0ac53015013df5f305dcba3f411ba15cfc4074d8c2d517ee7d9e81c9eb72a7328bfbe31c9122156397565ac55e740404e2b1fe7","id":"fef2a50f7d9d3d5a5f38ee761bc087ec16198d3f0140df6d1e8193abf7c2b146","kind":1,"pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1671150419}]`, + 1 + ) + ).toBeTruthy() + + expect( + fj.matchEventKind( + `["EVENT","nostril-query",{"content":"{\"name\":\"fiatjaf\",\"about\":\"buy my merch at fiatjaf store\",\"picture\":\"https://fiatjaf.com/static/favicon.jpg\",\"nip05\":\"_@fiatjaf.com\"}","created_at":1671217411,"id":"b52f93f6dfecf9d81f59062827cd941412a0e8398dda60baf960b17499b88900","kind":12720,"pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","sig":"fc1ea5d45fa5ed0526faed06e8fc7a558e60d1b213e9714f440828584ee999b93407092f9b04deea7e504fa034fc0428f31f7f0f95417b3280ebe6004b80b470","tags":[]}]`, + 12720 + ) + ).toBeTruthy() +}) diff --git a/fakejson.ts b/fakejson.ts new file mode 100644 index 0000000..3ba5fc0 --- /dev/null +++ b/fakejson.ts @@ -0,0 +1,26 @@ +export function getHex64(json: string, field: string): string { + let len = field.length + 3 + let idx = json.indexOf(`"${field}":`) + len + let s = json.slice(idx).indexOf(`"`) + idx + 1 + return json.slice(s, s + 64) +} + +export function getInt(json: string, field: string): number { + let len = field.length + let idx = json.indexOf(`"${field}":`) + len + 3 + let sliced = json.slice(idx) + let end = Math.min(sliced.indexOf(','), sliced.indexOf('}')) + return parseInt(sliced.slice(0, end), 10) +} + +export function matchEventId(json: string, id: string): boolean { + return id === getHex64(json, 'id') +} + +export function matchEventPubkey(json: string, pubkey: string): boolean { + return pubkey === getHex64(json, 'pubkey') +} + +export function matchEventKind(json: string, kind: number): boolean { + return kind === getInt(json, 'kind') +} diff --git a/index.ts b/index.ts index fe0f850..eda6ea3 100644 --- a/index.ts +++ b/index.ts @@ -3,6 +3,8 @@ export * from './relay' export * from './event' export * from './filter' +export * as fj from './fakejson' + export * as nip04 from './nip04' export * as nip05 from './nip05' export * as nip06 from './nip06' diff --git a/relay.ts b/relay.ts index 0546309..5934248 100644 --- a/relay.ts +++ b/relay.ts @@ -2,6 +2,7 @@ import {Event, verifySignature, validateEvent} from './event' import {Filter, matchFilters} from './filter' +import {getHex64} from './fakejson' type RelayEvent = 'connect' | 'disconnect' | 'error' | 'notice' @@ -31,11 +32,16 @@ type SubscriptionOptions = { id?: string } -export function relayInit(url: string, alreadyHaveEvent?: (id: string) => boolean): Relay { +export function relayInit( + url: string, + alreadyHaveEvent: (id: string) => boolean = () => false +): Relay { var ws: WebSocket var resolveClose: () => void - var setOpen: (value: (PromiseLike | void)) => void - var untilOpen = new Promise((resolve) => { setOpen = resolve }) + var setOpen: (value: PromiseLike | void) => void + var untilOpen = new Promise(resolve => { + setOpen = resolve + }) var openSubs: {[id: string]: {filters: Filter[]} & SubscriptionOptions} = {} var listeners: { connect: Array<() => void> @@ -61,7 +67,6 @@ export function relayInit(url: string, alreadyHaveEvent?: (id: string) => boolea failed: Array<(reason: string) => void> } } = {} - let idRegex = /"id":"([a-fA-F0-9]+)"/; async function connectRelay(): Promise { return new Promise((resolve, reject) => { @@ -81,35 +86,36 @@ export function relayInit(url: string, alreadyHaveEvent?: (id: string) => boolea resolveClose && resolveClose() } - let incomingMessageQueue: any[] = [] + let incomingMessageQueue: string[] = [] let handleNextInterval: any - const handleNext = () => { + ws.onmessage = e => { + incomingMessageQueue.push(e.data) + if (!handleNextInterval) { + handleNextInterval = setInterval(handleNext, 0) + } + } + + function handleNext() { if (incomingMessageQueue.length === 0) { clearInterval(handleNextInterval) handleNextInterval = null return } - var data = incomingMessageQueue.shift() - if (data && !!alreadyHaveEvent) { - const match = idRegex.exec(data) - if (match) { - const id = match[1]; - if (alreadyHaveEvent(id)) { - return - } - } + var json = incomingMessageQueue.shift() + if (!json || alreadyHaveEvent(getHex64(json, 'id'))) { + return } - try { - data = JSON.parse(data) - } catch (err) {} - if (data.length >= 1) { + try { + let data = JSON.parse(json) + + // we won't do any checks against the data since all failures (i.e. invalid messages from relays) + // will naturally be caught by the encompassing try..catch block + switch (data[0]) { case 'EVENT': - if (data.length !== 3) return // ignore empty or malformed EVENT - let id = data[1] let event = data[2] if ( @@ -123,13 +129,11 @@ export function relayInit(url: string, alreadyHaveEvent?: (id: string) => boolea } return case 'EOSE': { - if (data.length !== 2) return // ignore empty or malformed EOSE let id = data[1] ;(subListeners[id]?.eose || []).forEach(cb => cb()) return } case 'OK': { - if (data.length < 3) return // ignore empty or malformed OK let id: string = data[1] let ok: boolean = data[2] let reason: string = data[3] || '' @@ -138,18 +142,12 @@ export function relayInit(url: string, alreadyHaveEvent?: (id: string) => boolea return } case 'NOTICE': - if (data.length !== 2) return // ignore empty or malformed NOTICE let notice = data[1] listeners.notice.forEach(cb => cb(notice)) return } - } - } - - ws.onmessage = e => { - incomingMessageQueue.push(e.data) - if (!handleNextInterval) { - handleNextInterval = setInterval(handleNext, 0) + } catch (err) { + return } } }) @@ -216,19 +214,13 @@ export function relayInit(url: string, alreadyHaveEvent?: (id: string) => boolea return { url, sub, - on: ( - type: RelayEvent, - cb: any - ): void => { + on: (type: RelayEvent, cb: any): void => { listeners[type].push(cb) if (type === 'connect' && ws?.readyState === 1) { cb() } }, - off: ( - type: RelayEvent, - cb: any - ): void => { + off: (type: RelayEvent, cb: any): void => { let index = listeners[type].indexOf(cb) if (index !== -1) listeners[type].splice(index, 1) },