Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions src/services/miniapp-runtime/__tests__/launch-context.test.ts
Original file line number Diff line number Diff line change
@@ -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>): 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' });
});
});

8 changes: 5 additions & 3 deletions src/services/miniapp-runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -919,7 +921,7 @@ export function launchApp(
appId,
url: manifest.url,
mountTarget,
contextParams,
contextParams: launchContextParams,
wujieConfig: manifest.wujieConfig,
permissionsPolicyAllow,
onLoad,
Expand All @@ -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);
Expand Down
36 changes: 36 additions & 0 deletions src/services/miniapp-runtime/launch-context.ts
Original file line number Diff line number Diff line change
@@ -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<string, string>,
): Record<string, string> | 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;
}