From 5b2640d50365cb850682ccecd6930284ff4930a0 Mon Sep 17 00:00:00 2001 From: Gaubee Date: Tue, 10 Feb 2026 16:23:36 +0800 Subject: [PATCH] fix(ecosystem): ensure rwahub uses iframe runtime --- scripts/vite-plugin-miniapps.ts | 21 ++- .../ecosystem/__tests__/registry.test.ts | 178 ++++++++++++++---- src/services/ecosystem/registry.ts | 18 +- vite.config.ts | 6 +- 4 files changed, 172 insertions(+), 51 deletions(-) diff --git a/scripts/vite-plugin-miniapps.ts b/scripts/vite-plugin-miniapps.ts index 28d81e83c..15582f9f5 100644 --- a/scripts/vite-plugin-miniapps.ts +++ b/scripts/vite-plugin-miniapps.ts @@ -616,11 +616,18 @@ function scanRemoteMiniappsForBuild( ): Array { if (!existsSync(miniappsPath)) return []; - const configByDirName = new Map( - remoteConfigs - .map((c) => (c.build?.locale ? [c.build.locale.dirName, c] : null)) - .filter((item): item is [string, RemoteMiniappConfig] => item !== null), - ); + const configByDirName = new Map(); + for (const remoteConfig of remoteConfigs) { + const buildDirName = remoteConfig.build?.locale?.dirName; + if (buildDirName && !configByDirName.has(buildDirName)) { + configByDirName.set(buildDirName, remoteConfig); + } + + const serverDirName = remoteConfig.server?.locale?.dirName; + if (serverDirName && !configByDirName.has(serverDirName)) { + configByDirName.set(serverDirName, remoteConfig); + } + } const remoteApps: Array< MiniappManifest & { url: string; runtime?: MiniappRuntime; wujieConfig?: WujieRuntimeConfig } > = []; @@ -645,8 +652,8 @@ function scanRemoteMiniappsForBuild( url: baseUrl, icon: manifest.icon.startsWith('http') ? manifest.icon : new URL(manifest.icon, baseUrl).href, screenshots: manifest.screenshots?.map((s) => (s.startsWith('http') ? s : new URL(s, baseUrl).href)) ?? [], - runtime: config?.build?.runtime ?? 'wujie', - wujieConfig: config?.build?.wujieConfig, + runtime: config?.build?.runtime ?? config?.server?.runtime ?? 'wujie', + wujieConfig: config?.build?.wujieConfig ?? config?.server?.wujieConfig, }); } catch { console.warn(`[miniapps] ${entry.name}: invalid remote manifest.json, skipping`); diff --git a/src/services/ecosystem/__tests__/registry.test.ts b/src/services/ecosystem/__tests__/registry.test.ts index d45107954..34be56cc7 100644 --- a/src/services/ecosystem/__tests__/registry.test.ts +++ b/src/services/ecosystem/__tests__/registry.test.ts @@ -67,6 +67,94 @@ describe('Miniapp Registry (Subscription v2)', () => { expect(getAppById('xin.dweb.teleport')?.name).toBe('Teleport') }) + it('prefers the later source when duplicate app id exists', async () => { + ecosystemStore.setState(() => ({ + permissions: [], + sources: [ + { + url: '/ecosystem-primary.json', + name: 'Primary', + enabled: true, + lastUpdated: new Date().toISOString(), + }, + { + url: '/ecosystem-override.json', + name: 'Override', + enabled: true, + lastUpdated: new Date().toISOString(), + }, + ], + })) + + global.fetch = vi.fn((input: RequestInfo | URL) => { + const requestUrl = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url + + if (requestUrl.includes('/ecosystem-primary.json')) { + return Promise.resolve({ + ok: true, + status: 200, + headers: new Map(), + json: () => + Promise.resolve({ + name: 'primary', + version: '1', + updated: '2025-01-01', + apps: [ + { + id: 'xin.dweb.rwahub', + name: 'RWA Hub', + url: 'https://legacy.example/', + icon: '/legacy-icon.svg', + description: 'legacy runtime', + version: '1.0.0', + runtime: 'wujie', + }, + ], + }), + }) + } + + if (requestUrl.includes('/ecosystem-override.json')) { + return Promise.resolve({ + ok: true, + status: 200, + headers: new Map(), + json: () => + Promise.resolve({ + name: 'override', + version: '1', + updated: '2025-01-01', + apps: [ + { + id: 'xin.dweb.rwahub', + name: 'RWA Hub', + url: 'https://latest.example/', + icon: '/latest-icon.svg', + description: 'iframe runtime', + version: '1.0.0', + runtime: 'iframe', + }, + ], + }), + }) + } + + return Promise.resolve({ + ok: false, + status: 404, + headers: new Map(), + json: () => Promise.resolve({}), + }) + }) as unknown as typeof fetch + + await refreshSources() + + const app = getAppById('xin.dweb.rwahub') + expect(app?.runtime).toBe('iframe') + expect(app?.url).toBe('https://latest.example/') + expect(app?.sourceUrl).toBe('/ecosystem-override.json') + }) + it('searches cached apps by keyword', async () => { global.fetch = vi.fn().mockResolvedValue({ ok: true, @@ -89,39 +177,63 @@ describe('Miniapp Registry (Subscription v2)', () => { }) it('ranks featured apps by featuredScore', async () => { - global.fetch = vi.fn().mockResolvedValue({ - ok: true, - status: 200, - headers: new Map(), - json: () => - Promise.resolve({ - name: 'x', - version: '1', - updated: '2025-01-01', - apps: [ - { - id: 'xin.dweb.a', - name: 'A', - url: '/', - icon: '', - description: '', - version: '1.0.0', - officialScore: 100, - communityScore: 0, - }, - { - id: 'xin.dweb.b', - name: 'B', - url: '/', - icon: '', - description: '', - version: '1.0.0', - officialScore: 0, - communityScore: 100, - }, - ], - }), - }) + ecosystemStore.setState(() => ({ + permissions: [], + sources: [ + { + url: '/ecosystem-featured.json', + name: 'Featured', + enabled: true, + lastUpdated: new Date().toISOString(), + }, + ], + })) + + global.fetch = vi.fn((input: RequestInfo | URL) => { + const requestUrl = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url + if (!requestUrl.includes('/ecosystem-featured.json')) { + return Promise.resolve({ + ok: false, + status: 404, + headers: new Map(), + json: () => Promise.resolve({}), + }) + } + + return Promise.resolve({ + ok: true, + status: 200, + headers: new Map(), + json: () => + Promise.resolve({ + name: 'x', + version: '1', + updated: '2025-01-01', + apps: [ + { + id: 'xin.dweb.a', + name: 'A', + url: '/', + icon: '', + description: '', + version: '1.0.0', + officialScore: 100, + communityScore: 0, + }, + { + id: 'xin.dweb.b', + name: 'B', + url: '/', + icon: '', + description: '', + version: '1.0.0', + officialScore: 0, + communityScore: 100, + }, + ], + }), + }) + }) as unknown as typeof fetch await refreshSources() const featured = await getFeaturedApps(1, new Date('2025-01-05T00:00:00.000Z')) diff --git a/src/services/ecosystem/registry.ts b/src/services/ecosystem/registry.ts index cac6d8bc5..26872d949 100644 --- a/src/services/ecosystem/registry.ts +++ b/src/services/ecosystem/registry.ts @@ -306,22 +306,26 @@ function mergeAppsFromSources( const normalized = normalizeAppFromSource(app, source, payload); if (!normalized) continue; - const id = normalized.id; - if (!indexById.has(id)) { - indexById.set(id, merged.length); + const appId = normalized.id; + const existingIndex = indexById.get(appId); + + if (existingIndex === undefined) { + indexById.set(appId, merged.length); merged.push(normalized); + } else { + merged[existingIndex] = normalized; } if (typeof normalized.officialScore === 'number') { - const list = officialScoresById.get(id) ?? []; + const list = officialScoresById.get(appId) ?? []; list.push(normalized.officialScore); - officialScoresById.set(id, list); + officialScoresById.set(appId, list); } if (typeof normalized.communityScore === 'number') { - const list = communityScoresById.get(id) ?? []; + const list = communityScoresById.get(appId) ?? []; list.push(normalized.communityScore); - communityScoresById.set(id, list); + communityScoresById.set(appId, list); } } } diff --git a/vite.config.ts b/vite.config.ts index 1a3ce264e..3de0355a3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -20,16 +20,14 @@ const remoteMiniappsConfig: RemoteMiniappConfig[] = [ metadataUrl: 'https://iweb.xin/rwahub.bfmeta.com.miniapp/metadata.json', dirName: 'rwa-hub', }, - runtime: 'wujie', - wujieConfig: { rewriteAbsolutePaths: true }, + runtime: 'iframe', }, build: { remote: { name: 'RWA', sourceUrl: 'https://iweb.xin/rwahub.bfmeta.com.miniapp/source.json', }, - runtime: 'wujie', - wujieConfig: { rewriteAbsolutePaths: true }, + runtime: 'iframe', }, }, ];