diff --git a/plugins/platforms/photon/sidecar/index.mjs b/plugins/platforms/photon/sidecar/index.mjs index ee2620c5877f7..1bc9ac96e9fd3 100644 --- a/plugins/platforms/photon/sidecar/index.mjs +++ b/plugins/platforms/photon/sidecar/index.mjs @@ -848,8 +848,16 @@ const server = http.createServer(async (req, res) => { const space = await resolveSpace(spaceId); // iMessage renders markdown natively; spectrum-ts degrades it to // readable plain text on platforms that don't. + // spectrumMarkdown() enables enableDataDetection in the underlying + // iMessage API, which can 500 on messages containing raw URLs. + // Plain-text URLs are auto-linked by iMessage, so route markdown + // messages that contain URLs through spectrumText while preserving + // spectrumMarkdown for URL-free markdown. + const hasUrl = /https?:\/\/[^\s)'"<>]+/i.test(text); const builder = - format === "markdown" ? spectrumMarkdown(text) : spectrumText(text); + format === "markdown" && !hasUrl + ? spectrumMarkdown(text) + : spectrumText(text); const result = await space.send(builder); return ok(res, { messageId: result?.id || null }); } diff --git a/tests/plugins/platforms/photon/test_url_send_path.py b/tests/plugins/platforms/photon/test_url_send_path.py new file mode 100644 index 0000000000000..f3bfd81b2c165 --- /dev/null +++ b/tests/plugins/platforms/photon/test_url_send_path.py @@ -0,0 +1,50 @@ +"""Regression tests for Photon raw-URL message delivery. + +The iMessage markdown builder enables data detection inside spectrum-ts. On +some IMAgentKit sends, that path returns a 500 when the message contains a raw +URL. The sidecar should keep markdown rendering for URL-free messages, but use +plain text for messages containing URLs so iMessage can auto-link them without +hitting the data-detection failure path. +""" + +from __future__ import annotations + +import re +from pathlib import Path + + +SIDECAR = Path("plugins/platforms/photon/sidecar/index.mjs") + + +def _source() -> str: + return SIDECAR.read_text(encoding="utf-8") + + +def test_photon_sidecar_detects_raw_urls_before_builder_selection() -> None: + src = _source() + + assert "hasUrl" in src + assert "https?:\\/\\/" in src + assert re.search(r"const\s+hasUrl\s*=\s*/https\?:", src) + + +def test_markdown_builder_is_used_only_for_url_free_markdown() -> None: + src = _source() + + assert re.search( + r'format\s*===\s*["\']markdown["\']\s*&&\s*!hasUrl', + src, + ) + assert "spectrumMarkdown(text)" in src + + +def test_text_builder_is_fallback_for_markdown_messages_with_urls() -> None: + src = _source() + + # Builder selection must still have spectrumText as the fallback branch so + # markdown+URL messages avoid spectrumMarkdown's data-detection path. + assert "spectrumText(text)" in src + assert re.search( + r'format\s*===\s*["\']markdown["\']\s*&&\s*!hasUrl[\s\S]*\?[\s\S]*spectrumMarkdown\(text\)[\s\S]*:[\s\S]*spectrumText\(text\)', + src, + )