From 785bc28bf2c88d114ac97d1c84c9679e908f30c4 Mon Sep 17 00:00:00 2001 From: Gaubee Date: Fri, 13 Feb 2026 16:27:48 +0800 Subject: [PATCH] feat(miniapp): append stable launch revision query param --- .../__tests__/launch-context.test.ts | 64 +++++++++++++++++++ src/services/miniapp-runtime/index.ts | 8 ++- .../miniapp-runtime/launch-context.ts | 36 +++++++++++ 3 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 src/services/miniapp-runtime/__tests__/launch-context.test.ts create mode 100644 src/services/miniapp-runtime/launch-context.ts diff --git a/src/services/miniapp-runtime/__tests__/launch-context.test.ts b/src/services/miniapp-runtime/__tests__/launch-context.test.ts new file mode 100644 index 000000000..20b351b40 --- /dev/null +++ b/src/services/miniapp-runtime/__tests__/launch-context.test.ts @@ -0,0 +1,64 @@ +import { describe, expect, it } from 'vitest'; +import type { MiniappManifest } from '@/services/ecosystem/types'; +import { buildMiniappLaunchContextParams } from '../launch-context'; + +function createManifest(partial?: Partial): MiniappManifest { + return { + id: 'xin.dweb.demo', + name: 'Demo', + description: 'Demo miniapp', + icon: 'https://example.com/icon.svg', + url: 'https://example.com', + version: '1.0.0', + ...partial, + }; +} + +describe('buildMiniappLaunchContextParams', () => { + it('appends stable revision by manifest version', () => { + const manifest = createManifest({ version: '1.2.3' }); + + const result = buildMiniappLaunchContextParams(manifest); + + expect(result).toEqual({ __rv: 'v:1.2.3' }); + }); + + it('keeps existing params and appends revision', () => { + const manifest = createManifest({ version: '2.0.0' }); + + const result = buildMiniappLaunchContextParams(manifest, { + account: 'bfm-address-1', + }); + + expect(result).toEqual({ + account: 'bfm-address-1', + __rv: 'v:2.0.0', + }); + }); + + it('does not override explicit revision param', () => { + const manifest = createManifest({ version: '3.0.0' }); + + const result = buildMiniappLaunchContextParams(manifest, { + __rv: 'v:custom', + from: 'bridge', + }); + + expect(result).toEqual({ + __rv: 'v:custom', + from: 'bridge', + }); + }); + + it('falls back to updated timestamp when version is empty', () => { + const manifest = createManifest({ + version: '', + updated: '2026-02-13T03:00:00Z', + }); + + const result = buildMiniappLaunchContextParams(manifest); + + expect(result).toEqual({ __rv: 'u:2026-02-13T03:00:00Z' }); + }); +}); + diff --git a/src/services/miniapp-runtime/index.ts b/src/services/miniapp-runtime/index.ts index 57972ec17..ae6f2a566 100644 --- a/src/services/miniapp-runtime/index.ts +++ b/src/services/miniapp-runtime/index.ts @@ -73,6 +73,7 @@ import { type RuntimeStateDomCommand, } from './runtime-state-machine'; import { domBindApplyCommand, domBindSyncIframeMountTarget } from './runtime-dom-binding'; +import { buildMiniappLaunchContextParams } from './launch-context'; import { isDwebEnvironment } from '@/lib/crypto/secure-storage'; import type { MiniappContext as MiniappContextPayload } from '@biochain/bio-sdk'; @@ -871,6 +872,7 @@ export function launchApp( const containerType: ContainerType = manifest.runtime ?? 'iframe'; const targetDesktop = manifest.targetDesktop ?? 'stack'; const permissionsPolicyAllow = getPermissionsPolicyAllow(manifest); + const launchContextParams = buildMiniappLaunchContextParams(manifest, contextParams); const instance: MiniappInstance = { appId, @@ -919,7 +921,7 @@ export function launchApp( appId, url: manifest.url, mountTarget, - contextParams, + contextParams: launchContextParams, wujieConfig: manifest.wujieConfig, permissionsPolicyAllow, onLoad, @@ -934,11 +936,11 @@ export function launchApp( } catch (error) { // If sync creation fails, fall back to async globalThis.console.warn('[miniapp-runtime] Sync container creation failed, falling back to async:', error); - createContainerAsync(instance, manifest, contextParams, onLoad, mountTarget); + createContainerAsync(instance, manifest, launchContextParams, onLoad, mountTarget); } } else { // Asynchronous container creation (wujie) - createContainerAsync(instance, manifest, contextParams, onLoad, mountTarget); + createContainerAsync(instance, manifest, launchContextParams, onLoad, mountTarget); } const presentTransition = createTransition('present', appId); diff --git a/src/services/miniapp-runtime/launch-context.ts b/src/services/miniapp-runtime/launch-context.ts new file mode 100644 index 000000000..3f49f23cf --- /dev/null +++ b/src/services/miniapp-runtime/launch-context.ts @@ -0,0 +1,36 @@ +import type { MiniappManifest } from '@/services/ecosystem/types'; + +const MINIAPP_RUNTIME_REVISION_PARAM = '__rv'; + +function deriveMiniappRuntimeRevision(manifest: MiniappManifest): string | null { + const version = manifest.version.trim(); + if (version.length > 0) { + return `v:${version}`; + } + + const updated = manifest.updated?.trim(); + if (updated && updated.length > 0) { + return `u:${updated}`; + } + + return null; +} + +export function buildMiniappLaunchContextParams( + manifest: MiniappManifest, + contextParams?: Record, +): Record | undefined { + const nextContextParams = contextParams ? { ...contextParams } : {}; + if (!(MINIAPP_RUNTIME_REVISION_PARAM in nextContextParams)) { + const revision = deriveMiniappRuntimeRevision(manifest); + if (revision) { + nextContextParams[MINIAPP_RUNTIME_REVISION_PARAM] = revision; + } + } + + if (Object.keys(nextContextParams).length === 0) { + return undefined; + } + return nextContextParams; +} +