const isMobile = { Android: () => navigator.userAgent.match(/Android/i), iOS: () => navigator.userAgent.match(/iPhone|iPad|iPod/i), any: () => navigator.userAgent.match(/Android|iPhone|iPad|iPod/i) }; function escapeHtml(text) { return text .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'") .replace(/\n/g, "
"); } function escapeAttr(text) { return text .replace(/&/g, "&") .replace(/"/g, """) .replace(/'/g, "'") .replace(//g, ">"); } const SAFE_URI_SCHEME = /^(https?:|simplex:|mailto:|tel:)/i; function safeHref(uri) { if (SAFE_URI_SCHEME.test(uri)) return escapeAttr(uri); return escapeAttr(`javascript:void(alert('Potentially malicious link blocked:\\n'+${JSON.stringify(uri)}))`); } function getSimplexLinkDescr(linkType) { switch (linkType) { case 'contact': return 'SimpleX contact address'; case 'invitation': return 'SimpleX one-time invitation'; case 'group': return 'SimpleX group link'; case 'channel': return 'SimpleX channel link'; case 'relay': return 'SimpleX relay link'; default: return 'SimpleX link'; } } function viaHost(smpHosts) { const first = smpHosts[0] ?? '?'; return `via ${first}`; } function isCurrentSite(uri) { return uri.startsWith("https://simplex.chat") || uri.startsWith("https://www.simplex.chat") } function targetBlank(uri) { return isCurrentSite(uri) ? '' : ' target="_blank"' } function renderMarkdown(fts) { let html = ''; for (const ft of fts) { const { format, text } = ft; if (!format) { html += escapeHtml(text); continue; } try { switch (format.type) { case 'bold': html += `${escapeHtml(text)}`; break; case 'italic': html += `${escapeHtml(text)}`; break; case 'strikeThrough': html += `${escapeHtml(text)}`; break; case 'snippet': html += `${escapeHtml(text)}`; break; case 'secret': html += `${escapeHtml(text)}`; break; case 'small': html += `${escapeHtml(text)}`; break; case 'colored': html += `${escapeHtml(text)}`; break; case 'uri': { let href = text.startsWith('http://') || text.startsWith('https://') || text.startsWith('simplex:/') ? text : 'https://' + text; html += `${escapeHtml(text)}`; break; } case 'hyperLink': { const { showText, linkUri } = format; html += `${escapeHtml(showText ?? linkUri)}`; break; } case 'simplexLink': { const { showText, linkType, simplexUri, smpHosts } = format; const linkText = showText ? escapeHtml(showText) : getSimplexLinkDescr(linkType); html += `${linkText} (${escapeHtml(viaHost(smpHosts))})`; break; } case 'command': html += `${escapeHtml(text)}`; break; case 'mention': html += `${escapeHtml(text)}`; break; case 'email': html += `${escapeHtml(text)}`; break; case 'phone': html += `${escapeHtml(text)}`; break; case 'unknown': html += escapeHtml(text); break; default: html += escapeHtml(text); } } catch(e) { console.log(e); html += escapeHtml(text); } } return html; } const simplexAddressRegexp = /^simplex:\/([a-z]+)#(.+)/i; const simplexShortLinkTypes = ["a", "c", "g", "i", "r"]; function platformSimplexUri(uri) { if (isMobile.any()) return uri; const res = uri.match(simplexAddressRegexp); if (!res || !Array.isArray(res) || res.length < 3) return uri; const linkType = res[1]; const fragment = res[2]; if (simplexShortLinkTypes.includes(linkType)) { const queryIndex = fragment.indexOf('?'); if (queryIndex === -1) return uri; const hashPart = fragment.substring(0, queryIndex); const queryStr = fragment.substring(queryIndex + 1); const params = new URLSearchParams(queryStr); const host = params.get('h'); if (!host) return uri; params.delete('h'); let newFragment = hashPart; const remainingParams = params.toString(); if (remainingParams) newFragment += '?' + remainingParams; return `https://${host}:/${linkType}#${newFragment}`; } else { return `https://simplex.chat/${linkType}#${fragment}`; } }