Rewrite kind 3063 fetch to use #i tag filtering via direct WebSocket, bypassing NDK grouping/cache issues

This commit is contained in:
Laan Tungir
2026-08-04 11:37:02 -04:00
parent b0233f8bc3
commit d4dfd31263
3 changed files with 86 additions and 25 deletions
+1 -1
View File
@@ -11,6 +11,7 @@ Usage:
import asyncio import asyncio
import json import json
import os
import struct import struct
import subprocess import subprocess
import sys import sys
@@ -33,7 +34,6 @@ KZAPSTORE_COMMUNITY_PUBKEY = "acfeaea6e51420e8068fac446ca9d17d7a9ef6a5d20d93894e
# Load real app developer pubkeys (kind 32267 events) so stack `a` tags # Load real app developer pubkeys (kind 32267 events) so stack `a` tags
# reference events that actually exist on the relay. # reference events that actually exist on the relay.
# Generated by fetching kind 32267 events from wss://relay.zapstore.dev. # Generated by fetching kind 32267 events from wss://relay.zapstore.dev.
import os
_PUBKEYS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "app_pubkeys.json") _PUBKEYS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "app_pubkeys.json")
try: try:
with open(_PUBKEYS_PATH) as f: with open(_PUBKEYS_PATH) as f:
+82 -21
View File
@@ -206,6 +206,8 @@
let appVersions = {}; // identifier -> { version, size, url, created_at } let appVersions = {}; // identifier -> { version, size, url, created_at }
let assetSub = null; let assetSub = null;
let assetsLoaded = false; let assetsLoaded = false;
let assetEventCount = 0; // total kind 3063 events received
let assetOldestTimestamp = Infinity; // oldest raw event timestamp seen (for pagination)
/* /*
AUTH STATE MODEL (Template reference) AUTH STATE MODEL (Template reference)
@@ -788,7 +790,8 @@
console.log('[app-stacks] Added app def:', apps[apps.length - 1].name); console.log('[app-stacks] Added app def:', apps[apps.length - 1].name);
if (appDefsLoaded) renderApps(); if (appDefsLoaded) renderApps();
}); });
window.addEventListener('ndkEose', () => { window.addEventListener('ndkEose', (event) => {
if (appDefSub && event.detail && event.detail.subId !== appDefSub.subId) return;
if (!appDefsLoaded) { appDefsLoaded = true; renderApps(); } if (!appDefsLoaded) { appDefsLoaded = true; renderApps(); }
}); });
} }
@@ -823,7 +826,8 @@
console.log('[app-stacks] Added stack:', stacks[stacks.length - 1].name); console.log('[app-stacks] Added stack:', stacks[stacks.length - 1].name);
if (stacksLoaded) renderApps(); if (stacksLoaded) renderApps();
}); });
window.addEventListener('ndkEose', function() { window.addEventListener('ndkEose', function(event) {
if (stackSub && event.detail && event.detail.subId !== stackSub.subId) return;
if (!stacksLoaded) { stacksLoaded = true; renderApps(); } if (!stacksLoaded) { stacksLoaded = true; renderApps(); }
}); });
} }
@@ -841,27 +845,84 @@
return { identifier, version, size, url: urls[0] || '', created_at: evt.created_at || 0 }; return { identifier, version, size, url: urls[0] || '', created_at: evt.created_at || 0 };
} }
// Direct WebSocket fetch of kind 3063 events from relay.zapstore.dev.
// Uses #i tag filtering to request only events for our specific apps.
// Bypasses NDK to avoid grouping/cache issues.
var ZAPSTORE_RELAY = 'wss://relay.zapstore.dev';
function subscribeAppAssets() { function subscribeAppAssets() {
console.log('[app-stacks] Subscribing to app assets (kind 3063)...'); // Wait for app definitions to load so we know the identifiers
assetSub = subscribe({ kinds: [3063], limit: 500 }, { closeOnEose: false, cacheUsage: 'CACHE_FIRST' }); if (!appDefsLoaded || apps.length === 0) {
setTimeout(subscribeAppAssets, 1000);
return;
}
console.log('[app-stacks] Fetching kind 3063 events for ' + apps.length + ' apps from ' + ZAPSTORE_RELAY);
fetchAppAssetsByIdentifier();
} }
function initAppAssetListener() { function fetchAppAssetsByIdentifier() {
window.addEventListener('ndkEvent', function(event) { var identifiers = apps.map(function(a) { return a.identifier; });
var evt = event.detail; var batchSize = 50;
if (evt.kind !== 3063) return; var batches = [];
var parsed = parseAppAsset(evt); for (var i = 0; i < identifiers.length; i += batchSize) {
if (!parsed.identifier) return; batches.push(identifiers.slice(i, i + batchSize));
// Keep the latest version (highest created_at) }
var existing = appVersions[parsed.identifier]; console.log('[app-stacks] Fetching in ' + batches.length + ' batches of up to ' + batchSize + ' apps each');
if (!existing || parsed.created_at > existing.created_at) {
appVersions[parsed.identifier] = parsed; var batchIndex = 0;
if (assetsLoaded) renderApps(); function fetchNextBatch() {
if (batchIndex >= batches.length) {
if (!assetsLoaded) {
assetsLoaded = true;
renderApps();
}
console.log('[app-stacks] Kind 3063 fetch complete. Total events: ' + assetEventCount + ', unique apps with versions: ' + Object.keys(appVersions).length);
return;
} }
});
window.addEventListener('ndkEose', function() { var batch = batches[batchIndex];
if (!assetsLoaded) { assetsLoaded = true; renderApps(); } batchIndex++;
}); var reqId = 'assets-' + batchIndex + '-' + Date.now();
console.log('[app-stacks] Fetching batch ' + batchIndex + '/' + batches.length + ' (' + batch.length + ' apps)...');
var ws = new WebSocket(ZAPSTORE_RELAY);
ws.onopen = function() {
ws.send(JSON.stringify(['REQ', reqId, { kinds: [3063], '#i': batch, limit: 500 }]));
};
ws.onmessage = function(ev) {
var msg = JSON.parse(ev.data);
if (msg[0] === 'EVENT' && msg[1] === reqId) {
var evt = msg[2];
assetEventCount++;
var parsed = parseAppAsset(evt);
if (parsed.identifier) {
var existing = appVersions[parsed.identifier];
if (!existing || parsed.created_at > existing.created_at) {
appVersions[parsed.identifier] = parsed;
if (assetsLoaded) renderApps();
}
}
} else if (msg[0] === 'EOSE' && msg[1] === reqId) {
ws.send(JSON.stringify(['CLOSE', reqId]));
ws.close();
console.log('[app-stacks] Batch ' + batchIndex + ' complete (total events: ' + assetEventCount + ', unique apps: ' + Object.keys(appVersions).length + ')');
if (!assetsLoaded) {
assetsLoaded = true;
renderApps();
}
setTimeout(fetchNextBatch, 100);
}
};
ws.onerror = function(err) {
console.error('[app-stacks] WebSocket error in batch ' + batchIndex + ':', err);
if (!assetsLoaded) {
assetsLoaded = true;
renderApps();
}
setTimeout(fetchNextBatch, 100);
};
}
fetchNextBatch();
} }
async function publishAppStack(name, description) { async function publishAppStack(name, description) {
@@ -970,8 +1031,8 @@
initAppStackListener(); initAppStackListener();
subscribeAppStacks(); subscribeAppStacks();
// Set up app-asset (kind 3063) listener and subscription for version info // Fetch kind 3063 (Software Asset) events directly from relay.zapstore.dev
initAppAssetListener(); // for version info. Bypasses NDK to avoid grouping/cache issues.
subscribeAppAssets(); subscribeAppAssets();
// Optional UX note for public mode pages. // Optional UX note for public mode pages.
+3 -3
View File
@@ -1,5 +1,5 @@
{ {
"VERSION": "v0.7.97", "VERSION": "v0.7.98",
"VERSION_NUMBER": "0.7.97", "VERSION_NUMBER": "0.7.98",
"BUILD_DATE": "2026-08-04T13:32:52.287Z" "BUILD_DATE": "2026-08-04T15:37:02.089Z"
} }