+
A minimal working example — parent sends a table, child receives it. That's it.
+
+
+
+
+
+
+ Parent window
+
+
+
+
+
+
+
-
- -
-
send(data, opts?)
- Auto-selects format — zero-copy → copy → JSON
- ⭐
-
- -
-
sendJSON(data, opts?)
- Send any JSON-serialisable value
-
- -
-
sendArrowCopy(buf, opts?)
- Arrow IPC via postMessage (universal)
-
- -
-
sendArrowZeroCopy(buf)
- Arrow IPC via SharedArrayBuffer — no copy ⚡
-
- -
-
onReady(cb)
- Fires once handshake completes
-
- -
-
onStateChange(cb)
- cb(newState, oldState) on every transition
-
- -
-
isSABSupported()
- true if SharedArrayBuffer is available
-
- -
-
getState() / isReady()
- INIT · CONNECTING · READY · SENDING · ERROR · CLOSED
-
- -
-
close()
- Reject pending ACKs, cleanup listeners
-
-
-
-
-
-
-
ArrowChildReceiver
-
( config? )
+
+
+
+
-
- -
-
onData(cb)
- result.table (Arrow) or result.data (JSON)
-
- -
-
resumeListening()
- Re-enable after FIRST_MESSAGE mode
-
- -
-
onStateChange / onError
- Same as emitter
-
- -
-
close()
- Cleanup, clear buffer and intervals
-
-
-
+
import { ArrowParentEmitter, tableFromArrays, tableToIPC } from 'iframe-flight';
-
-
-
- Apache Arrow
- re-exported from iframe-flight
+const iframe = document.querySelector('iframe');
+const emitter = new ArrowParentEmitter(iframe);
+
+emitter.onReady(async () => {
+ const table = tableFromArrays({
+ id: [1, 2, 3],
+ name: ['Alice', 'Bob', 'Charlie'],
+ score: [95.5, 87.3, 92.1],
+ });
+ const ack = await emitter.send(tableToIPC(table));
+ console.log(`rows=${ack.rows} zeroCopy=${ack.isZeroCopy}`);
+});
+
+
+
+
Child iframe
+
-
- -
-
tableFromArrays(cols)
- Create Arrow table from typed/JS arrays
-
- -
-
tableToIPC(table)
- Serialize → Uint8Array IPC buffer
-
- -
-
Table, Schema, Field …
- Full Apache Arrow types available
-
-
+
import { ArrowChildReceiver } from 'iframe-flight';
+
+const receiver = new ArrowChildReceiver();
+
+receiver.onData((result) => {
+ // result.table → Apache Arrow Table
+ // result.data → plain JS value (JSON mode)
+ // result.rows / .cols / .isZeroCopy / …
+ console.log(result.table.toArray());
+});
-
+
+
-
@@ -587,319 +465,241 @@
✈ iframe-flight
import { ArrowParentEmitter, State, tableFromArrays, tableToIPC } from 'iframe-flight';
-/* ── State ── */
-let sent = 0, acked = 0;
-let alphaReady = false, betaReady = false;
-let lastReply = null;
+/* ══ ROUTER ══ */
+function route() {
+ const page = location.hash.replace('#','') || 'demo';
+ document.querySelectorAll('[data-page]').forEach(el => { el.hidden = el.dataset.page !== page; });
+ document.querySelectorAll('.nav-link').forEach(a => {
+ const target = a.getAttribute('href').replace('#','') || 'demo';
+ const active = target === page;
+ a.classList.toggle('text-blue-700', active);
+ a.classList.toggle('border-blue-600', active);
+ a.classList.toggle('text-zinc-500', !active);
+ a.classList.toggle('border-transparent', !active);
+ });
+}
+window.addEventListener('hashchange', route);
+route();
+/* ══ SHARED HELPERS ══ */
const q = id => document.getElementById(id);
-/* ── Log ── */
-function log(panelId, msg, type) {
- const panel = q(panelId);
- const colors = { ok:'text-green-400', err:'text-red-400', info:'text-blue-400', recv:'text-purple-400', warn:'text-amber-400' };
+function appendLog(panelId, msg, type) {
+ const colors = { ok:'text-green-700', err:'text-red-600', info:'text-blue-700', recv:'text-purple-700', warn:'text-amber-700' };
const row = document.createElement('div');
row.className = 'flex gap-2';
row.innerHTML =
- `
${new Date().toISOString().slice(11,23)}` +
- `
${String(msg).replace(/&/g,'&').replace(//g,'>')}`;
+ `
${new Date().toISOString().slice(11,23)}` +
+ `
${String(msg).replace(/&/g,'&').replace(//g,'>')}`;
+ const panel = q(panelId);
panel.appendChild(row);
panel.scrollTop = panel.scrollHeight;
}
-/* ── Status dots ── */
function setDot(dotId, stateId, s) {
- const cfg = {
- READY: { bg:'#22c55e', glow:'0 0 5px #22c55e', anim:false },
- SENDING: { bg:'#3b82f6', glow:'0 0 5px #3b82f6', anim:false },
- RECEIVING: { bg:'#a855f7', glow:'0 0 5px #a855f7', anim:false },
- ERROR: { bg:'#ef4444', glow:'none', anim:false },
- CLOSED: { bg:'#52525b', glow:'none', anim:false },
- }[s] || { bg:'#eab308', glow:'none', anim:true };
+ const cfg = { READY:{bg:'#22c55e'}, SENDING:{bg:'#3b82f6'}, RECEIVING:{bg:'#a855f7'}, ERROR:{bg:'#ef4444'}, CLOSED:{bg:'#d4d4d8'} }[s] || { bg:'#fbbf24', pulse:true };
const el = q(dotId);
el.style.background = cfg.bg;
- el.style.boxShadow = cfg.glow;
- el.className = `w-2 h-2 rounded-full shrink-0${cfg.anim ? ' blink' : ''}`;
- q(stateId).textContent = s;
+ el.className = `w-2 h-2 rounded-full shrink-0${cfg.pulse ? ' animate-pulse' : ''}`;
+ if (stateId) q(stateId).textContent = s;
}
-/* ── Target selector styling ── */
-function refreshTargetStyles() {
- document.querySelectorAll('.target-opt').forEach(lbl => {
- const inp = lbl.querySelector('input');
- if (inp.checked) {
- lbl.className = lbl.className.replace('border-zinc-700 text-zinc-500', 'border-blue-600 text-blue-400 bg-blue-950/40');
- } else {
- lbl.className = lbl.className.replace('border-blue-600 text-blue-400 bg-blue-950/40', 'border-zinc-700 text-zinc-500');
- }
- });
-}
-function refreshModeStyles(name) {
- document.querySelectorAll(`input[name="${name}"]`).forEach(inp => {
- const lbl = inp.closest('label');
- if (inp.checked) {
- lbl.className = lbl.className.replace('border-zinc-700 text-zinc-500', 'border-green-700 text-green-400 bg-green-950/40');
- } else {
- lbl.className = lbl.className.replace('border-green-700 text-green-400 bg-green-950/40', 'border-zinc-700 text-zinc-500');
- }
- });
+function makeArrowBuf() {
+ const t = tableFromArrays({ id:[1,2,3,4,5], label:['alpha','beta','gamma','delta','epsilon'], value:[1.1,2.2,3.3,4.4,5.5] });
+ return { buf: tableToIPC(t), schema: t.schema.toString() };
}
-refreshTargetStyles();
-refreshModeStyles('mode-alpha');
-refreshModeStyles('mode-beta');
-/* ── Arrow data factory ── */
-function makeArrowBuf() {
- const table = tableFromArrays({
- id: [1, 2, 3, 4, 5],
- label: ['alpha','beta','gamma','delta','epsilon'],
- value: [1.1, 2.2, 3.3, 4.4, 5.5],
+/* ══ TARGET / MODE RADIO STYLING ══ */
+function styleRadios(selector, activeClass) {
+ document.querySelectorAll(selector).forEach(lbl => {
+ const on = lbl.querySelector('input').checked;
+ lbl.classList.toggle(activeClass, on);
+ lbl.classList.toggle('border-zinc-300', !on);
+ lbl.classList.toggle('text-zinc-500', !on);
});
- return { buf: tableToIPC(table), schema: table.schema.toString() };
}
+styleRadios('.target-opt', 'border-blue-500');
+styleRadios('.mode-opt', 'border-green-500');
+document.querySelectorAll('input[name="target"]').forEach(r => r.addEventListener('change', () => styleRadios('.target-opt','border-blue-500')));
+document.querySelectorAll('input[name^="mode-"]').forEach(r => r.addEventListener('change', () => styleRadios('.mode-opt','border-green-500')));
-/* ── Inspector ── */
+/* ══ INSPECTOR ══ */
function updateInspector(meta, from, to) {
q('inspector-empty').classList.add('hidden');
q('inspector-grid').classList.remove('hidden');
-
const live = q('inspector-live');
live.style.background = '#22c55e';
- live.style.boxShadow = '0 0 4px #22c55e';
- setTimeout(() => { live.style.background = '#52525b'; live.style.boxShadow = 'none'; }, 1200);
-
- const set = (id, val) => { q(id).textContent = (val !== undefined && val !== null) ? String(val) : '—'; };
- set('insp-source', from || meta.source);
+ setTimeout(() => { live.style.background = '#d4d4d8'; }, 1200);
+ const set = (id, val) => { q(id).textContent = (val != null) ? String(val) : '—'; };
+ set('insp-source', from);
set('insp-receiver', meta.receiver || to);
- set('insp-msgid', meta.messageId || '—');
- set('insp-ts', meta.timestamp ? new Date(meta.timestamp).toISOString().slice(11,23) : '—');
- set('insp-format', meta.format);
- set('insp-rows', meta.rows !== undefined ? meta.rows : '—');
- set('insp-cols', meta.cols !== undefined ? meta.cols : '—');
- set('insp-size', meta.size ? meta.size + ' B' : '—');
- set('insp-proc', meta.processingTime !== undefined ? meta.processingTime + ' ms' : '—');
+ set('insp-msgid', meta.messageId || '—');
+ set('insp-ts', meta.timestamp ? new Date(meta.timestamp).toISOString().slice(11,23) : '—');
+ set('insp-format', meta.format);
+ set('insp-rows', meta.rows ?? '—');
+ set('insp-cols', meta.cols ?? '—');
+ set('insp-size', meta.size ? meta.size + ' B' : '—');
+ set('insp-proc', meta.processingTime != null ? meta.processingTime + ' ms' : '—');
q('insp-zc').textContent = meta.isZeroCopy === true ? '⚡ yes' : meta.isZeroCopy === false ? 'no' : '—';
- q('insp-zc').className = `font-mono text-[11px] ${meta.isZeroCopy ? 'text-green-400' : 'text-zinc-500'}`;
- set('insp-corr', meta.correlationId || '—');
- const schema = meta.schema || '—';
- q('insp-schema').textContent = schema.length > 35 ? schema.slice(0,33)+'…' : schema;
- q('insp-schema').title = schema;
- set('insp-recvat', meta.receivedAt ? new Date(meta.receivedAt).toISOString().slice(11,23) : '—');
+ set('insp-corr', meta.correlationId || '—');
+ set('insp-recvat', meta.receivedAt ? new Date(meta.receivedAt).toISOString().slice(11,23) : '—');
}
-/* ══════════════════════════════
- EMITTERS
-══════════════════════════════ */
-function makeEmitter(frameId, logId, dotId, stateId, hsPrefix, onReady_cb) {
- const emitter = new ArrowParentEmitter(q(frameId), { handshakeTimeout:8000, ackTimeout:5000, allowedOrigins:['*'] });
- emitter.onStateChange((next, prev) => {
+/* ══ DEMO PAGE EMITTERS ══ */
+let alphaReady = false, betaReady = false, lastReply = null;
+let sent = 0, acked = 0;
+
+function makeEmitter(frameId, logId, dotId, stateId, onReadyCb) {
+ const e = new ArrowParentEmitter(q(frameId), { handshakeTimeout:8000, ackTimeout:5000, allowedOrigins:['*'] });
+ e.onStateChange((next, prev) => {
setDot(dotId, stateId, next);
- log(logId, `State: ${prev} → ${next}`, 'info');
+ appendLog(logId, `State: ${prev} → ${next}`, 'info');
if (next === State.READY && prev === State.CONNECTING) {
- log(logId, '✅ Handshake complete', 'ok');
- onReady_cb();
+ appendLog(logId, '✅ Handshake complete', 'ok');
+ onReadyCb();
}
- }).onError(err => log(logId, `❌ ${err.message}`, 'err'));
- return emitter;
+ }).onError(err => appendLog(logId, `❌ ${err.message}`, 'err'));
+ return e;
}
-let emitterAlpha = makeEmitter('frame-alpha', 'log-alpha', 'alpha-dot', 'alpha-state', 'alpha', () => {
+let emitterAlpha = makeEmitter('frame-alpha','log-alpha','alpha-dot','alpha-state', () => {
alphaReady = true;
- if (!betaReady) {
- ['hs-child-ready','hs-parent-ack'].forEach(id => {
- q(id).textContent = q(id).textContent.replace('⬜','✅');
- q(id).className = 'text-green-500';
- });
- }
+ if (!betaReady) { ['hs-child-ready','hs-parent-ack'].forEach(id => { q(id).textContent = q(id).textContent.replace('⬜','✅'); q(id).className='text-green-700'; }); }
refreshSendButtons();
});
-
-let emitterBeta = makeEmitter('frame-beta', 'log-beta', 'beta-dot', 'beta-state', 'beta', () => {
+let emitterBeta = makeEmitter('frame-beta','log-beta','beta-dot','beta-state', () => {
betaReady = true;
- if (alphaReady) {
- q('hs-connected').textContent = '✅ Ready';
- q('hs-connected').className = 'text-green-500';
- log('parent-log', '✅ Both children ready', 'ok');
- }
+ if (alphaReady) { q('hs-connected').textContent='✅ Ready'; q('hs-connected').className='text-green-700'; appendLog('parent-log','✅ Both children ready','ok'); }
refreshSendButtons();
});
-setDot('parent-dot', 'parent-state', 'CONNECTING');
-log('parent-log', 'Waiting for child iframes…', 'info');
+setDot('parent-dot','parent-state','CONNECTING');
+appendLog('parent-log','Waiting for child iframes…','info');
-/* ── SAB ── */
-const sabOk = emitterAlpha.isSABSupported();
+const sabOk = emitterAlpha.isSABSupported();
const notice = q('sab-notice');
-if (sabOk) {
- notice.textContent = '⚡ SharedArrayBuffer available — zero-copy enabled';
- notice.className += ' text-green-600';
-} else {
- notice.textContent = '⚠ SharedArrayBuffer unavailable (COOP/COEP missing) — copy mode only';
- notice.className += ' text-amber-600';
-}
+notice.textContent = sabOk ? '⚡ SharedArrayBuffer available — zero-copy enabled' : '⚠ SharedArrayBuffer unavailable (COOP/COEP missing) — copy mode only';
+notice.className += sabOk ? ' text-green-700' : ' text-amber-600';
-/* ── Send button enablement ── */
function refreshSendButtons() {
const sel = document.querySelector('input[name="target"]:checked').value;
- const ok = (sel === 'alpha' && alphaReady) ||
- (sel === 'beta' && betaReady) ||
- (sel === 'both' && alphaReady && betaReady);
+ const ok = (sel==='alpha'&&alphaReady)||(sel==='beta'&&betaReady)||(sel==='both'&&alphaReady&&betaReady);
['btn-auto','btn-json','btn-copy'].forEach(id => q(id).disabled = !ok);
q('btn-zerocopy').disabled = !ok || !sabOk;
- if (alphaReady || betaReady) setDot('parent-dot', 'parent-state', 'READY');
+ if (alphaReady||betaReady) setDot('parent-dot','parent-state','READY');
}
-
-/* ── Target selector ── */
-document.querySelectorAll('input[name="target"]').forEach(r => {
- r.addEventListener('change', () => { refreshTargetStyles(); refreshSendButtons(); });
-});
+document.querySelectorAll('input[name="target"]').forEach(r => r.addEventListener('change', refreshSendButtons));
function getTargets() {
const sel = document.querySelector('input[name="target"]:checked').value;
- if (sel === 'alpha') return [{ e:emitterAlpha, name:'child-alpha', log:'log-alpha' }];
- if (sel === 'beta') return [{ e:emitterBeta, name:'child-beta', log:'log-beta' }];
- return [{ e:emitterAlpha, name:'child-alpha', log:'log-alpha' }, { e:emitterBeta, name:'child-beta', log:'log-beta' }];
+ if (sel==='alpha') return [{e:emitterAlpha, name:'child-alpha', log:'log-alpha'}];
+ if (sel==='beta') return [{e:emitterBeta, name:'child-beta', log:'log-beta'}];
+ return [{e:emitterAlpha,name:'child-alpha',log:'log-alpha'},{e:emitterBeta,name:'child-beta',log:'log-beta'}];
}
-/* ── Generic send ── */
-function doSend(label, makeFn) {
- getTargets().forEach(({ e, name }) => {
- sent++;
- q('stat-sent').textContent = sent;
- log('parent-log', `→ [${name}] ${label}…`);
+function doSend(label, fn) {
+ getTargets().forEach(({e, name}) => {
+ sent++; q('stat-sent').textContent = sent;
+ appendLog('parent-log', `→ [${name}] ${label}…`);
const t0 = performance.now();
- makeFn(e).then(ack => {
- const rtt = Math.round(performance.now() - t0);
- acked++;
- q('stat-acked').textContent = acked;
- q('stat-rtt').textContent = rtt;
- log('parent-log', `✅ [${name}] rows=${ack.rows} cols=${ack.cols} proc=${ack.processingTime}ms zc=${ack.isZeroCopy}`, 'ok');
- }).catch(err => log('parent-log', `❌ [${name}] ${err.message}`, 'err'));
+ fn(e).then(ack => {
+ const rtt = Math.round(performance.now()-t0);
+ acked++; q('stat-acked').textContent = acked; q('stat-rtt').textContent = rtt;
+ appendLog('parent-log', `✅ [${name}] rows=${ack.rows} proc=${ack.processingTime}ms zc=${ack.isZeroCopy}`, 'ok');
+ }).catch(err => appendLog('parent-log', `❌ [${name}] ${err.message}`, 'err'));
});
}
-q('btn-auto').addEventListener('click', () => doSend('send() auto', e => {
- const a = makeArrowBuf(); return e.send(a.buf, { format:'auto', schema:a.schema, correlationId:'auto-'+Date.now() });
-}));
-q('btn-json').addEventListener('click', () => doSend('sendJSON()', e =>
- e.sendJSON([{id:1,name:'Alice',score:95.5},{id:2,name:'Bob',score:87.3},{id:3,name:'Charlie',score:92.1}], { correlationId:'json-'+Date.now() })
-));
-q('btn-copy').addEventListener('click', () => doSend('sendArrowCopy()', e => {
- const a = makeArrowBuf(); return e.sendArrowCopy(a.buf, { schema:a.schema, correlationId:'copy-'+Date.now() });
-}));
-q('btn-zerocopy').addEventListener('click', () => doSend('sendArrowZeroCopy()', e => {
- const a = makeArrowBuf(); return e.sendArrowZeroCopy(a.buf);
-}));
+q('btn-auto').addEventListener('click', () => doSend('send()', e => { const a=makeArrowBuf(); return e.send(a.buf,{format:'auto',schema:a.schema,correlationId:'auto-'+Date.now()}); }));
+q('btn-json').addEventListener('click', () => doSend('sendJSON()', e => e.sendJSON([{id:1,name:'Alice',score:95.5},{id:2,name:'Bob',score:87.3},{id:3,name:'Charlie',score:92.1}],{correlationId:'json-'+Date.now()})));
+q('btn-copy').addEventListener('click', () => doSend('sendArrowCopy()', e => { const a=makeArrowBuf(); return e.sendArrowCopy(a.buf,{schema:a.schema}); }));
+q('btn-zerocopy').addEventListener('click', () => doSend('sendArrowZeroCopy()', e => { const a=makeArrowBuf(); return e.sendArrowZeroCopy(a.buf); }));
q('btn-close').addEventListener('click', () => {
- emitterAlpha.close(); emitterBeta.close();
- alphaReady = betaReady = false;
- ['btn-auto','btn-json','btn-copy','btn-zerocopy'].forEach(id => q(id).disabled = true);
- log('parent-log', 'Connections closed', 'err');
- setDot('parent-dot', 'parent-state', 'CLOSED');
+ emitterAlpha.close(); emitterBeta.close(); alphaReady=betaReady=false;
+ ['btn-auto','btn-json','btn-copy','btn-zerocopy'].forEach(id=>q(id).disabled=true);
+ appendLog('parent-log','Connections closed','err'); setDot('parent-dot','parent-state','CLOSED');
});
-/* ── Relay ── */
q('btn-relay').addEventListener('click', () => {
if (!lastReply) return;
- const from = lastReply.sourceId;
- const toName = from === 'child-alpha' ? 'child-beta' : 'child-alpha';
- const toFrame = toName === 'child-alpha' ? q('frame-alpha') : q('frame-beta');
- toFrame.contentWindow.postMessage({ __iframeFlight:'relay', from, payload:lastReply.data }, '*');
- log('parent-log', `📨 Relay ${from} → ${toName}`, 'info');
- q('btn-relay').disabled = true;
- lastReply = null;
+ const from=lastReply.sourceId, toName=from==='child-alpha'?'child-beta':'child-alpha';
+ (toName==='child-alpha'?q('frame-alpha'):q('frame-beta')).contentWindow.postMessage({__iframeFlight:'relay',from,payload:lastReply.data},'*');
+ appendLog('parent-log',`📨 Relay ${from} → ${toName}`,'info');
+ q('btn-relay').disabled=true; lastReply=null;
});
-/* ── Messages from children ── */
window.addEventListener('message', e => {
if (!e.data) return;
-
if (e.data.__iframeFlight === 'log') {
- const { sourceId, msg, type, state, meta } = e.data;
- const lid = sourceId === 'child-alpha' ? 'log-alpha' : sourceId === 'child-beta' ? 'log-beta' : 'parent-log';
- log(lid, msg, type);
- if (state) {
- if (sourceId === 'child-alpha') setDot('alpha-dot', 'alpha-state', state);
- if (sourceId === 'child-beta') setDot('beta-dot', 'beta-state', state);
- }
+ const {sourceId,msg,type,state,meta} = e.data;
+ const lid = sourceId==='child-alpha'?'log-alpha':sourceId==='child-beta'?'log-beta':'parent-log';
+ appendLog(lid, msg, type);
+ if (state) { if(sourceId==='child-alpha')setDot('alpha-dot','alpha-state',state); if(sourceId==='child-beta')setDot('beta-dot','beta-state',state); }
if (meta) updateInspector(meta, sourceId, 'parent');
- if (msg && msg.includes('Paused')) {
- if (sourceId === 'child-alpha') q('btn-resume-alpha').disabled = false;
- if (sourceId === 'child-beta') q('btn-resume-beta').disabled = false;
- }
+ if (msg?.includes('Paused')) { if(sourceId==='child-alpha')q('btn-resume-alpha').disabled=false; if(sourceId==='child-beta')q('btn-resume-beta').disabled=false; }
return;
}
-
if (e.data.__iframeFlight === 'child-reply') {
- const r = e.data;
- lastReply = r;
- log('parent-log', `↑ Reply from ${r.sourceId}: ${JSON.stringify(r.data||{}).slice(0,60)}`, 'recv');
- updateInspector({ messageId:r.messageId, timestamp:r.timestamp, format:'postMessage (raw)', receiver:'parent' }, r.sourceId, 'parent');
- q('btn-relay').disabled = false;
+ const r=e.data; lastReply=r;
+ appendLog('parent-log',`↑ Reply from ${r.sourceId}: ${JSON.stringify(r.data||{}).slice(0,60)}`,'recv');
+ updateInspector({messageId:r.messageId,timestamp:r.timestamp,format:'postMessage (raw)',receiver:'parent'},r.sourceId,'parent');
+ q('btn-relay').disabled=false;
}
});
-/* ── Mode selectors ── */
-function reloadChild(frameId, logId, dotId, stateId, sourceId, resumeBtnId, getEmitter, setEmitter) {
- const mode = document.querySelector(`input[name="mode-${sourceId === 'child-alpha' ? 'alpha' : 'beta'}"]:checked`).value;
- q(logId).innerHTML = '';
- setDot(dotId, stateId, 'CONNECTING');
- q(resumeBtnId).disabled = true;
- if (sourceId === 'child-alpha') alphaReady = false; else betaReady = false;
- ['btn-auto','btn-json','btn-copy','btn-zerocopy'].forEach(id => q(id).disabled = true);
- log('parent-log', `Reloading ${sourceId} (${mode === 'first' ? 'FIRST_MESSAGE' : 'CONTINUOUS'})…`, 'info');
- getEmitter().close();
- q(frameId).src = `./child.html?sourceId=${sourceId}${mode === 'first' ? '&mode=first' : ''}`;
- const ne = makeEmitter(frameId, logId, dotId, stateId, '', () => {
- if (sourceId === 'child-alpha') alphaReady = true; else betaReady = true;
- setEmitter(ne);
- refreshSendButtons();
- });
- setEmitter(ne);
+function reloadChild(frameId,logId,dotId,stateId,sourceId,resumeBtnId,getE,setE) {
+ const mode = document.querySelector(`input[name="mode-${sourceId==='child-alpha'?'alpha':'beta'}"]:checked`).value;
+ q(logId).innerHTML=''; setDot(dotId,stateId,'CONNECTING'); q(resumeBtnId).disabled=true;
+ if(sourceId==='child-alpha')alphaReady=false; else betaReady=false;
+ ['btn-auto','btn-json','btn-copy','btn-zerocopy'].forEach(id=>q(id).disabled=true);
+ appendLog('parent-log',`Reloading ${sourceId} (${mode==='first'?'FIRST_MESSAGE':'CONTINUOUS'})…`,'info');
+ getE().close();
+ q(frameId).src = `./child.html?sourceId=${sourceId}${mode==='first'?'&mode=first':''}`;
+ const ne = makeEmitter(frameId,logId,dotId,stateId,()=>{ if(sourceId==='child-alpha')alphaReady=true; else betaReady=true; setE(ne); refreshSendButtons(); });
+ setE(ne);
}
+document.querySelectorAll('input[name="mode-alpha"]').forEach(r=>r.addEventListener('change',()=>{styleRadios('.mode-opt','border-green-500');reloadChild('frame-alpha','log-alpha','alpha-dot','alpha-state','child-alpha','btn-resume-alpha',()=>emitterAlpha,ne=>emitterAlpha=ne);}));
+document.querySelectorAll('input[name="mode-beta"]').forEach(r=>r.addEventListener('change',()=>{styleRadios('.mode-opt','border-green-500');reloadChild('frame-beta','log-beta','beta-dot','beta-state','child-beta','btn-resume-beta',()=>emitterBeta,ne=>emitterBeta=ne);}));
+q('btn-resume-alpha').addEventListener('click',()=>{ q('frame-alpha').contentWindow.postMessage({__iframeFlight:'resume'},'*'); q('btn-resume-alpha').disabled=true; appendLog('parent-log','▶ resume → child-alpha','info'); });
+q('btn-resume-beta').addEventListener('click',()=>{ q('frame-beta').contentWindow.postMessage({__iframeFlight:'resume'},'*'); q('btn-resume-beta').disabled=true; appendLog('parent-log','▶ resume → child-beta','info'); });
+
+/* ══ QUICK START PAGE EMITTER ══ */
+let qsEmitter = new ArrowParentEmitter(q('qs-frame'), { handshakeTimeout:8000, ackTimeout:5000, allowedOrigins:['*'] });
+qsEmitter.onStateChange((next,prev) => {
+ setDot('qs-dot', null, next);
+ if (next===State.READY) { q('qs-send').disabled=false; appendLog('qs-log','✅ Ready — click send()','ok'); }
+}).onError(err => appendLog('qs-log',`❌ ${err.message}`,'err'));
-document.querySelectorAll('input[name="mode-alpha"]').forEach(r => r.addEventListener('change', () => {
- refreshModeStyles('mode-alpha');
- reloadChild('frame-alpha','log-alpha','alpha-dot','alpha-state','child-alpha','btn-resume-alpha', () => emitterAlpha, ne => emitterAlpha = ne);
-}));
-document.querySelectorAll('input[name="mode-beta"]').forEach(r => r.addEventListener('change', () => {
- refreshModeStyles('mode-beta');
- reloadChild('frame-beta','log-beta','beta-dot','beta-state','child-beta','btn-resume-beta', () => emitterBeta, ne => emitterBeta = ne);
-}));
-
-q('btn-resume-alpha').addEventListener('click', () => {
- q('frame-alpha').contentWindow.postMessage({ __iframeFlight:'resume' }, '*');
- q('btn-resume-alpha').disabled = true;
- log('parent-log', '▶ resumeListening() → child-alpha', 'info');
+window.addEventListener('message', e => {
+ if (e.data?.__iframeFlight==='log' && e.data.sourceId==='quickstart')
+ appendLog('qs-log', e.data.msg, e.data.type);
});
-q('btn-resume-beta').addEventListener('click', () => {
- q('frame-beta').contentWindow.postMessage({ __iframeFlight:'resume' }, '*');
- q('btn-resume-beta').disabled = true;
- log('parent-log', '▶ resumeListening() → child-beta', 'info');
+
+q('qs-send').addEventListener('click', () => {
+ const a = makeArrowBuf();
+ appendLog('qs-log','→ send()…');
+ qsEmitter.send(a.buf,{format:'auto',schema:a.schema}).then(ack => {
+ appendLog('qs-log',`✅ rows=${ack.rows} cols=${ack.cols} zeroCopy=${ack.isZeroCopy}`,'ok');
+ }).catch(err => appendLog('qs-log',`❌ ${err.message}`,'err'));
});
-/* ── Tabs ── */
+/* ══ TABS ══ */
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
- document.querySelectorAll('.tab-btn').forEach(b => {
- b.classList.remove('text-blue-400','border-blue-400');
- b.classList.add('text-zinc-500','border-transparent');
- });
+ document.querySelectorAll('.tab-btn').forEach(b => { b.classList.replace('text-blue-700','text-zinc-500'); b.classList.replace('border-blue-600','border-transparent'); });
document.querySelectorAll('.tab-panel').forEach(p => p.classList.add('hidden'));
- btn.classList.add('text-blue-400','border-blue-400');
- btn.classList.remove('text-zinc-500','border-transparent');
+ btn.classList.replace('text-zinc-500','text-blue-700'); btn.classList.replace('border-transparent','border-blue-600');
q(btn.dataset.tab).classList.remove('hidden');
});
});
-/* ── Copy buttons ── */
+/* ══ COPY BUTTONS ══ */
document.querySelectorAll('.copy-btn').forEach(btn => {
btn.addEventListener('click', () => {
navigator.clipboard.writeText(q(btn.dataset.target).textContent).then(() => {
- const svg = btn.querySelector('svg');
- btn.classList.add('text-green-400','border-green-700');
- svg.nextSibling.textContent = ' Copied!';
- setTimeout(() => { btn.classList.remove('text-green-400','border-green-700'); svg.nextSibling.textContent = ' Copy'; }, 1800);
+ btn.classList.add('text-green-700','border-green-400');
+ btn.querySelector('svg').nextSibling.textContent = ' Copied!';
+ setTimeout(() => { btn.classList.remove('text-green-700','border-green-400'); btn.querySelector('svg').nextSibling.textContent = ' Copy'; }, 1800);
});
});
});