@@ -498,20 +498,75 @@ <h1>PebbleOS Emulator</h1>
498498 } ;
499499 }
500500
501- // Config pages: open in a popup with return_to pointing at our
502- // return page, which posts the response fragment back.
503- function openConfigUrl ( url , onClosed ) {
504- const ret = new URL ( 'config-return.html' , window . location . href ) . href ;
505- const sep = url . includes ( '?' ) ? '&' : '?' ;
506- const full = url + sep + 'return_to=' + encodeURIComponent ( ret + '#' ) ;
501+ // Config pages. Hosted (http/https) pages open in a popup with the
502+ // Rebble return_to convention. Clay-style data: pages cannot be
503+ // window.open()ed (browsers block top-level data: navigation), so
504+ // they render in an in-page modal iframe instead: the HTML is
505+ // decoded and Clay's default close target (pebblejs://close) is
506+ // rewritten to our same-origin return page, which posts the
507+ // response fragment back via postMessage.
508+ function listenForConfigClose ( onClosed , cleanup ) {
507509 const handler = ( e ) => {
508510 if ( e . origin !== window . location . origin ) return ;
509511 if ( e . data && e . data . type === 'pkjs-config-closed' ) {
510512 window . removeEventListener ( 'message' , handler ) ;
513+ if ( cleanup ) cleanup ( ) ;
511514 onClosed ( e . data . response ) ;
512515 }
513516 } ;
514517 window . addEventListener ( 'message' , handler ) ;
518+ return handler ;
519+ }
520+
521+ function decodeDataUrl ( url ) {
522+ const m = / ^ d a t a : [ ^ , ] * ?( ; b a s e 6 4 ) ? , ( .* ) $ / s. exec ( url ) ;
523+ if ( ! m ) return null ;
524+ return m [ 1 ] ? atob ( m [ 2 ] ) : decodeURIComponent ( m [ 2 ] ) ;
525+ }
526+
527+ function openConfigUrl ( url , onClosed ) {
528+ const ret = new URL ( 'config-return.html' , window . location . href ) . href ;
529+
530+ if ( url . startsWith ( 'data:' ) ) {
531+ let html = decodeDataUrl ( url ) ;
532+ if ( html === null ) { say ( 'Could not decode the settings page.' ) ; return ; }
533+ // Clay falls back to pebblejs://close when no return_to query
534+ // parameter reaches it (data: URLs have no query string).
535+ html = html . split ( 'pebblejs://close' ) . join ( ret ) ;
536+
537+ const overlay = document . createElement ( 'div' ) ;
538+ overlay . style . cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.7);' +
539+ 'z-index:1000;display:flex;align-items:center;justify-content:center;' ;
540+ const box = document . createElement ( 'div' ) ;
541+ box . style . cssText = 'position:relative;width:min(440px,94vw);height:min(720px,92vh);' +
542+ 'background:#fff;border-radius:8px;overflow:hidden;display:flex;flex-direction:column;' ;
543+ const bar = document . createElement ( 'div' ) ;
544+ bar . style . cssText = 'background:#1e1e30;color:#d8d8e0;font-size:12px;' +
545+ 'padding:6px 10px;display:flex;justify-content:space-between;align-items:center;' ;
546+ bar . innerHTML = '<span>App settings</span>' ;
547+ const closeBtn = document . createElement ( 'button' ) ;
548+ closeBtn . textContent = 'Cancel' ;
549+ closeBtn . style . cssText = 'font-size:12px;padding:2px 10px;' ;
550+ bar . appendChild ( closeBtn ) ;
551+ const frame = document . createElement ( 'iframe' ) ;
552+ frame . style . cssText = 'border:0;flex:1;width:100%;background:#fff;' ;
553+ frame . srcdoc = html ;
554+ box . appendChild ( bar ) ; box . appendChild ( frame ) ;
555+ overlay . appendChild ( box ) ;
556+ document . body . appendChild ( overlay ) ;
557+
558+ const handler = listenForConfigClose ( onClosed , ( ) => overlay . remove ( ) ) ;
559+ closeBtn . addEventListener ( 'click' , ( ) => {
560+ window . removeEventListener ( 'message' , handler ) ;
561+ overlay . remove ( ) ;
562+ onClosed ( '' ) ; // empty response = user cancelled
563+ } ) ;
564+ return ;
565+ }
566+
567+ const sep = url . includes ( '?' ) ? '&' : '?' ;
568+ const full = url + sep + 'return_to=' + encodeURIComponent ( ret + '#' ) ;
569+ listenForConfigClose ( onClosed ) ;
515570 window . open ( full , 'pkjs-config' , 'width=420,height=680' ) ;
516571 }
517572
@@ -612,8 +667,9 @@ <h1>PebbleOS Emulator</h1>
612667 await installBytes ( new Uint8Array ( await f . arrayBuffer ( ) ) , f . name ) ;
613668 } ) ;
614669
615- // Debug/testing hook: install from raw bytes without the store .
670+ // Debug/testing hooks .
616671 window . __installPbw = installBytes ;
672+ window . __openConfig = openConfigUrl ;
617673
618674 // Drag-drop a .pbw anywhere on the page.
619675 document . addEventListener ( 'dragover' , ( e ) => {
0 commit comments