From ed0f315b1a7f8d759d855cc775df3252dafee71e Mon Sep 17 00:00:00 2001 From: Sandwich <299465+dskvr@users.noreply.github.com> Date: Wed, 8 Jan 2025 12:54:05 +0700 Subject: [PATCH] validate NIP-11 against schema --- .../partials/relay-single/RelayNip11.svelte | 38 ++++++++++ .../src/lib/services/Nip05Service/index.ts | 7 +- .../services/SchemaValidationService/index.ts | 18 ++--- .../schemavalidation.worker.ts | 73 +++++++++---------- .../relays/[protocol]/[...relay]/+page.svelte | 14 +++- libraries/schemata-js-ajv/src/index.ts | 6 +- libraries/schemata/nips/nip-11/schema.yaml | 33 +-------- 7 files changed, 100 insertions(+), 89 deletions(-) create mode 100644 apps/gui/src/lib/components/partials/relay-single/RelayNip11.svelte diff --git a/apps/gui/src/lib/components/partials/relay-single/RelayNip11.svelte b/apps/gui/src/lib/components/partials/relay-single/RelayNip11.svelte new file mode 100644 index 00000000..814b118a --- /dev/null +++ b/apps/gui/src/lib/components/partials/relay-single/RelayNip11.svelte @@ -0,0 +1,38 @@ +<script lang="ts"> + import { Nip11 } from '@nostrwatch/nip66/models'; + import { onMount } from 'svelte'; + import { derived, writable, type Readable, type Writable } from 'svelte/store'; + import { SchemaValidationService, type SchemaValidationServiceResponse } from '$lib/services/SchemaValidationService'; + + export let nip11: Writable<Nip11> + + const schemaValidationService = new SchemaValidationService(); + + const validationResult: Writable<SchemaValidationServiceResponse | null> = writable(null); + + const nip11Valid: Readable<boolean> = derived(validationResult, $validationResult => { + return $validationResult?.status === 'success' && $validationResult?.result?.valid === true; + }); + + onMount(() => { + schemaValidationService.validateNip11($nip11?.json, $nip11?.hash).then( (result: SchemaValidationServiceResponse) => { + validationResult.set(result); + }) + }); +</script> + +{#if $nip11Valid} + <div class="bg-green-500 text-white p-4 rounded-lg"> + <p class="text-lg font-bold">NIP-11 has no issues</p> + </div> +{:else} + <div class="bg-red-500 text-white p-4 rounded-lg"> + <p class="text-lg font-bold">NIP-11 requires attentions</p> + </div> +{/if} + +{#if $validationResult} + <pre class="py-6 px-8 bg-white/5 rounded-lg">{JSON.stringify($validationResult, null, 4)}</pre> +{/if} + +<pre class="py-6 px-8 bg-white/5 rounded-lg">{JSON.stringify($nip11?.json, null, 4)}</pre> \ No newline at end of file diff --git a/apps/gui/src/lib/services/Nip05Service/index.ts b/apps/gui/src/lib/services/Nip05Service/index.ts index d0697972..71a05c42 100644 --- a/apps/gui/src/lib/services/Nip05Service/index.ts +++ b/apps/gui/src/lib/services/Nip05Service/index.ts @@ -16,10 +16,9 @@ export class Nip05Service { } find(pubkey: string, nip05: Nip05): INip05Result | undefined { + const $nip05s: INip05Map = get(nip05s) const key = generateNip05MapKey(pubkey, nip05) - let n05s = get(nip05s) - let result = n05s.get(key) - return result + return $nip05s.get(key) } async check(pubkey: string, nip05: Nip05): Promise<INip05Result> { @@ -27,7 +26,7 @@ export class Nip05Service { this.worker.postMessage({ pubkey, nip05 }) let result: INip05Result | undefined; while(!result){ - result = get(nip05s).get(key) + result = (get(nip05s) as INip05Map).get(key) await new Promise( resolve => setTimeout( resolve, 200 )) } return result; diff --git a/apps/gui/src/lib/services/SchemaValidationService/index.ts b/apps/gui/src/lib/services/SchemaValidationService/index.ts index 707f1189..1f573548 100644 --- a/apps/gui/src/lib/services/SchemaValidationService/index.ts +++ b/apps/gui/src/lib/services/SchemaValidationService/index.ts @@ -1,4 +1,4 @@ -import { deterministicHash } from '@nostrwatch/nip66/utils/hash'; +import { deterministicHash } from '@nostrwatch/nip66/utils'; import type { NostrEvent } from 'nostr-tools'; import { get } from 'svelte/store'; import { EventEmitter } from 'tseep' @@ -41,7 +41,7 @@ export class SchemaValidationService { const timeout = setTimeout( () => reject({ status: 'error', hash, error: 'Request timed out, worker may have been terminated.' }), 5000) this.emitter.once(this.emitterKey(hash), (response: SchemaValidationServiceResponse) => { clearTimeout(timeout) - const { result, error } = response; + const { error } = response; if(error) { reject(response) } @@ -52,6 +52,11 @@ export class SchemaValidationService { }) } + private onmessage(message: MessageEvent<SchemaValidationServiceResponse>){ + const { hash } = message.data; + this.emitter.emit(this.emitterKey(hash), message.data) + } + async validate(request: SchemaValidationServiceRequest, hash?: string): Promise<SchemaValidationServiceResponse> { hash = hash ?? deterministicHash(request.json) this._subIds.add(hash) @@ -59,12 +64,12 @@ export class SchemaValidationService { return this.respond(hash) } - async validateNip11(nip11: string): Promise<SchemaValidationServiceResponse> { + async validateNip11(nip11: any, hash?: string): Promise<SchemaValidationServiceResponse> { const request: SchemaValidationServiceRequest = { type: 'nip11', json: nip11 } - return this.validate(request) + return this.validate(request, hash) } async validateMessage(json: string, subject: string, slug: string): Promise<SchemaValidationServiceResponse> { @@ -89,9 +94,4 @@ export class SchemaValidationService { } return this.validate(request, hash) } - - private onmessage(message: MessageEvent<SchemaValidationServiceResponse>){ - const { hash } = message.data; - this.emitter.emit(this.emitterKey(hash), message.data) - } } \ No newline at end of file diff --git a/apps/gui/src/lib/services/SchemaValidationService/schemavalidation.worker.ts b/apps/gui/src/lib/services/SchemaValidationService/schemavalidation.worker.ts index 528cbc99..6fd29a1d 100644 --- a/apps/gui/src/lib/services/SchemaValidationService/schemavalidation.worker.ts +++ b/apps/gui/src/lib/services/SchemaValidationService/schemavalidation.worker.ts @@ -1,42 +1,41 @@ -import { deterministicHash } from '@nostrwatch/nip66/utils/hash'; -import { type SchemaValidatorResult } from '@nostrwatch/schemata-js-ajv' +import { deterministicHash } from '@nostrwatch/nip66/utils'; +import { validateNip11, validateMessage, validateNote, type SchemaValidatorResult } from '@nostrwatch/schemata-js-ajv' self.onmessage = ({ data }) => { - import('@nostrwatch/schemata-js-ajv').then( ({validateNip11, validateMessage, validateNote}) => { - const { json, type, subject, slug } = data as any; - let { hash } = data as any; - let result: SchemaValidatorResult; - let error: string = ''; - if(!hash) { - hash = deterministicHash(json) + console.log('schema validation worker recieved data', data) + const { json, type, subject, slug } = data as any; + let { hash } = data as any; + let result: SchemaValidatorResult; + let error: string = ''; + if(!hash) { + hash = deterministicHash(json) + } + if(type === 'nip11') { + result = validateNip11(json) + } + else if(type === 'message') { + if(subject && slug) { + result = validateMessage(json, subject, slug) } - if(type === 'nip11') { - result = validateNip11(json) + else { + error = 'Both subject and slug are required for message validation (for example subject as "relay" and slug as "ok" for the NIP-01 "OK" message.)' } - else if(type === 'message') { - if(subject && slug) { - result = validateMessage(json, subject, slug) - } - else { - error = 'Both subject and slug are required for message validation (for example subject "relay" and slug "ok"' - } - } - else if(type === 'note') { - result = validateNote(json) - } - if(result) { - self.postMessage({ - status: 'success', - hash, - result - }) - } - else if(error){ - self.postMessage({ - status: 'error', - hash, - error - }) - } - }); + } + else if(type === 'note') { + result = validateNote(json) + } + if(result) { + self.postMessage({ + status: 'success', + hash, + result + }) + } + else if(error){ + self.postMessage({ + status: 'error', + hash, + error + }) + } } \ No newline at end of file diff --git a/apps/gui/src/routes/relays/[protocol]/[...relay]/+page.svelte b/apps/gui/src/routes/relays/[protocol]/[...relay]/+page.svelte index 9e78088b..5f0c1a54 100644 --- a/apps/gui/src/routes/relays/[protocol]/[...relay]/+page.svelte +++ b/apps/gui/src/routes/relays/[protocol]/[...relay]/+page.svelte @@ -36,6 +36,7 @@ let CardSpeed: typeof import('$lib/components/partials/relay-single/cards/CardSpeed.svelte').default | null = null; let CardNips: typeof import('$lib/components/partials/relay-single/cards/CardNips.svelte').default | null = null; let RelayAudits: typeof import('$lib/components/partials/relay-single/RelayAudits.svelte').default | null = null; + let RelayNip11: typeof import('$lib/components/partials/relay-single/RelayNip11.svelte').default | null = null; const loadComponent = async (importFunc: () => Promise<any>, setter: (component: any) => void) => { try { @@ -47,11 +48,11 @@ }; const loadComponents = () => { + loadComponent(() => import('svelte-bricks'), (comp) => Masonry = comp); loadComponent(() => import('$lib/components/partials/ProfileCompact.svelte'), (comp) => ProfileCompact = comp); loadComponent(() => import('$lib/components/partials/relay-single/RelayChecks.svelte'), (comp) => RelayChecks = comp); loadComponent(() => import('$lib/components/partials/relay-single/OperatorFeed.svelte'), (comp) => OperatorFeed = comp); loadComponent(() => import('$lib/components/ui/tabs'), (comp) => Tabs = comp); - loadComponent(() => import('svelte-bricks'), (comp) => Masonry = comp); loadComponent(() => import('$lib/components/partials/relay-single/cards/CardChecks.svelte'), (comp) => CardChecks = comp); loadComponent(() => import('$lib/components/partials/relay-single/cards/CardFees.svelte'), (comp) => CardFees = comp); loadComponent(() => import('$lib/components/partials/relay-single/cards/CardInsights.svelte'), (comp) => CardInsights = comp); @@ -63,6 +64,7 @@ loadComponent(() => import('$lib/components/partials/relay-single/cards/CardSpeed.svelte'), (comp) => CardSpeed = comp); loadComponent(() => import('$lib/components/partials/relay-single/cards/CardNips.svelte'), (comp) => CardNips = comp); loadComponent(() => import('$lib/components/partials/relay-single/RelayAudits.svelte'), (comp) => RelayAudits = comp); + loadComponent(() => import('$lib/components/partials/relay-single/RelayNip11.svelte'), (comp) => RelayNip11 = comp); }; doBootstrap.set(false); @@ -389,19 +391,25 @@ </Tabs.Content> {/if} - {#if Tabs && RelayChecks} + {#if Tabs && RelayChecks && $relayAggregate} <Tabs.Content value="checks" class="py-6"> <RelayChecks relay={relayUrl} monitors={$monitors} checks={$checksrelay} aggregate={$relayAggregate} /> </Tabs.Content> {/if} + + {#if Tabs && RelayNip11 && $nip11Ready} <Tabs.Content value="nip11"> <!-- {$nip11s.get(relayUrl)?.length ?? 0} NIP-11s from NIP-66 events [{$nip11s.get(relayUrl)?.[0] ? true : false}] <br /> --> <!-- {#if $nip11sLocal?.get(relayUrl)} NIP-11 found locally <br /> {/if} --> - <pre class="py-6 px-8 bg-white/5 rounded-lg">{JSON.stringify($nip11?.json, null, 4)}</pre> + <!-- <pre class="py-6 px-8 bg-white/5 rounded-lg"> + {JSON.stringify($nip11?.json, null, 4)} + </pre> --> + <RelayNip11 {nip11} /> </Tabs.Content> + {/if} <Tabs.Content value="operator-feed"> diff --git a/libraries/schemata-js-ajv/src/index.ts b/libraries/schemata-js-ajv/src/index.ts index a3df212d..fc6c8e02 100644 --- a/libraries/schemata-js-ajv/src/index.ts +++ b/libraries/schemata-js-ajv/src/index.ts @@ -1,12 +1,8 @@ import { Ajv, type ErrorObject } from 'ajv'; -import addErrors from 'ajv-errors' import * as NostrSchemata from '@nostrwatch/schemata' import { type NostrEvent } from 'nostr-tools' -const ajv = new Ajv({ allErrors: true }); -addErrors(ajv); - type NostrSchemataType = typeof NostrSchemata; const defaultResult: SchemaValidatorResult = { valid: false, errors: [], warnings: [] } @@ -18,6 +14,8 @@ export type SchemaValidatorResult = { } const validate = (schema: any, data: any): SchemaValidatorResult => { + const ajv = new Ajv({ allErrors: true }); + ajv.addKeyword("errorMessage") const result = structuredClone(defaultResult) const validate = ajv.compile(schema); const valid = validate(data); diff --git a/libraries/schemata/nips/nip-11/schema.yaml b/libraries/schemata/nips/nip-11/schema.yaml index 0a603df0..6f937c81 100644 --- a/libraries/schemata/nips/nip-11/schema.yaml +++ b/libraries/schemata/nips/nip-11/schema.yaml @@ -84,20 +84,6 @@ properties: type: number created_at_upper_limit: type: number - required: - - max_message_length - - max_subscriptions - - max_filters - - max_limit - - max_subid_length - - max_event_tags - - max_content_length - - min_pow_difficulty - - auth_required - - payment_required - - restricted_writes - - created_at_lower_limit - - created_at_upper_limit payments_url: type: string pattern: "^https?://" @@ -111,23 +97,6 @@ properties: publication: $ref: "#/$defs/fee" additionalProperties: false -required: -- name -- description -- pubkey -- contact -- supported_nips -- software -- version -- retention -- relay_country -- icon -- language_tags -- tags -- posting_policy -- limitation -- payments_url -- fees allOf: - if: properties: @@ -207,4 +176,4 @@ allOf: - kinds - count - time -additionalProperties: false +additionalProperties: false \ No newline at end of file