Skip to content

Commit

Permalink
validate NIP-11 against schema
Browse files Browse the repository at this point in the history
  • Loading branch information
dskvr committed Jan 8, 2025
1 parent 9932f0b commit ed0f315
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 89 deletions.
Original file line number Diff line number Diff line change
@@ -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>
7 changes: 3 additions & 4 deletions apps/gui/src/lib/services/Nip05Service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,17 @@ 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> {
const key = generateNip05MapKey(pubkey, nip05)
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;
Expand Down
18 changes: 9 additions & 9 deletions apps/gui/src/lib/services/SchemaValidationService/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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)
}
Expand All @@ -52,19 +52,24 @@ 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)
this.worker.postMessage(request)
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> {
Expand All @@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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
})
}
}
14 changes: 11 additions & 3 deletions apps/gui/src/routes/relays/[protocol]/[...relay]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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">
Expand Down
6 changes: 2 additions & 4 deletions libraries/schemata-js-ajv/src/index.ts
Original file line number Diff line number Diff line change
@@ -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: [] }
Expand All @@ -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);
Expand Down
33 changes: 1 addition & 32 deletions libraries/schemata/nips/nip-11/schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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?://"
Expand All @@ -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:
Expand Down Expand Up @@ -207,4 +176,4 @@ allOf:
- kinds
- count
- time
additionalProperties: false
additionalProperties: false

0 comments on commit ed0f315

Please sign in to comment.