完善移动壳系统分享目标解析

移动 Expo 壳解析统一分享目标并调用 React Native 系统分享面板

补充直接分享、缓存作品目标和空分享目标的移动壳测试

更新宿主壳方案和项目共享决策记录
This commit is contained in:
2026-06-17 23:08:20 +08:00
parent d67f9d5725
commit 1a26806804
4 changed files with 132 additions and 8 deletions

View File

@@ -20,6 +20,8 @@ import {
} from '../../../packages/shared/src/contracts/hostBridge';
import { resolveMobileShellWebViewUrl } from './mobileShellNavigation';
const WEB_APP_ORIGIN = 'https://app.genarrative.world';
export const MOBILE_HOST_CAPABILITIES: HostBridgeCapability[] = [
'host.getRuntime',
'host.events',
@@ -143,13 +145,73 @@ async function runHaptics(payload: unknown) {
return true;
}
function stringField(value: unknown, field: string) {
if (!value || typeof value !== 'object') {
return undefined;
}
const fieldValue = (value as Record<string, unknown>)[field];
if (typeof fieldValue !== 'string') {
return undefined;
}
const text = fieldValue.trim();
return text || undefined;
}
function shareTargetPayload(value: unknown) {
if (!value || typeof value !== 'object') {
return value;
}
const target = value as Record<string, unknown>;
return target.target ?? value;
}
function workDetailUrl(work: string) {
return `${WEB_APP_ORIGIN}/works/detail?work=${encodeURIComponent(work)}`;
}
function webAppPathUrl(path: string) {
return new URL(path, WEB_APP_ORIGIN).toString();
}
function normalizeSharePayload(value: unknown): ShareOpenPayload | null {
const target = shareTargetPayload(value);
const payload =
target && typeof target === 'object'
? (target as Record<string, unknown>).payload ?? target
: target;
if (!payload || typeof payload !== 'object') {
return null;
}
const title = stringField(payload, 'title');
const message = stringField(payload, 'message');
const directUrl = stringField(payload, 'url') ?? stringField(payload, 'href');
const work = stringField(payload, 'work');
const path = stringField(payload, 'path') ?? stringField(payload, 'targetPath');
const url = directUrl ?? (work ? workDetailUrl(work) : undefined) ?? (path ? webAppPathUrl(path) : undefined);
if (!title && !message && !url) {
return null;
}
return {
...(title ? { title } : {}),
...(message ? { message } : {}),
...(url ? { url } : {}),
};
}
async function openShare(payload: unknown) {
const sharePayload =
payload && typeof payload === 'object'
? (payload as ShareOpenPayload)
: currentShareTarget && typeof currentShareTarget === 'object'
? (currentShareTarget as ShareOpenPayload)
: undefined;
normalizeSharePayload(payload) ?? normalizeSharePayload(currentShareTarget);
if (!sharePayload) {
throw invalidRequest('share target is required');
}
const url = sharePayload?.url;
const message = [sharePayload?.message, url].filter(Boolean).join('\n');
@@ -255,3 +317,8 @@ export async function handleMobileHostBridgeMessage(
sendResponse(failure(parsed, normalizeError(error)));
}
}
export function resetMobileHostBridgeForTest() {
currentShareTarget = null;
navigation = null;
}