feat(v0.4): multi-device discovery + share link (frontend)
- tabs.ts: on open, syncLiveSessions() fetches /live-sessions and adds a tab for each host session not already open → any device sees & mirrors everything running on the host. Empty storage + no live sessions → one fresh tab. activeSessionId() + openSession(id) helpers. - share.ts: 🔗 toolbar button → QR + <origin>/?join=<id> for the active session. - main.ts: ?join=<id> on load opens/focuses that shared session, then strips the param. Mount share button (toolbar: 🔍 ⚙ ▦ 🕘 ⌨ 🔗 📱). - style.css: #sharemodal reuses the QR card/backdrop. Verified in-browser: fresh device auto-discovered the host session and replayed its scrollback; two concurrent clients (browser + ws) both received live output (clientCount=2); share modal shows the join URL/QR.
This commit is contained in:
72
public/share.ts
Normal file
72
public/share.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* public/share.ts — "share this session" (v0.4 multi-device mirror).
|
||||
*
|
||||
* A 🔗 toolbar button shows a QR + URL of the form `<origin>/?join=<sessionId>`
|
||||
* for the ACTIVE tab. Opening that link on another device on the same network
|
||||
* joins the same live terminal (mirror: both see output, either can type).
|
||||
* Reuses the .qr-* card styles.
|
||||
*/
|
||||
|
||||
import QRCode from 'qrcode'
|
||||
|
||||
export function mountShareSession(toolbar: HTMLElement, getActiveId: () => string | null): void {
|
||||
const modal = document.createElement('div')
|
||||
modal.id = 'sharemodal'
|
||||
modal.className = 'qrmodal'
|
||||
modal.style.display = 'none'
|
||||
|
||||
const card = document.createElement('div')
|
||||
card.className = 'qr-card'
|
||||
const title = document.createElement('div')
|
||||
title.className = 'qr-title'
|
||||
title.textContent = 'Share this session'
|
||||
const canvas = document.createElement('canvas')
|
||||
const urlEl = document.createElement('div')
|
||||
urlEl.className = 'qr-url'
|
||||
const note = document.createElement('div')
|
||||
note.className = 'qr-note'
|
||||
const close = document.createElement('button')
|
||||
close.className = 'qr-close'
|
||||
close.textContent = 'Close'
|
||||
card.append(title, canvas, urlEl, note, close)
|
||||
modal.appendChild(card)
|
||||
document.body.appendChild(modal)
|
||||
|
||||
const hide = (): void => {
|
||||
modal.style.display = 'none'
|
||||
}
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) hide()
|
||||
})
|
||||
close.addEventListener('click', hide)
|
||||
|
||||
const open = (): void => {
|
||||
const id = getActiveId()
|
||||
if (!id) {
|
||||
// The active tab hasn't attached yet — no id to share.
|
||||
urlEl.textContent = ''
|
||||
note.textContent = 'This tab is still connecting — try again in a second.'
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (ctx) ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
modal.style.display = 'flex'
|
||||
return
|
||||
}
|
||||
const url = `${location.origin}/?join=${id}`
|
||||
urlEl.textContent = url
|
||||
void QRCode.toCanvas(canvas, url, { width: 220, margin: 1 })
|
||||
const host = location.hostname
|
||||
note.textContent =
|
||||
host === 'localhost' || host === '127.0.0.1'
|
||||
? 'Tip: open this page via your LAN IP (not localhost) so the code works on other devices.'
|
||||
: 'Scan on a device on the same network to mirror this session.'
|
||||
modal.style.display = 'flex'
|
||||
}
|
||||
|
||||
const toggle = document.createElement('button')
|
||||
toggle.className = 'toolbtn'
|
||||
toggle.textContent = '🔗'
|
||||
toggle.title = 'Share this session (mirror it on another device)'
|
||||
toggle.setAttribute('aria-label', 'Share this session')
|
||||
toggle.addEventListener('click', open)
|
||||
toolbar.appendChild(toggle)
|
||||
}
|
||||
Reference in New Issue
Block a user