Compare commits

..
1 Commits
3 changed files with 86 additions and 25 deletions
+1 -1
View File
@@ -11,6 +11,7 @@ Usage:
import asyncio
import json
import os
import struct
import subprocess
import sys
@@ -33,7 +34,6 @@ KZAPSTORE_COMMUNITY_PUBKEY = "acfeaea6e51420e8068fac446ca9d17d7a9ef6a5d20d93894e
# Load real app developer pubkeys (kind 32267 events) so stack `a` tags
# reference events that actually exist on the relay.
# 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")
try:
with open(_PUBKEYS_PATH) as f:
+82 -21
View File
@@ -206,6 +206,8 @@
let appVersions = {}; // identifier -> { version, size, url, created_at }
let assetSub = null;
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)
@@ -788,7 +790,8 @@
console.log('[app-stacks] Added app def:', apps[apps.length - 1].name);
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(); }
});
}
@@ -823,7 +826,8 @@
console.log('[app-stacks] Added stack:', stacks[stacks.length - 1].name);
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(); }
});
}
@@ -841,27 +845,84 @@
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() {
console.log('[app-stacks] Subscribing to app assets (kind 3063)...');
assetSub = subscribe({ kinds: [3063], limit: 500 }, { closeOnEose: false, cacheUsage: 'CACHE_FIRST' });
// Wait for app definitions to load so we know the identifiers
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() {
window.addEventListener('ndkEvent', function(event) {
var evt = event.detail;
if (evt.kind !== 3063) return;
var parsed = parseAppAsset(evt);
if (!parsed.identifier) return;
// Keep the latest version (highest created_at)
var existing = appVersions[parsed.identifier];
if (!existing || parsed.created_at > existing.created_at) {
appVersions[parsed.identifier] = parsed;
if (assetsLoaded) renderApps();
function fetchAppAssetsByIdentifier() {
var identifiers = apps.map(function(a) { return a.identifier; });
var batchSize = 50;
var batches = [];
for (var i = 0; i < identifiers.length; i += batchSize) {
batches.push(identifiers.slice(i, i + batchSize));
}
console.log('[app-stacks] Fetching in ' + batches.length + ' batches of up to ' + batchSize + ' apps each');
var batchIndex = 0;
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() {
if (!assetsLoaded) { assetsLoaded = true; renderApps(); }
});
var batch = batches[batchIndex];
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) {
@@ -970,8 +1031,8 @@
initAppStackListener();
subscribeAppStacks();
// Set up app-asset (kind 3063) listener and subscription for version info
initAppAssetListener();
// Fetch kind 3063 (Software Asset) events directly from relay.zapstore.dev
// for version info. Bypasses NDK to avoid grouping/cache issues.
subscribeAppAssets();
// Optional UX note for public mode pages.
+3 -3
View File
@@ -1,5 +1,5 @@
{
"VERSION": "v0.7.97",
"VERSION_NUMBER": "0.7.97",
"BUILD_DATE": "2026-08-04T13:32:52.287Z"
"VERSION": "v0.7.98",
"VERSION_NUMBER": "0.7.98",
"BUILD_DATE": "2026-08-04T15:37:02.089Z"
}