Skip to content
Draft
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
10 changes: 2 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

91 changes: 62 additions & 29 deletions src/share/share.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,22 @@ async function init() {
// Get nid from URL params
const params = new URLSearchParams(window.location.search);
const nid = params.get('nid');


const root = document.getElementById('root');
if (!root) return;

if (!nid) {
document.getElementById('root')!.innerHTML = '<p>No asset to share</p>';
const p = document.createElement('p');
p.textContent = 'No asset to share';
root.appendChild(p);
return;
}

// Validate nid to only allow safe characters (alphanumeric, hyphens, underscores)
if (!/^[a-zA-Z0-9_-]+$/.test(nid)) {
const p = document.createElement('p');
p.textContent = 'Invalid asset ID';
root.appendChild(p);
return;
}

Expand All @@ -22,19 +35,17 @@ async function init() {
const shareText = `${settings.huntModeMessage} ${verifyUrl} ${settings.huntModeHashtags}`;
const twitterUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(shareText)}`;

// Render UI
document.getElementById('root')!.innerHTML = `
// Render static UI structure (no user input interpolated into innerHTML)
root.innerHTML = `
<div class="share-container">
<div class="share-icon">🎯</div>
<h1 class="share-title">Snap Verified!</h1>
<p class="share-subtitle">
Your screenshot is now on the blockchain!<br>
Share it on X to join the AI Hunt event.
</p>

<div class="verify-link">
<a href="${verifyUrl}" target="_blank">${verifyUrl}</a>
</div>

<div class="verify-link" id="verifyLinkContainer"></div>

<div class="share-buttons">
<button class="share-btn share-btn-x" id="shareX">
Expand All @@ -43,15 +54,15 @@ async function init() {
</svg>
Share on X
</button>

<button class="share-btn share-btn-copy" id="copyLink">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
Copy Verification Link
</button>

<button class="share-btn share-btn-close" id="closeBtn">
Maybe Later
</button>
Expand All @@ -60,25 +71,47 @@ async function init() {
<div class="copied-toast" id="toast">Link copied!</div>
`;

// Event listeners
document.getElementById('shareX')!.addEventListener('click', () => {
window.open(twitterUrl, '_blank');
});

document.getElementById('copyLink')!.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(verifyUrl);
const toast = document.getElementById('toast')!;
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 2000);
} catch (err) {
console.error('Failed to copy:', err);
}
});

document.getElementById('closeBtn')!.addEventListener('click', () => {
window.close();
});
// Inject the verify link using safe DOM APIs to prevent XSS
const verifyLinkContainer = document.getElementById('verifyLinkContainer');
if (verifyLinkContainer) {
const link = document.createElement('a');
link.href = verifyUrl;
link.textContent = verifyUrl;
link.target = '_blank';
link.rel = 'noopener noreferrer';
verifyLinkContainer.appendChild(link);
}

// Event listeners with safe element access
const shareXBtn = document.getElementById('shareX');
if (shareXBtn) {
shareXBtn.addEventListener('click', () => {
window.open(twitterUrl, '_blank', 'noopener,noreferrer');
});
}

const copyLinkBtn = document.getElementById('copyLink');
if (copyLinkBtn) {
copyLinkBtn.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(verifyUrl);
const toast = document.getElementById('toast');
if (toast) {
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 2000);
}
} catch (err) {
console.error('Failed to copy:', err);
}
});
}

const closeBtn = document.getElementById('closeBtn');
if (closeBtn) {
closeBtn.addEventListener('click', () => {
window.close();
});
}
}

init();