From 3dfb548c11c2d972317022b59874face007f2bc0 Mon Sep 17 00:00:00 2001 From: clawdbot Date: Wed, 28 Jan 2026 05:04:41 +0800 Subject: [PATCH] sdk: improve goView media import robustness --- packages/sdk/src/core/goview/convert.ts | 26 ++++++++++++++++++++- packages/sdk/src/core/widgets/urlLike.ts | 29 +++++++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/packages/sdk/src/core/goview/convert.ts b/packages/sdk/src/core/goview/convert.ts index 074f36c..8b6a2a7 100644 --- a/packages/sdk/src/core/goview/convert.ts +++ b/packages/sdk/src/core/goview/convert.ts @@ -312,6 +312,30 @@ function optionOf(cIn: GoViewComponentLike): unknown { ); } +function looksLikeJsonString(input: string): boolean { + const s = input.trim(); + if (!s) return false; + // Avoid parsing obvious URLs. + if (/^(https?:\/\/|\/\/|data:|rtsp:\/\/|rtmp:\/\/)/i.test(s)) return false; + return ( + (s.startsWith('{') && s.endsWith('}')) || + (s.startsWith('[') && s.endsWith(']')) + ); +} + +function normalizeOption(option: unknown): unknown { + // Some exports accidentally store option blobs as JSON strings. + if (typeof option === 'string' && looksLikeJsonString(option)) { + try { + return JSON.parse(option) as unknown; + } catch { + return option; + } + } + + return option; +} + function toNumber(v: unknown, fallback: number): number { if (typeof v === 'number' && Number.isFinite(v)) return v; if (typeof v === 'string') { @@ -414,7 +438,7 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt for (const raw of componentList) { const c = unwrapComponent(raw); - const option = optionOf(c); + const option = normalizeOption(optionOf(c)); // We try to infer the widget kind early so we can pick better default sizes // when exports omit sizing information. diff --git a/packages/sdk/src/core/widgets/urlLike.ts b/packages/sdk/src/core/widgets/urlLike.ts index 8a8b9fc..f1f9fb0 100644 --- a/packages/sdk/src/core/widgets/urlLike.ts +++ b/packages/sdk/src/core/widgets/urlLike.ts @@ -13,8 +13,35 @@ export function pickUrlLike(input: unknown, maxDepth = 3): string { return pickUrlLikeInner(input, maxDepth); } +function looksLikeJsonString(input: string): boolean { + const s = input.trim(); + if (!s) return false; + // Avoid parsing obvious URLs. + if (/^(https?:\/\/|\/\/|data:|rtsp:\/\/|rtmp:\/\/)/i.test(s)) return false; + // Simple heuristic: JSON objects/arrays. + return ( + (s.startsWith('{') && s.endsWith('}')) || + (s.startsWith('[') && s.endsWith(']')) || + // sometimes exports double-encode strings: "http://..." + (s.startsWith('"') && s.endsWith('"') && s.includes('://')) + ); +} + function pickUrlLikeInner(input: unknown, depth: number): string { - if (typeof input === 'string') return input; + if (typeof input === 'string') { + // Some low-code exports stringify nested option blobs; try to recover URLs from them. + if (depth > 0 && looksLikeJsonString(input)) { + try { + const parsed = JSON.parse(input) as unknown; + const nested = pickUrlLikeInner(parsed, depth - 1); + if (nested) return nested; + } catch { + // ignore + } + } + + return input; + } if (!input) return ''; if (Array.isArray(input)) {