diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index c46cd8bd..7c038501 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/apps/gui/package.json b/apps/gui/package.json index fccaf7ff..8a15bf35 100755 --- a/apps/gui/package.json +++ b/apps/gui/package.json @@ -98,6 +98,7 @@ "radix-svelte": "^0.9.0", "surrealdb": "^1.0.6", "svelte-time": "^0.9.0", + "svelte-timeago": "^0.1.2", "vite-plugin-compression": "^0.5.1" } } diff --git a/apps/gui/src/lib/components/blocks/Header.svelte b/apps/gui/src/lib/components/blocks/Header.svelte index add3deb3..59f21efb 100644 --- a/apps/gui/src/lib/components/blocks/Header.svelte +++ b/apps/gui/src/lib/components/blocks/Header.svelte @@ -6,8 +6,8 @@

nostr.watch

diff --git a/apps/gui/src/lib/components/blocks/console/Console.svelte b/apps/gui/src/lib/components/blocks/table/DataTable.svelte similarity index 93% rename from apps/gui/src/lib/components/blocks/console/Console.svelte rename to apps/gui/src/lib/components/blocks/table/DataTable.svelte index a058e9bf..a4342b3a 100644 --- a/apps/gui/src/lib/components/blocks/console/Console.svelte +++ b/apps/gui/src/lib/components/blocks/table/DataTable.svelte @@ -1,5 +1,3 @@ - - + +
+
+
+ {#if monitor?.profile?.photo} + + {photo} + + {:else} + + {photo} + + {/if} +
+
+
+ + {#if monitor?.profile?.name} + {monitor.profile.name} + {:else} + {monitor.pubkey.slice(0, 21)}... + {/if} + + {#if monitor?.profile?.nip05} + {monitor.profile.nip05} + {/if} + {#if lastActive} + last active + {/if} +
+
+ {#if checksCount} + reporting {checksCount} relays online + {/if} +
+
+ checks + {#if checks} + {#each checks as check} + {check} + {/each} + {/if} +
+
+ {#if monitor?.profile?.lud16} + {monitor.profile.lud16} + {/if} + {#if relays?.length} + Publishes to {relays.length} relays {relays.join(', ')} + {/if} +
+
+
+
\ No newline at end of file diff --git a/apps/gui/src/lib/stores/monitors.ts b/apps/gui/src/lib/stores/monitors.ts index 10e88916..e1d5c31c 100644 --- a/apps/gui/src/lib/stores/monitors.ts +++ b/apps/gui/src/lib/stores/monitors.ts @@ -1,10 +1,10 @@ -import type { Readable } from "svelte/motion"; -import { derived, writable, type Writable } from "svelte/store"; +import { derived, writable, type Writable, get } from "svelte/store"; import type { ICheck } from "@nostrwatch/nip66/models"; import { eventsArray } from "./events.js"; import type { IMonitor, IEvent } from "@nostrwatch/nip66/models"; import { throttledDerived } from "$lib/utils/stores.js"; -import { StateManager } from "@nostrwatch/nip66"; +import { MonitorManager, StateManager } from "@nostrwatch/nip66"; + export type Monitor = { priority?: number, @@ -19,14 +19,45 @@ export const monitors = throttledDerived( monitorsMap, ($monitorsMap) => { const arr = Array.from($monitorsMap.values()); - //console.log('monitors array', arr) - //console.log('monitors stringified:', JSON.stringify(arr)) StateManager.set('cache:monitors', arr); return arr; }, 10 ) +export const monitorRows = derived( + monitorsMap, + ($monitorsMap) => { + const $monitors = Array.from($monitorsMap.values()); + return $monitors.map((monitor: Monitor) => { + const row: Record = new Object(); + row.id = monitor.registration.pubkey; + row.pubkey = monitor.registration.pubkey; + row.name = monitor.profile.name ?? null + row.photo = monitor?.profile?.photo ?? monitor?.profile?.picture ?? null + row.about = monitor?.profile?.about ?? null + row.nip05 = monitor?.profile?.nip05 ?? null + row.lud16 = monitor?.profile?.lud16 ?? null + row.geohash = monitor?.registration?.geohash ?? null + row.checks = monitor?.registration?.checks ?? null + row.networks = monitor?.registration?.networks ?? null + row.frequency = monitor?.registration?.frequency ?? null + row.lastActive = monitor?.registration?.lastActive ?? null + row.relays = monitor.relays ?? null + const reportingOnline = get(monitorChecksCount) + row.reportingOnline = reportingOnline?.[monitor.registration.pubkey] ?? 0 + return row; + }) + } +); + +export const prioritizedMonitors = derived( + monitors, + ($monitors) => { + return MonitorManager.sortMonitorsByPriority($monitors); + } +); + export const monitorChecksCount = throttledDerived( eventsArray, ($eventsArray) => { diff --git a/apps/gui/src/lib/utils/lifecycle.ts b/apps/gui/src/lib/utils/lifecycle.ts index 1b017ac6..23834472 100644 --- a/apps/gui/src/lib/utils/lifecycle.ts +++ b/apps/gui/src/lib/utils/lifecycle.ts @@ -112,7 +112,7 @@ export const bootstrap = async (_instance?: Nip66) => { const nip66Instance = await instance(_instance); bindBootstrapEmitters(nip66Instance); await nip66Instance.monitorService.bootstrap(); - bindLiveSubscriptionEmitters(nip66Instance); + // bindLiveSubscriptionEmitters(nip66Instance); }; export const destroy = () => { diff --git a/apps/gui/src/lib/utils/pfp.ts b/apps/gui/src/lib/utils/pfp.ts new file mode 100644 index 00000000..f913a57b --- /dev/null +++ b/apps/gui/src/lib/utils/pfp.ts @@ -0,0 +1,278 @@ +export class PFP { + private static instance: PFP; + private canvas: HTMLCanvasElement; + private ctx: CanvasRenderingContext2D; + + private constructor(size = 256) { + this.canvas = document.createElement("canvas"); + this.canvas.width = this.canvas.height = size; + this.ctx = this.canvas.getContext("2d")!; + } + + static generate(seed: string): string { + if (!this.instance) this.instance = new PFP(); + return this.instance.create(seed); + } + + private create(seed: string): string { + const rand = this.prng(seed); + const ctx = this.ctx; + const size = this.canvas.width; + + // Clear canvas + ctx.clearRect(0, 0, size, size); + + // Generate a harmonious color palette with background variance + const palette = this.generatePalette(rand); + + // Draw background with variable color + ctx.fillStyle = palette.background; + ctx.fillRect(0, 0, size, size); + + // Select a composition based on the seed + const compositionIndex = Math.floor(rand() * 3); + + switch (compositionIndex) { + case 0: + this.drawSymmetricalPattern(rand, palette, size); + break; + case 1: + this.drawFractalPattern(rand, palette, size); + break; + case 2: + this.drawSpiralPattern(rand, palette, size); + break; + } + + return this.canvas.toDataURL(); + } + + private drawFractalPattern(rand: () => number, palette: Palette, size: number): void { + const ctx = this.ctx; + + // Variable scaling and overflow + const scale = 1 + rand() * 1.5; + const startX = (rand() * size) - size * (scale - 1) / 2; + const startY = (rand() * size) - size * (scale - 1) / 2; + + const maxDepth = 5 + Math.floor(rand() * 3); // Increase max depth to 5–7 + const initialRadius = (size / 2) * scale * 0.8; // Larger initial radius to occupy more space + + // Determine fractal type based on seed + const fractalType = Math.floor(rand() * 3); + + if (fractalType === 0) { + this.drawTreeFractal(rand, ctx, startX, startY, -Math.PI / 2, initialRadius, maxDepth, palette); + } else if (fractalType === 1) { + this.drawCircleFractal(rand, ctx, startX, startY, initialRadius, maxDepth, palette); + } else { + this.drawPolygonFractal(rand, ctx, startX, startY, initialRadius, maxDepth, palette); + } + } + + private drawTreeFractal(rand: () => number, ctx: CanvasRenderingContext2D, x: number, y: number, angle: number, length: number, depth: number, palette: Palette): void { + if (depth === 0 || length < 2) return; + + const x2 = x + Math.cos(angle) * length; + const y2 = y + Math.sin(angle) * length; + + ctx.strokeStyle = palette.colors[depth % palette.colors.length]; + ctx.lineWidth = depth; + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x2, y2); + ctx.stroke(); + + const branches = 2 + Math.floor(rand() * 2); // 2–3 branches + for (let i = 0; i < branches; i++) { + const newAngle = angle + (rand() - 0.5) * Math.PI / 2; // Vary angle between branches + const newLength = length * (0.5 + rand() * 0.3); // Reduce length + this.drawTreeFractal(rand, ctx, x2, y2, newAngle, newLength, depth - 1, palette); + } + } + + private drawCircleFractal(rand: () => number, ctx: CanvasRenderingContext2D, x: number, y: number, radius: number, depth: number, palette: Palette): void { + if (depth === 0 || radius < 2) return; + + ctx.fillStyle = palette.colors[depth % palette.colors.length]; + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.fill(); + + const childCount = 3 + Math.floor(rand() * 3); // 3–5 children + for (let i = 0; i < childCount; i++) { + const angle = (i / childCount) * Math.PI * 2 + rand() * Math.PI / childCount; + const newX = x + Math.cos(angle) * radius; + const newY = y + Math.sin(angle) * radius; + const newRadius = radius * 0.5; + this.drawCircleFractal(rand, ctx, newX, newY, newRadius, depth - 1, palette); + } + } + + private drawPolygonFractal(rand: () => number, ctx: CanvasRenderingContext2D, x: number, y: number, radius: number, depth: number, palette: Palette): void { + if (depth === 0 || radius < 3) return; + + const points = 3 + depth % 5; // Vary points between 3–7 + const rotation = rand() * Math.PI * 2; + + ctx.save(); + ctx.translate(x, y); + ctx.rotate(rotation); + + ctx.beginPath(); + for (let i = 0; i <= points; i++) { + const angle = (i / points) * Math.PI * 2; + const px = Math.cos(angle) * radius; + const py = Math.sin(angle) * radius; + ctx.lineTo(px, py); + } + ctx.closePath(); + + ctx.fillStyle = palette.colors[depth % palette.colors.length]; + ctx.fill(); + + ctx.restore(); + + // Recursively draw smaller polygons at each vertex + for (let i = 0; i < points; i++) { + const angle = (i / points) * Math.PI * 2 + rotation; + const newX = x + Math.cos(angle) * radius; + const newY = y + Math.sin(angle) * radius; + const newRadius = radius * 0.5; + this.drawPolygonFractal(rand, ctx, newX, newY, newRadius, depth - 1, palette); + } + } + + private drawSymmetricalPattern(rand: () => number, palette: Palette, size: number): void { + const ctx = this.ctx; + + // Variable scaling and overflow + const scale = 1 + rand() * 1.5; // Scale between 1x to 2.5x + const centerX = (rand() * size) - size * (scale - 1) / 2; + const centerY = (rand() * size) - size * (scale - 1) / 2; + + const layers = 3 + Math.floor(rand() * 3); // 3-5 layers + + for (let i = 0; i < layers; i++) { + const radius = (size / 2) * scale * ((layers - i) / layers); + const points = 5 + Math.floor(rand() * 4); // 5-8 points + const rotation = rand() * Math.PI * 2; + + ctx.save(); + ctx.translate(centerX, centerY); + ctx.rotate(rotation); + + ctx.beginPath(); + for (let j = 0; j <= points; j++) { + const angle = (j / points) * Math.PI * 2; + const x = Math.cos(angle) * radius; + const y = Math.sin(angle) * radius; + ctx.lineTo(x, y); + } + ctx.closePath(); + + ctx.fillStyle = palette.colors[i % palette.colors.length]; + ctx.fill(); + + ctx.restore(); + } + } + + private drawSpiralPattern(rand: () => number, palette: Palette, size: number): void { + const ctx = this.ctx; + + // Variable scaling and overflow + const scale = 1 + rand() * 1.5; + const centerX = (rand() * size) - size * (scale - 1) / 2; + const centerY = (rand() * size) - size * (scale - 1) / 2; + + const maxRadius = size / 2 * scale * 1.2; + const turns = 3 + Math.floor(rand() * 3); // 3-5 turns + const segments = 100; + + // Variable line thickness + const baseLineWidth = 2 + rand() * 6; // Line width between 2 to 8 + ctx.lineWidth = baseLineWidth; + + ctx.beginPath(); + for (let i = 0; i <= segments * turns; i++) { + const t = i / (segments * turns); + const angle = t * Math.PI * 2 * turns; + const radius = t * maxRadius; + const x = centerX + Math.cos(angle) * radius; + const y = centerY + Math.sin(angle) * radius; + ctx.lineTo(x, y); + } + ctx.strokeStyle = palette.colors[0]; + ctx.stroke(); + + // Draw shapes along the spiral + const shapeCount = 10 + Math.floor(rand() * 10); // 10-20 shapes + for (let i = 0; i < shapeCount; i++) { + const t = i / shapeCount; + const angle = t * Math.PI * 2 * turns; + const radius = t * maxRadius; + const x = centerX + Math.cos(angle) * radius; + const y = centerY + Math.sin(angle) * radius; + const shapeRadius = size / 20 * scale + rand() * size / 20 * scale; + const points = 3 + Math.floor(rand() * 5); + const rotation = rand() * Math.PI * 2; + + ctx.save(); + ctx.translate(x, y); + ctx.rotate(rotation); + + ctx.beginPath(); + for (let j = 0; j <= points; j++) { + const a = (j / points) * Math.PI * 2; + const px = Math.cos(a) * shapeRadius; + const py = Math.sin(a) * shapeRadius; + ctx.lineTo(px, py); + } + ctx.closePath(); + + ctx.fillStyle = palette.colors[(i + 1) % palette.colors.length]; + ctx.fill(); + + ctx.restore(); + } + } + + private generatePalette(rand: () => number): Palette { + const baseHue = rand() * 360; + const secondaryHue = (baseHue + 30 + rand() * 60) % 360; + + const colors = [ + `hsl(${baseHue}, 70%, 50%)`, + `hsl(${(baseHue + 120) % 360}, 70%, 50%)`, + `hsl(${(baseHue + 240) % 360}, 70%, 50%)`, + ]; + + const backgroundColorIndex = Math.floor(rand() * colors.length); + const background = colors.splice(backgroundColorIndex, 1)[0]; + + return { + background, + colors, + }; + } + + private prng(seed: string): () => number { + let h = 2166136261 >>> 0; + for (let i = 0; i < seed.length; i++) { + h ^= seed.charCodeAt(i); + h = Math.imul(h, 16777619); + } + return () => { + h += h << 13; h ^= h >>> 7; + h += h << 3; h ^= h >>> 17; + h += h << 5; + return (h >>> 0) / 4294967295; + }; + } +} + +interface Palette { + background: string; + colors: string[]; +} diff --git a/apps/gui/src/routes/+layout.svelte b/apps/gui/src/routes/+layout.svelte index 4265dfd7..728a3931 100755 --- a/apps/gui/src/routes/+layout.svelte +++ b/apps/gui/src/routes/+layout.svelte @@ -4,14 +4,19 @@ import { goto } from '$app/navigation'; import { onMount } from 'svelte'; import Header from '$lib/components/blocks/Header.svelte'; + // import { bootstrap, destroy } from '$lib/utils/lifecycle.js'; - onMount(() => { + onMount(async () => { const userAgent = navigator.userAgent || navigator.vendor || window.opera; const isMobile = /Android|iPhone|iPad|iPod|Opera Mini|IEMobile|Mobile/i.test(userAgent); if (isMobile && $page.url.pathname !== '/mobile') { goto('/mobile'); // Redirect to the mobile-specific template } + if (typeof window === 'undefined' || typeof navigator === 'undefined') return; + const bootstrap = (await import('$lib/utils/lifecycle')).bootstrap; + bootstrap() + }); let { children } = $props(); diff --git a/apps/gui/src/routes/+page.svelte b/apps/gui/src/routes/+page.svelte index 6d2569ab..69482f55 100755 --- a/apps/gui/src/routes/+page.svelte +++ b/apps/gui/src/routes/+page.svelte @@ -2,21 +2,22 @@ import { onMount, onDestroy } from 'svelte'; import Stats from '$lib/components/blocks/Stats.svelte'; - import Console from '$lib/components/blocks/console/Console.svelte'; + import DataTable from '$lib/components/blocks/table/DataTable.svelte'; import { bootstrap, destroy } from '$lib/utils/lifecycle.js'; + import { relayAggregates } from '$lib/stores/checks.js'; export const prerender = true; onMount(() => { if (typeof window === 'undefined' || typeof navigator === 'undefined') return; - bootstrap(); + // bootstrap(); }); // onDestroy(destroy);
- - +
+ {#if $monitors.length} + + + + {/if} + -
- - -
\ No newline at end of file + \ No newline at end of file diff --git a/apps/gui/src/routes/preferences/+page.svelte b/apps/gui/src/routes/preferences/+page.svelte index 5804f149..ee211d79 100644 --- a/apps/gui/src/routes/preferences/+page.svelte +++ b/apps/gui/src/routes/preferences/+page.svelte @@ -4,6 +4,7 @@ import { instance, destroy } from '$lib/utils/lifecycle.js'; import { wipeCache } from '$lib/utils/cache.js'; import type Nip66 from '@nostrwatch/nip66' + import Stats from '$lib/components/blocks/Stats.svelte'; let Nip66Instance: Nip66 | null; @@ -19,4 +20,6 @@ -{/if} \ No newline at end of file +{/if} + + \ No newline at end of file diff --git a/apps/gui/src/routes/relays/+page.svelte b/apps/gui/src/routes/relays/+page.svelte new file mode 100644 index 00000000..e7480187 --- /dev/null +++ b/apps/gui/src/routes/relays/+page.svelte @@ -0,0 +1,13 @@ + + +
+ + +
diff --git a/apps/gui/src/routes/relays/[relay]/+page.svelte b/apps/gui/src/routes/relays/[relay]/+page.svelte new file mode 100644 index 00000000..e69de29b diff --git a/libraries/nip66/adapters/cache/DexieETLAdapter/src/DexieAdapter.ts b/libraries/nip66/adapters/cache/DexieETLAdapter/src/DexieAdapter.ts index 504688a7..4c5e11e0 100644 --- a/libraries/nip66/adapters/cache/DexieETLAdapter/src/DexieAdapter.ts +++ b/libraries/nip66/adapters/cache/DexieETLAdapter/src/DexieAdapter.ts @@ -56,7 +56,7 @@ class DexieAdapter extends CacheAdapter implements ICacheAdapter { return this._idb; } - async newWorker(): Promise { + async newWorker(): Promise { return DexieWorker(); } @@ -147,8 +147,8 @@ class DexieAdapter extends CacheAdapter implements ICacheAdapter { async patchMonitor(monitor: Partial): Promise { return new Promise((resolve, reject) => { - if(!monitor?.id) return resolve(void 0) - this.idb.monitors.update(monitor.id, monitor) + if(!monitor?.pubkey) return resolve(void 0) + this.idb.monitors.update(monitor.pubkey, monitor) .then(() => resolve(void 0)) .catch(reject) }) diff --git a/libraries/nip66/adapters/cache/NostrSqliteAdapter/src/NostrSqliteAdapter.ts b/libraries/nip66/adapters/cache/NostrSqliteAdapter/src/NostrSqliteAdapter.ts index 8fc0e468..9b6cc62e 100644 --- a/libraries/nip66/adapters/cache/NostrSqliteAdapter/src/NostrSqliteAdapter.ts +++ b/libraries/nip66/adapters/cache/NostrSqliteAdapter/src/NostrSqliteAdapter.ts @@ -27,7 +27,9 @@ export class NostrSqliteAdapter extends CacheAdapter implements INostrSqliteAdap } destroy(){ - this.relay.worker.terminate() + if(this.relay.worker instanceof Worker){ + this.relay.worker.terminate() + } } setup(){} @@ -52,7 +54,7 @@ export class NostrSqliteAdapter extends CacheAdapter implements INostrSqliteAdap return generateSubId(randomInRange(11, 24)) } - async newWorker(): Promise { + async newWorker(): Promise { let worker; if(this.overloadWorker) { console.log(`NostrSqliteAdapter: using overloadWorker`) @@ -70,7 +72,10 @@ export class NostrSqliteAdapter extends CacheAdapter implements INostrSqliteAdap ); } console.log('NostrSqliteAdapter: newWorker', worker) - if(!(worker instanceof Worker)) throw new Error('NostrSqliteAdapter: Worker is not a Worker instance') + // if(!(worker instanceof Worker) && !(worker instanceof SharedWorker)) throw new Error('NostrSqliteAdapter: Worker is not a Worker or SharedWorker instance') + if(worker instanceof SharedWorker) { + worker.port.start() + } this.relay = new WorkerRelayInterface(worker); this._ready = true return this.relay.worker; @@ -84,25 +89,6 @@ export class NostrSqliteAdapter extends CacheAdapter implements INostrSqliteAdap console.log('NostrSqlLite: ready') } -// async newWorker(): Promise { - // let worker; - // if (import.meta.env.DEV) { - // /* @vite-ignore */ - // worker = new Worker(new URL('./workers/nostrsqlite.worker.js', import.meta.url), { type: 'module' }); - // // worker = new URL("./workers/nostrsqlite.worker.js", import.meta.url) - // } else { - // worker = new Worker( - // new URL("./workers/nostrsqlite.worker.js", import.meta.url), - // { type: 'module' } - // ); - // } - // this.relay = new WorkerRelayInterface(worker); - // this._ready = true - // return this.relay.worker; - // } - - - async addEvent(event: IEvent): Promise { this.EVENT(event); } diff --git a/libraries/nip66/adapters/cache/NostrSqliteAdapter/src/NostrSqliteWorker.ts b/libraries/nip66/adapters/cache/NostrSqliteAdapter/src/NostrSqliteWorker.ts index 575be98e..1e0bb00f 100644 --- a/libraries/nip66/adapters/cache/NostrSqliteAdapter/src/NostrSqliteWorker.ts +++ b/libraries/nip66/adapters/cache/NostrSqliteAdapter/src/NostrSqliteWorker.ts @@ -5,97 +5,107 @@ import type { IEvent } from '@nostrwatch/nip66/interfaces' import { handleMsg as relayHandler, insertBatch, WorkerState, relayInit, messageChannelInit, relayEvent, InitAargs, relayWipe } from '@nostrwatch/worker-relay/dist/worker-utils' export class NostrSqliteWorker extends AdapterCacheWorker { - state: WorkerState = { - self: this.mainThread as DedicatedWorkerGlobalScope, + state: WorkerState = { + self: self as SharedWorkerGlobalScope | DedicatedWorkerGlobalScope, eventWriteQueue: [], relay: undefined, messageChannel: undefined, - insertBatchEvery: 1000, insertBatchSize: 25, - lastBatch: 0 - } + lastBatch: 0, + }; relay = relayHandler; - private batcher: ReturnType = setTimeout(() => { insertBatch(this.state) }, 1000) - - constructor( options: WorkerOptions ){ - super(options) - console.log('NostrSqliteWorker constructor') - this.setupHandlers() + + private batcher: ReturnType = setTimeout(() => insertBatch(this.state), this.state.insertBatchEvery); + + constructor(options: WorkerOptions) { + super(options); + console.log('NostrSqliteWorker', this.state) + console.log('NostrSqliteWorker constructor'); + this._setupHandlers() } - destroy(){ - clearTimeout(this.batcher) + destroy() { + clearTimeout(this.batcher); + if (this.state.messageChannel) { + this.state.messageChannel.close(); + } + console.log('NostrSqliteWorker destroyed'); } - - async setup(command: AdapterWorkerMessage){ - - console.log(`NostrSqliteWorker: setup()`, command) + + async setup(command: AdapterWorkerMessage) { + console.log(`NostrSqliteWorker: setup()`, command); const conf: InitAargs = { databasePath: "relay.db", - insertBatchSize: this.state.insertBatchSize + insertBatchSize: this.state.insertBatchSize, + }; + + try { + await relayInit(this.state, conf); + } catch (err) { + console.error('Setup failed, retrying with wipe:', err); + await relayWipe(this.state); + await this.setup(command); } - await relayInit(this.state, conf).catch( async () => { - await relayWipe(this.state) - this.setup(command); - }) - // await relayWipe(this.state) - if(command?.channelPort){ - this.state.messageChannel = command.channelPort - this.setupChannelHandlers() + + if (command?.channelPort) { + this.state.messageChannel = command.channelPort; + this.setupChannelHandlers(); } - console.log(this.channel) } - setupHandlers(){ - console.log('NostrSqliteWorker: setupHandlers()') - if(!this?.mainThread) return console.warn('NostrSqliteWorker: mainThread not defined') - this.mainThread.onmessage = async (message: MessageEvent) => { - console.log(`NostrSqliteWorker: From Main Thread:`, message.data) - if(message.data.type === 'setup'){ - console.log('NostrSqliteWorker: setup()') - await this.__setup(message.data) - return - } - else { - this.fromMainThread(message); - } - } - if(this.state?.messageChannel){ - this.state.messageChannel.onmessage = async (message: MessageEvent) => { - console.log(`NostrSqliteWorker: Over MessageChannel: ${message.data.cmd} -> ${message.data.args}`) - this.relay(this.state, message) + //do nothing. + setupHandlers(){} + + _setupHandlers() { + this.state.self = typeof SharedWorkerGlobalScope !== 'undefined' && this.mainThread instanceof SharedWorkerGlobalScope + ? this.mainThread as SharedWorkerGlobalScope + : this.mainThread as DedicatedWorkerGlobalScope; + + const onmessage = async (message: MessageEvent) => { + console.log(`NostrSqliteWorker: Received:`, message.data); + if (message.data.type === 'setup') { + await this.setup(message.data); + } else { + this.fromMainThread(message); } + }; + + if (this.state.self instanceof DedicatedWorkerGlobalScope) { + console.log('Running in DedicatedWorkerGlobalScope'); + this.state.self.onmessage = onmessage; + } else if (this.state.self instanceof SharedWorkerGlobalScope) { + console.log('Running in SharedWorkerGlobalScope'); + this.state.self.onconnect = (event: MessageEvent) => { + const port = event.ports[0]; + port.onmessage = onmessage; + }; } - } - setupChannelHandlers(){ - if(!this.state.messageChannel) return console.warn('channel not defined') + setupChannelHandlers() { + if (!this.state.messageChannel) return console.warn('Channel not defined'); this.state.messageChannel.onmessage = (message: MessageEvent) => { - const command = message.data as AdapterCacheWorkerCommand; - this.onChannelMessage(command); - } - this.state.messageChannel.onmessageerror = this.onMessageError - } + const command = message.data as AdapterCacheWorkerCommand; + this.onChannelMessage(command); + }; + this.state.messageChannel.onmessageerror = this.onMessageError; + } fromMainThread(ev: MessageEvent) { - console.log(`CacheWorker: From Main Thread:`, ev) - this.relay(this.state, ev) + console.log(`CacheWorker: From Main Thread:`, ev); + if(this.state.relay) + this.relay(this.state, ev); } - + async addEvent(nostrEvent: IEvent) { - // console.log(`NostrSqliteWorker: Over MessageChannel: addEvent() -> ${nostrEvent.id}`) - relayEvent(this.state, nostrEvent) + relayEvent(this.state, nostrEvent); } - + async addEvents(nostrEvents: IEvent[]) { - console.log(`NostrSqliteWorker: Over MessageChannel: relay.eventBatch() -> ${nostrEvents.length}`) - this?.state?.relay?.eventBatch?.(nostrEvents) - // for(const event of nostrEvents){ - // this.addEvent(event) - // } + console.log(`NostrSqliteWorker: relay.eventBatch() -> ${nostrEvents.length}`); + this?.state?.relay?.eventBatch?.(nostrEvents); } } -export default NostrSqliteWorker; \ No newline at end of file +export default NostrSqliteWorker; diff --git a/libraries/nip66/adapters/cache/NostrSqliteAdapter/src/workers/nostrsqlite.worker.ts b/libraries/nip66/adapters/cache/NostrSqliteAdapter/src/workers/nostrsqlite.worker.ts index a52506f8..707efb22 100644 --- a/libraries/nip66/adapters/cache/NostrSqliteAdapter/src/workers/nostrsqlite.worker.ts +++ b/libraries/nip66/adapters/cache/NostrSqliteAdapter/src/workers/nostrsqlite.worker.ts @@ -2,11 +2,11 @@ import { NostrSqliteWorker } from '../NostrSqliteWorker'; -import { IWorkerGlobalScope } from "@nostrwatch/nip66/interfaces"; -import { CacheWorker, ICacheAdapterWorker } from '@nostrwatch/nip66/factoryWorker'; +import { ISharedWorkerGlobalScope, IWorkerGlobalScope, } from "@nostrwatch/nip66/interfaces"; +import { CacheWorker } from '@nostrwatch/nip66/factoryWorker'; -const $self = self as unknown as IWorkerGlobalScope +console.log(`NostrSqliteWorker: Shared Worker loaded.`) -console.log(`NostrSqliteWorker: Worker loaded.`) +const $self = self as unknown as IWorkerGlobalScope | ISharedWorkerGlobalScope; -const worker: ICacheAdapterWorker = CacheWorker(NostrSqliteWorker, $self) \ No newline at end of file +CacheWorker(NostrSqliteWorker, $self) \ No newline at end of file diff --git a/libraries/nip66/adapters/cache/SurrealDbAdapter/src/SurrealDbAdapter.ts b/libraries/nip66/adapters/cache/SurrealDbAdapter/src/SurrealDbAdapter.ts index b2367dde..4f4ac346 100644 --- a/libraries/nip66/adapters/cache/SurrealDbAdapter/src/SurrealDbAdapter.ts +++ b/libraries/nip66/adapters/cache/SurrealDbAdapter/src/SurrealDbAdapter.ts @@ -41,7 +41,7 @@ export class SurrealDbAdapter extends CacheAdapter implements ICacheAdapter { } } - async newWorker(channelPort: MessagePort): Promise { + async newWorker(channelPort: MessagePort): Promise { return SurrealWorker(); } diff --git a/libraries/nip66/adapters/websocket/NostrToolsAdapter/src/NostrToolsAdapter.ts b/libraries/nip66/adapters/websocket/NostrToolsAdapter/src/NostrToolsAdapter.ts index 8617d0b3..1b5fa302 100644 --- a/libraries/nip66/adapters/websocket/NostrToolsAdapter/src/NostrToolsAdapter.ts +++ b/libraries/nip66/adapters/websocket/NostrToolsAdapter/src/NostrToolsAdapter.ts @@ -11,7 +11,7 @@ export class NostrToolsAdapter extends WebsocketAdapter { this.connect(); } - async newWorker(): Promise { + async newWorker(): Promise { return NostrToolsWorker(); } } \ No newline at end of file diff --git a/libraries/nip66/src/core/Adapter.ts b/libraries/nip66/src/core/Adapter.ts index 8b3b2955..a7ad4d39 100644 --- a/libraries/nip66/src/core/Adapter.ts +++ b/libraries/nip66/src/core/Adapter.ts @@ -18,7 +18,7 @@ export interface IAdapter { workers?: Workers; useWorker: boolean; - newWorker(): Promise; + newWorker(): Promise; // bindWorkerHandlers(): void; // _onMessage(event: MessageEvent): void; @@ -44,15 +44,20 @@ export abstract class Adapter { private _ls: LocalStorageWrapper; private _workers?: Workers - protected _overloadWorker?: Worker; + protected _overloadWorker?: Worker | SharedWorker; useWorker: boolean = true; - constructor( worker?: Worker | URL ) { + constructor( worker?: Worker | SharedWorker | URL, shared?: boolean ) { if (worker instanceof Worker) { this.overloadWorker = worker; } else if(worker instanceof URL) { - this.overloadWorker = new Worker(worker, { type: "module" }); + if(shared) { + this.overloadWorker = new SharedWorker(worker, { type: "module" }); + } + else { + this.overloadWorker = new Worker(worker, { type: "module" }); + } } this._ls = new LocalStorageWrapper(['nip66', this.slug]) } @@ -64,11 +69,11 @@ export abstract class Adapter { throw new Error('Method not implemented.'); } - get overloadWorker(): Worker | undefined { + get overloadWorker(): Worker | SharedWorker | undefined { return this._overloadWorker; } - private set overloadWorker(worker: Worker) { + private set overloadWorker(worker: Worker | SharedWorker) { this._overloadWorker = worker; } diff --git a/libraries/nip66/src/core/AdapterWorker.ts b/libraries/nip66/src/core/AdapterWorker.ts index 58733aa1..4bef84d4 100644 --- a/libraries/nip66/src/core/AdapterWorker.ts +++ b/libraries/nip66/src/core/AdapterWorker.ts @@ -1,5 +1,6 @@ +/// -import { IEvent, IWorkerCommand, IWorkerGlobalScope } from "../interfaces"; +import { IEvent, ISharedWorkerGlobalScope, IWorkerCommand, IWorkerGlobalScope } from "../interfaces"; import { NostrEvent } from "nostr-tools"; import { Workers } from "./Workers"; import { AdapterMessage } from "./Adapter"; @@ -14,8 +15,8 @@ export interface AdapterWorkerMessage extends AdapterMessage {} export interface AdapterWorkerCommand extends AdapterWorkerMessage { command?: string; - channelPort?: MessagePort - mainThread?: IWorkerGlobalScope; + channelPort?: MessagePort + mainThread?: IWorkerGlobalScope | ISharedWorkerGlobalScope; } export enum AdapterWorkerResultType { @@ -31,29 +32,23 @@ export interface AdapterWorkerResult extends AdapterWorkerMessage { } export interface WorkerOptions { - mainThread?: IWorkerGlobalScope; + mainThread?: IWorkerGlobalScope | ISharedWorkerGlobalScope; channelPort?: MessagePort; } export class AdapterWorker { - private _context?: WorkerContext - private _mainThread?: IWorkerGlobalScope; + private _mainThread?: IWorkerGlobalScope | ISharedWorkerGlobalScope; private _channelPort?: MessagePort; constructor( options?: WorkerOptions ){ if(!options) return console.log('AdapterWorker', options) - this.setContext(options) - // console.log('mainThread?:', this.mainThread) + this._mainThread = options?.mainThread + this._channelPort = options?.channelPort this.setupHandlers() } - - get context(): WorkerContext | undefined { - return this._context - } - - get mainThread(): IWorkerGlobalScope | undefined { + get mainThread(): IWorkerGlobalScope | ISharedWorkerGlobalScope | undefined { return this._mainThread; } @@ -65,26 +60,6 @@ export class AdapterWorker { return this._channelPort } - setContext(options?: WorkerOptions){ - console.log('AdapterWorker.setContext', this?.constructor?.name,options) - if(!options) { - options = { - mainThread: this?.mainThread, - channelPort: this?.channel, - // sharedWorkerPort: this?.sharedWorker - } - } - if( options?.mainThread ) { - this._context = WorkerContext.Worker - this._mainThread = options.mainThread - } - if( options?.channelPort ) { - this._context = WorkerContext.Worker - this._channelPort = options.channelPort - } - console.log(this.constructor.name, 'context:', this._context) - } - //overload this. async setup(command: AdapterWorkerMessage): Promise{ console.warn(`${this.constructor.name} setup() method not implemented`) @@ -122,7 +97,7 @@ export class AdapterWorker { setupHandlers(){ console.log('AdapterWorker: setupHandlers()') if(!this?.mainThread) return console.warn('mainThread not defined') - this.mainThread.onmessage = async (message: MessageEvent) => { + const onmessage = async (message: MessageEvent) => { const command = message.data as AdapterWorkerMessage; console.log(`[Worker:${this.constructor.name}] mainthread.onmessage`, command) this.listenPingPong('mainthread', command) @@ -132,22 +107,15 @@ export class AdapterWorker { } this.onMainThreadMessage(command); } - console.log('', ) - } - - // postMessage( command: AdapterWorkerMessage, where: WorkerContext = WorkerContext.MainThread ) { - // switch(where as WorkerContext){ - // case WorkerContext.MainThread: - // this.postMessageAdapter(command) - // break; - // case WorkerContext.Worker: - // this.postMessageWorker(command) - // break; - // case WorkerContext.DedicatedWorker: - // this.postMessageDedicatedWorker(command) - // break; - // } - // } + if(this.mainThread instanceof DedicatedWorkerGlobalScope){ + console.log('Running in DedicatedWorkerGlobalScope'); + (this.mainThread as IWorkerGlobalScope).onmessage = onmessage + } + else if(this.mainThread instanceof SharedWorkerGlobalScope){ + (this.mainThread as ISharedWorkerGlobalScope).port.onmessage = onmessage + } + } + command(destination: string | string[], resultType: AdapterWorkerResultType, result: any){ console.log('AdapterWebsocketWorker: command', destination, resultType, result) diff --git a/libraries/nip66/src/core/Base.ts b/libraries/nip66/src/core/Base.ts index f696ecba..2de2b907 100644 --- a/libraries/nip66/src/core/Base.ts +++ b/libraries/nip66/src/core/Base.ts @@ -151,20 +151,11 @@ export default class { return this.websocketAdapter; } - get wsWorker(): Worker | undefined { - return this?.websocketAdapter?.workers?.websocketDedicated; + get wsWorker(): Worker | SharedWorker | undefined { + return this?.websocketAdapter?.workers?.websocket; } - - get wsSharedWorker(): SharedWorker | undefined { - return this?.websocketAdapter?.workers?.websocketShared; - } - get cacheWorker(): SharedWorker | Worker | undefined { - return this?.cacheAdapter?.workers?.cacheDedicated; - } - - get cacheSharedWorker(): SharedWorker | undefined { - return this?.cacheAdapter?.workers?.cacheShared; + return this?.cacheAdapter?.workers?.cache; } async bootstrap(){ diff --git a/libraries/nip66/src/core/CacheAdapter.ts b/libraries/nip66/src/core/CacheAdapter.ts index 4681ab40..2ee96c5d 100644 --- a/libraries/nip66/src/core/CacheAdapter.ts +++ b/libraries/nip66/src/core/CacheAdapter.ts @@ -136,29 +136,33 @@ export class CacheAdapter extends Adapter { static type = 'CacheAdapter'; readonly slug: string = 'CacheAdapter:unset'; - constructor(worker?: Worker | URL) { + constructor(worker?: Worker | SharedWorker | URL) { super(worker) StateManager.on('destroy', () => { - this.dedicatedWorker?.terminate() + if(this.worker instanceof Worker) + this.worker?.terminate() }) } - get dedicatedWorker(): Worker | undefined { - return this.workers?.cacheDedicated + get worker(): Worker | SharedWorker | undefined { + return this.workers?.cache } - get sharedWorker(): SharedWorker | undefined { - return this.workers?.cacheShared - } protected bindWorkerHandlers(): void { - if(!this?.workers?.cacheDedicated) return console.warn('[CacheAdapter] Error binding worker handlers: no worker found') - this.workers.cacheDedicated.onmessage = this._onMessage.bind(this); - this.workers.cacheDedicated.onerror = this._onError.bind(this) + if(!this?.workers?.cache) return console.warn('[CacheAdapter] Error binding worker handlers: no worker found') + if(this.workers.cache instanceof Worker) + this.workers.cache.onmessage = this._onMessage.bind(this); + else if(this.workers.cache instanceof SharedWorker) + this.workers.cache.port.onmessage = this._onMessage.bind(this); + this.workers.cache.onerror = this._onError.bind(this) } ping(): void { console.log(`[CacheAdapter:${this.constructor.name}] o/o SEND: PING -> cacheWorker`) - this.workers?.cacheDedicated?.postMessage({type: 'ping'}) + if(this.workers?.cache instanceof Worker) + this.workers?.cache?.postMessage({type: 'ping'}) + else if (this.workers?.cache instanceof SharedWorker) + this.workers?.cache?.port.postMessage({type: 'ping'}) } } \ No newline at end of file diff --git a/libraries/nip66/src/core/WebsocketAdapter.ts b/libraries/nip66/src/core/WebsocketAdapter.ts index a91ce269..6d73bd8d 100644 --- a/libraries/nip66/src/core/WebsocketAdapter.ts +++ b/libraries/nip66/src/core/WebsocketAdapter.ts @@ -93,19 +93,16 @@ export class WebsocketAdapter extends Adapter implements IWebsocketAdapter { private _subscriptions: Set = new Set() private _hashData: Record = {} - constructor(worker?: Worker | URL) { + constructor(worker?: Worker | SharedWorker | URL) { super(worker) StateManager.on('destroy', () => { - this.worker?.terminate() + if(this.worker instanceof Worker) + this.worker?.terminate() }) } - get worker(): Worker | undefined { - return this.workers?.websocketDedicated - } - - get sharedWorker(): SharedWorker | undefined { - return this.workers?.websocketShared + get worker(): Worker | SharedWorker | undefined { + return this.workers?.websocket } get subscriptions(): Set { @@ -117,14 +114,15 @@ export class WebsocketAdapter extends Adapter implements IWebsocketAdapter { terminate(): void {} unsubscribe(subId?: string): void {} - newWorker(): Promise { + newWorker(): Promise { throw new Error('Method not implemented.'); } protected bindWorkerHandlers(): void { - if(!this?.workers?.websocketDedicated) return console.warn('[WebsocketAdapter] Error binding worker handlers: no worker found') - this.workers.websocketDedicated.onmessage = this._onMessage.bind(this); - this.workers.websocketDedicated.onerror = this._onError.bind(this) + if(!this?.workers?.websocket) return console.warn('[WebsocketAdapter] Error binding worker handlers: no worker found') + if(this.workers.websocket instanceof Worker) + this.workers.websocket.onmessage = this._onMessage.bind(this); + this.workers.websocket.onerror = this._onError.bind(this) } onMessage(response: WebsocketResponseBody): void { @@ -180,7 +178,10 @@ export class WebsocketAdapter extends Adapter implements IWebsocketAdapter { } console.log(`[WebsocketAdapter:${this.constructor.name}] o/o SEND: ${message.use} -> websocketWorker`) this.subscriptions.add(hash) - this.worker.postMessage(message) + if(this.worker instanceof Worker) + this.worker.postMessage(message) + else if(this.worker instanceof SharedWorker) + this.worker.port.postMessage(message) return hash } @@ -253,7 +254,13 @@ export class WebsocketAdapter extends Adapter implements IWebsocketAdapter { ping(): void { console.log(`[WebsocketAdapter:${this.constructor.name}] o/o SEND: PING -> websocketWorker`) - this.workers?.websocketDedicated?.postMessage({type: 'ping'}) + if(this.worker instanceof Worker) { + (this.workers?.websocket as Worker)?.postMessage({type: 'ping'}) + } + else if(this.worker instanceof SharedWorker) { + (this.workers?.websocket as SharedWorker)?.port.postMessage({type: 'ping'}) + } + } } \ No newline at end of file diff --git a/libraries/nip66/src/core/Workers.ts b/libraries/nip66/src/core/Workers.ts index 0857a434..72ba70d3 100644 --- a/libraries/nip66/src/core/Workers.ts +++ b/libraries/nip66/src/core/Workers.ts @@ -8,8 +8,8 @@ export class Workers { private _channel: MessageChannel = new MessageChannel(); - private _websocket?: Worker; - private _cache?: Worker; + private _websocket?: Worker | SharedWorker; + private _cache?: Worker | SharedWorker; private _ready: boolean = false; @@ -22,36 +22,28 @@ export class Workers { } get websocketWorkers(): Partial { - const { websocketShared, websocketDedicated, channel } = this; - return { websocketShared, websocketDedicated, channel }; + const { websocket, channel } = this; + return { websocket, channel }; } get cacheWorkers(): Partial { - const { cacheShared, cacheDedicated, channel } = this; - return { cacheShared, cacheDedicated, channel }; + const { cache, channel } = this; + return { cache, channel }; } - get websocketShared(): SharedWorker | undefined { - return this._websocketShared; - } - - get cacheShared(): SharedWorker | undefined { - return this._cacheShared; - } - - get websocketDedicated(): Worker | undefined { + get websocket(): Worker | SharedWorker | undefined { return this._websocket; } - set websocketDedicated(worker: Worker | undefined){ + set websocket(worker: Worker | SharedWorker | undefined){ this._websocket = worker; } - get cacheDedicated(): Worker | undefined { + get cache(): Worker | SharedWorker | undefined { return this._cache; } - set cacheDedicated(worker: Worker | undefined){ + set cache(worker: Worker | SharedWorker | undefined){ this._cache = worker; } @@ -61,28 +53,41 @@ export class Workers { async setupWorkers(adapters: IAdaptersArgument){ if(adapters.cacheAdapter.useWorker){ - this.cacheDedicated = (await adapters.cacheAdapter.newWorker()) as Worker - console.log('cacheAdapter.newWorker()', this.cacheDedicated) + this.cache = (await adapters.cacheAdapter.newWorker()) as Worker | SharedWorker + console.log('cacheAdapter.newWorker()', this.cache) } if(adapters.websocketAdapter.useWorker){ - this.websocketDedicated = (await adapters.websocketAdapter.newWorker()) as Worker - console.log('websocketAdapter.newWorker()', this.websocketDedicated) + this.websocket = (await adapters.websocketAdapter.newWorker()) as Worker | SharedWorker + console.log('websocketAdapter.newWorker()', this.websocket) } if(adapters.cacheAdapter.useWorker && adapters.websocketAdapter.useWorker){ const cacheAdapterChannelPort = this.channel.port2; const websocketAdapterChannelPort = this.channel.port1; - if(this.cacheDedicated?.postMessage){ + if(this.cache instanceof Worker || this.cache instanceof SharedWorker){ const message = {type: 'setup', channelPort: cacheAdapterChannelPort} - console.log(`[Workers] setupWorkers() -> cacheDedicated.postMessage()`, this.cacheDedicated, message) - this.cacheDedicated.postMessage(message, [cacheAdapterChannelPort]); + if(this.cache instanceof Worker) { + console.log(`[Workers] setupWorkers() -> cache.postMessage() to Worker`, this.cache, message) + this.cache.postMessage(message, [cacheAdapterChannelPort]); + } + else if (this.cache instanceof SharedWorker) { + console.log(`[Workers] setupWorkers() -> cache.port.postMessage() to SharedWorker`, this.cache, message) + this.cache.port.postMessage(message, [cacheAdapterChannelPort]); + } + } else { console.warn('Cache Worker not defined') } - if(this.websocketDedicated?.postMessage){ + if(this.websocket instanceof Worker || this.websocket instanceof SharedWorker){ const message = {type: 'setup', channelPort: websocketAdapterChannelPort} - console.log(`[Workers] setupWorkers() -> websocketDedicated.postMessage()`, message) - this.websocketDedicated?.postMessage(message, [websocketAdapterChannelPort]); + if(this.websocket instanceof Worker) { + console.log(`[Workers] setupWorkers() [websocket] -> websocket.postMessage() to Worker`, message) + this.websocket.postMessage(message, [websocketAdapterChannelPort]); + } + else if(this.websocket instanceof SharedWorker) { + console.log(`[Workers] setupWorkers() [websocket] -> websocket.port.postMessage() to SharedWorker`, message) + this.websocket.port.postMessage(message, [websocketAdapterChannelPort]); + } } else { console.warn('Websocket Worker not defined') diff --git a/libraries/nip66/src/factory/cache.worker.ts b/libraries/nip66/src/factory/cache.worker.ts index 3db6c2ed..7363f58c 100644 --- a/libraries/nip66/src/factory/cache.worker.ts +++ b/libraries/nip66/src/factory/cache.worker.ts @@ -1,5 +1,5 @@ import { AdapterWorker } from '@base/core'; -import type { IWorkerGlobalScope } from '@interfaces/index'; +import type { ISharedWorkerGlobalScope, IWorkerGlobalScope } from '@interfaces/index'; import type { IAdapterWorkerCommand } from '@interfaces/IAdapterWorkerCommand'; export interface ICacheAdapterWorker { @@ -8,6 +8,6 @@ export interface ICacheAdapterWorker { onMessageError: (error: any) => void; } -export default (_AdapterWorker_: typeof AdapterWorker, root: IWorkerGlobalScope): any => { - return new _AdapterWorker_( { mainThread: root } ); +export default (_AdapterWorker_: typeof AdapterWorker, mainThread: IWorkerGlobalScope | ISharedWorkerGlobalScope): any => { + return new _AdapterWorker_( { mainThread } ); } \ No newline at end of file diff --git a/libraries/nip66/src/index.ts b/libraries/nip66/src/index.ts index 27ae3ce1..8333a017 100644 --- a/libraries/nip66/src/index.ts +++ b/libraries/nip66/src/index.ts @@ -2,6 +2,7 @@ import Base from './core/Base'; export { StateManager } from './managers/StateManager'; export { CacheAdapter, type ICacheAdapter, type GeohashOptions } from './core/CacheAdapter'; +export { MonitorManager, type MonitorPriorities, type MonitorPriority } from './managers/MonitorManager'; export * from './factory/cache.shared.worker'; export type * from './interfaces/index'; diff --git a/libraries/nip66/src/interfaces/IAdapterWorkerCommand.ts b/libraries/nip66/src/interfaces/IAdapterWorkerCommand.ts index efd52b50..46934144 100644 --- a/libraries/nip66/src/interfaces/IAdapterWorkerCommand.ts +++ b/libraries/nip66/src/interfaces/IAdapterWorkerCommand.ts @@ -1,7 +1,8 @@ +import { ISharedWorkerGlobalScope } from "./ISharedWorkerGlobalScope"; import { IWorkerCommand } from "./IWorkerCommand"; import { IWorkerGlobalScope } from "./IWorkerGlobalScope"; export interface IAdapterWorkerCommand extends IWorkerCommand { channelPort?: MessagePort - mainThread?: IWorkerGlobalScope; + mainThread?: IWorkerGlobalScope | ISharedWorkerGlobalScope; } \ No newline at end of file diff --git a/libraries/nip66/src/interfaces/ICacheAdapterSharedWorkerCommand.ts b/libraries/nip66/src/interfaces/ICacheAdapterSharedWorkerCommand.ts index 8821157c..9acbb8c1 100644 --- a/libraries/nip66/src/interfaces/ICacheAdapterSharedWorkerCommand.ts +++ b/libraries/nip66/src/interfaces/ICacheAdapterSharedWorkerCommand.ts @@ -1,8 +1,8 @@ +import { ISharedWorkerGlobalScope } from "./ISharedWorkerGlobalScope"; import { IWorkerCommand } from "./IWorkerCommand"; import { IWorkerGlobalScope } from "./IWorkerGlobalScope"; export interface ICacheAdapterSharedWorkerCommand extends IWorkerCommand { channelPort?: MessagePort - mainThread?: IWorkerGlobalScope; - sharedWorkerPort?: MessagePort; + mainThread?: IWorkerGlobalScope | ISharedWorkerGlobalScope; } \ No newline at end of file diff --git a/libraries/nip66/src/interfaces/ISharedWorkerGlobalScope.ts b/libraries/nip66/src/interfaces/ISharedWorkerGlobalScope.ts index 233455c8..d38517c6 100644 --- a/libraries/nip66/src/interfaces/ISharedWorkerGlobalScope.ts +++ b/libraries/nip66/src/interfaces/ISharedWorkerGlobalScope.ts @@ -2,4 +2,5 @@ export interface ISharedWorkerGlobalScope { onconnect: ((this: ISharedWorkerGlobalScope, ev: MessageEvent) => any) | null; addEventListener(type: 'connect', listener: (this: ISharedWorkerGlobalScope, ev: MessageEvent) => any, options?: boolean | AddEventListenerOptions): void; postMessage(message: any, transfer?: Transferable[]): void; + port: MessagePort; } \ No newline at end of file diff --git a/libraries/nip66/src/managers/MonitorManager.ts b/libraries/nip66/src/managers/MonitorManager.ts index 328a7676..65caadf4 100644 --- a/libraries/nip66/src/managers/MonitorManager.ts +++ b/libraries/nip66/src/managers/MonitorManager.ts @@ -54,7 +54,7 @@ export class MonitorManager { } get sortedMonitors(): Monitor[] { - const sortedMonitors = this.activeMonitors.sort((a, b) => a.priority - b.priority); + const sortedMonitors = MonitorManager.sortMonitorsByPriority(this.activeMonitors); return sortedMonitors } @@ -146,4 +146,8 @@ export class MonitorManager { sortedMonitor.priority = index + 1; }); } + + static sortMonitorsByPriority(monitors: Monitor[]): Monitor[] { + return monitors.sort((a, b) => a.priority - b.priority); + } } diff --git a/libraries/nip66/src/models/Event.ts b/libraries/nip66/src/models/Event.ts index a2e9b43f..a893f7ce 100644 --- a/libraries/nip66/src/models/Event.ts +++ b/libraries/nip66/src/models/Event.ts @@ -61,7 +61,7 @@ export const transformCheck = (event: any) => { const monitorPubkey = event.pubkey; const created_at = event.created_at; - const network = event.tags.find((tag: NostrTag) => tag[0] === 'n')?.[1] || null; + const networks = event.tags.find((tag: NostrTag) => tag[0] === 'n')?.[1] || null; const rttOpen = parseInt(event.tags.find((tag: NostrTag) => tag[0] === 'rtt-open')?.[1]) || null; const rttWrite = parseInt(event.tags.find((tag: NostrTag) => tag[0] === 'rtt-write')?.[1]) || null; const rtt = rttOpen || rttWrite || null; @@ -93,7 +93,7 @@ export const transformCheck = (event: any) => { relay, monitorPubkey, created_at, - network, + networks, rtt, operatorPubkey, supportedNips, @@ -120,7 +120,7 @@ export class Nip66Event extends NostEvent { 'relay', 'monitorPubkey', 'created_at', - 'network', + 'networks', 'rtt', 'operatorPubkey', 'supportedNips', @@ -152,8 +152,8 @@ export class Nip66Event extends NostEvent { return this.json.created_at; } - get network(): string | null { - return this.tags.find((tag: NostrTag) => tag[0] === 'n')?.[1] || null; + get networks(): string[] | null { + return this.tags.filter((tag: NostrTag) => tag[0] === 'n').map((tag: NostrTag) => tag[1]) || null; } get rtt(): number | null { @@ -166,13 +166,15 @@ export class Nip66Event extends NostEvent { } get supportedNips(): string[] | null { - return this.tags - .filter((tag: NostrTag) => tag[0] === 'N') - .map((tag: NostrTag) => tag[1]) || null; + const nips = + this.tags + .filter((tag: NostrTag) => tag[0] === 'N') + .map((tag: NostrTag) => tag[1]) || null; + return Array.from(new Set(nips)); } get software(): string | null { - return this.tags.find((tag: NostrTag) => tag[0] === 's')?.[1] || null; + return this.tags.find((tag: NostrTag) => tag[0] === 's')?.[1]?.toLowerCase() || null; } get version(): string | null { diff --git a/libraries/nip66/src/models/Monitor.ts b/libraries/nip66/src/models/Monitor.ts index 2d7c82b5..e442a8bd 100644 --- a/libraries/nip66/src/models/Monitor.ts +++ b/libraries/nip66/src/models/Monitor.ts @@ -48,7 +48,14 @@ export class Monitor { } set lastActive(value: number) { - this.registration.lastActive = value ? value : -1; + if(!this.registration.lastActive || value > this.registration.lastActive){ + this.registration.lastActive = value; + this.emitUpdate(); + } + } + + get checks(): string[] { + return this.registration.checks || []; } get lastActive(): number { @@ -174,8 +181,7 @@ export class Monitor { addRegistration(event: IEvent): void { this.registration = {...defaultMonitor, ...n66IEventToIMonitor(event)}; - StateManager.emit('monitor:update:registration', {pubkey: this.pubkey, value: this.registration}); - StateManager.emit('monitor:update', this); + this.emitUpdate('registration', this.registration) } addProfile(event: IEvent): void { @@ -185,8 +191,7 @@ export class Monitor { } catch (e) { console.warn('Monitor addProfile error:', e); } - StateManager.emit('monitor:update:profile', {pubkey: this.pubkey, value: this.profile}); - StateManager.emit('monitor:update', this); + this.emitUpdate('profile', this.profile); } addRelays(event: IEvent): void { @@ -194,10 +199,17 @@ export class Monitor { let relays = event.tags.filter((t) => t[0] === 'r').map((t) => new URL(t[1]).toString()); relays = relays ?? []; this.relays = relays; - StateManager.emit('monitor:update:relays', {pubkey: this.pubkey, value: this.relays}); - StateManager.emit('monitor:update', this); + this.emitUpdate('relays', this.relays); } catch (e) { console.warn('Monitor addRelays error:', e); } } + + private emitUpdate(key?: string, value?: any): void { + if(key && value) { + StateManager.emit(`monitor:update:${key}`, {pubkey: this.pubkey, value}); + } + + StateManager.emit('monitor:update', this); + } } diff --git a/libraries/nip66/src/services/MonitorService.ts b/libraries/nip66/src/services/MonitorService.ts index 5bc42d09..5c82dade 100644 --- a/libraries/nip66/src/services/MonitorService.ts +++ b/libraries/nip66/src/services/MonitorService.ts @@ -241,7 +241,8 @@ export class MonitorService extends Service { { filters: [{ authors, - kinds: [0, 10002] + kinds: [0, 10002], + limit: authors.length*10 }], relays: this.userMetaRelays, options: { @@ -306,12 +307,12 @@ export class MonitorService extends Service { callbacks ); for (const event of events) { - const { pubkey } = event; + const { pubkey, created_at } = event; const monitor = this.monitors.get(pubkey); if (monitor?.registration) { - monitor.lastActive = event.created_at as number; - this.monitors.set(pubkey, monitor); - } + if(created_at) + monitor.lastActive = created_at; + } } } @@ -345,7 +346,7 @@ export class MonitorService extends Service { console.warn('MonitorService getMonitorCheckFilters: no monitors'); return []; } - const monitors = this.sortedMonitors.slice(0, 3); + const monitors = this.sortedMonitors.slice(0, 12); let filters: Filter[] = []; const until = Math.round(Date.now()/1000); monitors.forEach((monitor) => { diff --git a/libraries/nip66/src/services/SchemaValidatorService.ts b/libraries/nip66/src/services/SchemaValidatorService.ts new file mode 100644 index 00000000..88cb470c --- /dev/null +++ b/libraries/nip66/src/services/SchemaValidatorService.ts @@ -0,0 +1 @@ +export class SchemaValidatorService {} \ No newline at end of file diff --git a/libraries/worker-relay/src/interface.ts b/libraries/worker-relay/src/interface.ts index 2a39ce1f..7aa497d9 100644 --- a/libraries/worker-relay/src/interface.ts +++ b/libraries/worker-relay/src/interface.ts @@ -14,7 +14,7 @@ export interface InitAargs { } export class WorkerRelayInterface { - #worker: Worker; + #worker: Worker | SharedWorker; #commandQueue: Map) => void> = new Map(); // Command timeout @@ -24,8 +24,8 @@ export class WorkerRelayInterface { * Interface wrapper for worker relay * @param scriptPath Path to worker script or Worker script object */ - constructor(scriptPath?: string | URL | Worker, channelPort?: MessagePort) { - if (scriptPath instanceof Worker) { + constructor(scriptPath?: string | URL | Worker | SharedWorker, channelPort?: MessagePort) { + if (scriptPath instanceof Worker || scriptPath instanceof SharedWorker) { this.#worker = scriptPath; } else { const sp = scriptPath ? scriptPath : new URL("@nostrwatch/worker-relay/dist/esm/worker.mjs", import.meta.url); @@ -34,10 +34,13 @@ export class WorkerRelayInterface { this.#worker.onerror = e => { console.error(e.message, e); }; - this.#worker.onmessageerror = e => { - console.error(e); - }; - this.#worker.onmessage = e => { + if(this.#worker instanceof Worker) { + this.#worker.onmessageerror = e => { + console.error(e); + }; + } + + const onmessage = (e: MessageEvent) => { const cmd = e.data as WorkerMessage; if (cmd.cmd === "reply") { const q = this.#commandQueue.get(cmd.id); @@ -45,8 +48,19 @@ export class WorkerRelayInterface { this.#commandQueue.delete(cmd.id); } }; + if(this.#worker instanceof Worker) { + this.#worker.onmessage = onmessage; + } + else if(this.#worker instanceof SharedWorker) { + this.#worker.port.onmessage = onmessage; + } if(channelPort) { - this.#worker.postMessage({ type: "setup", channelPort }, [channelPort]); + if(this.#worker instanceof Worker) { + this.#worker.postMessage({ type: "setup", channelPort }, [channelPort]); + } + else if(this.#worker instanceof SharedWorker) { + this.#worker.port.postMessage({ type: "setup", channelPort }, [channelPort]); + } } } @@ -110,7 +124,12 @@ export class WorkerRelayInterface { args, } as WorkerMessage; return await new Promise((resolve, reject) => { - this.#worker.postMessage(msg); + if(this.#worker instanceof Worker) { + this.#worker.postMessage(msg); + } + else if(this.#worker instanceof SharedWorker) { + this.#worker.port.postMessage(msg); + } const t = setTimeout(() => { this.#commandQueue.delete(id); reject(new Error("Timeout")); diff --git a/libraries/worker-relay/src/worker-utils.ts b/libraries/worker-relay/src/worker-utils.ts index 4a7da39c..b534a242 100644 --- a/libraries/worker-relay/src/worker-utils.ts +++ b/libraries/worker-relay/src/worker-utils.ts @@ -147,12 +147,19 @@ export const relayWipe = async (state: WorkerState) => { export const handleMsg = async (state: WorkerState, ev: MessageEvent, port?: MessagePort) => { async function reply(id: string, obj?: T) { - const _port = (port ?? state.self) as MessagePort | DedicatedWorkerGlobalScope; - _port.postMessage({ - id, - cmd: "reply", - args: obj, - } as WorkerMessage); + const _port = (port ?? state.self) as MessagePort | DedicatedWorkerGlobalScope | SharedWorkerGlobalScope; + const message = { + id, + cmd: "reply", + args: obj, + } as WorkerMessage + if(_port instanceof DedicatedWorkerGlobalScope){ + _port.postMessage(message); + } + else if (_port instanceof SharedWorkerGlobalScope) { + _port.port.postMessage(message); + } + } const msg = ev.data as WorkerMessage; diff --git a/yarn.lock b/yarn.lock index 6ffca119..409b5830 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2668,6 +2668,7 @@ __metadata: svelte-preprocess: "npm:^6.0.3" svelte-radix: "npm:^1.1.0" svelte-time: "npm:^0.9.0" + svelte-timeago: "npm:^0.1.2" tailwind-merge: "npm:^2.4.0" tailwind-variants: "npm:^0.2.1" tailwindcss: "npm:^3.4.4" @@ -18204,6 +18205,13 @@ __metadata: languageName: node linkType: hard +"svelte-timeago@npm:^0.1.2": + version: 0.1.2 + resolution: "svelte-timeago@npm:0.1.2" + checksum: 10c0/f9a599bc695fc180ec3d6a0d6655d007561c05dd3113574ac41e516c5683bdbbe9e76a27e26e3e392dbdd19da93d11310c23f078a0b78956624ad55e901b32c8 + languageName: node + linkType: hard + "svelte2tsx@npm:~0.7.16": version: 0.7.28 resolution: "svelte2tsx@npm:0.7.28"