mirror of
https://github.com/telemt/telemt.git
synced 2026-11-02 13:52:03 +03:00
WEB Bridge Sideband
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
(()=>{'use strict';
|
||||
let bootstrap="__BOOTSTRAP__",hello=false,emitted=0,boundaryTimer=null;
|
||||
const relayOrigin='https://__HOST__',requestMs=__BRIDGE_REQUEST_SECS__*1000;
|
||||
function eventBit(event){
|
||||
if(event==='runtime_started')return 1;if(event==='status_posted')return 2;if(event==='hello_received')return 4;if(event==='boundary_timeout')return 8;
|
||||
if(event==='hello_timeout')return 16;if(event==='client_close_before_hello')return 32;if(event==='document_unloaded_before_hello')return 64;
|
||||
return event==='runtime_error_before_hello'?128:0;
|
||||
}
|
||||
function discard(response){try{if(response.body)response.body.cancel().catch(()=>{})}catch(error){}}
|
||||
function report(event){
|
||||
if(hello&&event!=='hello_received')return;const bit=eventBit(event);if(!bit||(emitted&bit))return;emitted|=bit;
|
||||
let timer=null;
|
||||
try{
|
||||
const controller=new AbortController(),body=JSON.stringify({v:1,event});timer=setTimeout(()=>controller.abort(),requestMs);
|
||||
fetch(relayOrigin+'/api/v1/diagnostic',{method:'POST',body,signal:controller.signal,keepalive:true,mode:'same-origin',credentials:'omit',cache:'no-store',redirect:'error',referrerPolicy:'no-referrer',headers:{Authorization:'Bearer '+bootstrap,'Content-Type':'application/json'}})
|
||||
.then(discard,()=>{}).then(()=>clearTimeout(timer),()=>clearTimeout(timer));
|
||||
}catch(error){if(timer)clearTimeout(timer)}
|
||||
}
|
||||
const boundaryWall=Date.now()+requestMs,boundaryMonotonic=performance.now()+requestMs;
|
||||
function waitBoundary(){
|
||||
boundaryTimer=null;if(hello||(emitted&2))return;const remaining=Math.min(boundaryWall-Date.now(),boundaryMonotonic-performance.now());
|
||||
if(remaining>0){boundaryTimer=setTimeout(waitBoundary,remaining);return}report('boundary_timeout');
|
||||
}
|
||||
function clearBoundary(){try{if(boundaryTimer)clearTimeout(boundaryTimer)}catch(error){}boundaryTimer=null}
|
||||
function runtimeStarted(){report('runtime_started');try{boundaryTimer=setTimeout(waitBoundary,Math.max(1,Math.min(boundaryWall-Date.now(),boundaryMonotonic-performance.now())))}catch(error){}}
|
||||
function boundaryActivated(){clearBoundary()}
|
||||
function statusPosted(){clearBoundary();report('status_posted')}
|
||||
function helloReceived(){clearBoundary();hello=true;report('hello_received')}
|
||||
function helloTimeout(){report('hello_timeout')}
|
||||
function clientCloseBeforeHello(){report('client_close_before_hello')}
|
||||
function documentUnloadedBeforeHello(){report('document_unloaded_before_hello')}
|
||||
function setBootstrap(value){if(/^[A-Za-z0-9_-]{43}$/.test(value))bootstrap=value}
|
||||
addEventListener('error',()=>report('runtime_error_before_hello'));
|
||||
addEventListener('unhandledrejection',()=>report('runtime_error_before_hello'));
|
||||
globalThis.TelemtBridgeDiagnostics=Object.freeze({runtimeStarted,boundaryActivated,statusPosted,helloReceived,helloTimeout,clientCloseBeforeHello,documentUnloadedBeforeHello,setBootstrap});
|
||||
})();
|
||||
@@ -6,6 +6,7 @@
|
||||
<title>Connection</title>
|
||||
</head>
|
||||
<body>
|
||||
__DIAGNOSTIC_RUNTIME__
|
||||
<script nonce="__NONCE__">
|
||||
__RESPONSE_RUNTIME__
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
(()=>{'use strict';
|
||||
let bootstrap="__BOOTSTRAP__";
|
||||
const relayOrigin='https://__HOST__',carrierCapabilities='https,https-lanes,websocket,websocket-lanes';
|
||||
__DIAGNOSTIC_BINDING__;
|
||||
__DIAGNOSTIC_RUNTIME_STARTED__;
|
||||
const responseBody=globalThis.TelemtBridgeResponse;if(!responseBody)throw new Error('missing response runtime');
|
||||
const requestSupport=globalThis.TelemtBridgeRequest;if(!requestSupport)throw new Error('missing request runtime');
|
||||
const bufferSupport=globalThis.TelemtBridgeBuffers;if(!bufferSupport)throw new Error('missing buffer runtime');
|
||||
@@ -23,7 +25,7 @@ const pending=[],upPending=[],recoveryPending=[],lanes=new Map(),closedLanes=new
|
||||
const canonicalFailures=['timeout','network','upgrade','http','protocol'];
|
||||
const failure=(reason,message)=>Object.assign(new Error(message||reason),{telemtReason:reason});
|
||||
const failureReason=(error,fallback)=>error&&canonicalFailures.includes(error.telemtReason)?error.telemtReason:fallback;
|
||||
const status=state=>{if(port&&!closed)port.postMessage({t:'status',state})};
|
||||
const status=__STATUS_FUNCTION__;
|
||||
const socketURL=()=>relayOrigin.replace(/^https:/,'wss:')+'/api/v1/ws';
|
||||
const requestClient=requestSupport.create({
|
||||
origin:()=>relayOrigin,closed:()=>closed,retryMs:()=>bridgeRetryMs,longPollMs:()=>longPollMs,requestMs:()=>bridgeRequestMs,
|
||||
@@ -62,7 +64,7 @@ function retireCarrier(policy){
|
||||
}
|
||||
lanes.clear();closedLanes.clear();closedLaneOrder.length=0;releasePending(pending,null);releasePending(recoveryPending,null);
|
||||
for(const id of retireAllStreams())if(port){const frame=closeFrame(id);port.postMessage(frame,[frame])}
|
||||
bootstrap=policy.bootstrap;batchLimit=policy.limits.carrier_batch_bytes;queueLimit=policy.limits.pending_bytes_per_session;
|
||||
bootstrap=policy.bootstrap;__DIAGNOSTIC_BOOTSTRAP_REPLACED__;batchLimit=policy.limits.carrier_batch_bytes;queueLimit=policy.limits.pending_bytes_per_session;
|
||||
queueItemLimit=policy.limits.pending_items_per_session;maxStreams=policy.limits.max_streams_per_session;
|
||||
laneQueueLimit=Math.min(queueLimit,8388608);laneItemLimit=Math.min(queueItemLimit,1024);
|
||||
longPollMs=policy.timeouts.long_poll_secs*1000;bridgeRequestMs=policy.timeouts.bridge_request_secs*1000;
|
||||
@@ -487,20 +489,20 @@ function close(notifyServer){
|
||||
buffers.assertEmpty();
|
||||
}
|
||||
function activatePort(nextPort){
|
||||
initialized=true;port=nextPort;
|
||||
initialized=true;port=nextPort;__DIAGNOSTIC_BOUNDARY_ACTIVATED__;
|
||||
port.onmessage=message=>{
|
||||
observeResumeTrigger();
|
||||
if(message.data instanceof ArrayBuffer){
|
||||
if(!createStarted){createStarted=true;if(helloTimer)clearTimeout(helloTimer);helloTimer=null;helloFrame=message.data;if(negotiationEnabled){negotiationStartedAt=Date.now();armCarrierDeadline(attemptEpoch)}createSession(attemptEpoch)}
|
||||
if(!createStarted){__DIAGNOSTIC_HELLO_RECEIVED__;createStarted=true;if(helloTimer)clearTimeout(helloTimer);helloTimer=null;helloFrame=message.data;if(negotiationEnabled){negotiationStartedAt=Date.now();armCarrierDeadline(attemptEpoch)}createSession(attemptEpoch)}
|
||||
else{
|
||||
let data;try{data=acceptNativeFrames(message.data)}catch(error){fail(error&&error.telemtReason==='capacity'?'capacity':'protocol');return}if(!data)return;
|
||||
if(recoveryController.active()&&!recoveryReplaced){if(!reserve(data,null)){fail('capacity');return}recoveryPending.push(data)}
|
||||
else if(!carrierCommitted){if(!reserve(data,null)){fail('capacity');return}pending.push(data);maybeStartCandidate()}
|
||||
else queueCarrier(data);
|
||||
}
|
||||
}else if(message.data&&message.data.t==='close'){status('failed');close(true)}
|
||||
}else if(message.data&&message.data.t==='close'){__DIAGNOSTIC_CLIENT_CLOSE__;status('failed');close(true)}
|
||||
};
|
||||
port.start();status('connecting');helloTimer=setTimeout(()=>fail('timeout'),bridgeRequestMs);
|
||||
port.start();status('connecting');helloTimer=setTimeout(__HELLO_TIMEOUT_CALLBACK__,bridgeRequestMs);
|
||||
}
|
||||
recoveryController=recoverySupport.create({
|
||||
budgetMs:()=>bridgeRecoveryMs,requestMs:()=>bridgeRequestMs,url:()=>relayOrigin+recoveryPath,token:()=>cleanupToken||sessionToken,
|
||||
@@ -539,5 +541,5 @@ function discoverAndroid(){
|
||||
discoverAndroid();
|
||||
addEventListener('online',observeResumeTrigger);
|
||||
if(globalThis.document&&typeof globalThis.document.addEventListener==='function')globalThis.document.addEventListener('visibilitychange',()=>{if(globalThis.document.visibilityState==='visible')observeResumeTrigger()});
|
||||
addEventListener('pagehide',()=>fail('navigation'),{once:true});
|
||||
addEventListener('pagehide',__PAGEHIDE_CALLBACK__,{once:true});
|
||||
})();
|
||||
|
||||
@@ -18,6 +18,30 @@ fn render_page(bootstrap: &str, candidate_count: usize) -> BridgePage {
|
||||
15,
|
||||
120,
|
||||
0,
|
||||
false,
|
||||
&SecureRandom::new(),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_diagnostic_page(bootstrap: &str) -> BridgePage {
|
||||
render(
|
||||
"proxy.example.com",
|
||||
bootstrap,
|
||||
2 * 1024 * 1024,
|
||||
32 * 1024 * 1024,
|
||||
16 * 1024,
|
||||
1024,
|
||||
true,
|
||||
4,
|
||||
[3, 5, 8, 12],
|
||||
25,
|
||||
10,
|
||||
90,
|
||||
15,
|
||||
15,
|
||||
120,
|
||||
0,
|
||||
true,
|
||||
&SecureRandom::new(),
|
||||
)
|
||||
}
|
||||
@@ -77,6 +101,7 @@ fn rendered_page_embeds_the_configured_bridge_timing_policy() {
|
||||
11,
|
||||
119,
|
||||
4,
|
||||
false,
|
||||
&SecureRandom::new(),
|
||||
);
|
||||
|
||||
@@ -128,6 +153,7 @@ fn disabled_negotiation_does_not_arm_a_carrier_deadline() {
|
||||
15,
|
||||
120,
|
||||
0,
|
||||
false,
|
||||
&SecureRandom::new(),
|
||||
);
|
||||
assert!(page.body.contains(
|
||||
@@ -190,3 +216,87 @@ fn rendered_page_preserves_exact_v1_status_control_envelope() {
|
||||
);
|
||||
assert!(!page.body.contains("port.postMessage({t:'status',state,"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bridge_diagnostic_sideband_is_absent_by_default() {
|
||||
let page = render_page("IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 4);
|
||||
|
||||
assert!(page.body.contains("<body>\n<script nonce=\""));
|
||||
assert!(page.body.contains(
|
||||
"carrierCapabilities='https,https-lanes,websocket,websocket-lanes';\nconst responseBody="
|
||||
));
|
||||
assert!(!page.body.contains("/api/v1/diagnostic"));
|
||||
assert!(!page.body.contains("TelemtBridgeDiagnostics"));
|
||||
for event in [
|
||||
"runtime_started",
|
||||
"status_posted",
|
||||
"hello_received",
|
||||
"boundary_timeout",
|
||||
"hello_timeout",
|
||||
"client_close_before_hello",
|
||||
"document_unloaded_before_hello",
|
||||
"runtime_error_before_hello",
|
||||
] {
|
||||
assert!(!page.body.contains(event));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enabled_bridge_diagnostics_use_the_https_sideband_only() {
|
||||
let page = render_diagnostic_page("JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ");
|
||||
|
||||
assert!(!page.body.contains("__"));
|
||||
assert!(page.body.contains("fetch(relayOrigin+'/api/v1/diagnostic'"));
|
||||
assert!(page.body.contains("JSON.stringify({v:1,event})"));
|
||||
assert!(page.body.contains("'Content-Type':'application/json'"));
|
||||
assert!(page.body.contains("keepalive:true"));
|
||||
for event in [
|
||||
"runtime_started",
|
||||
"status_posted",
|
||||
"hello_received",
|
||||
"boundary_timeout",
|
||||
"hello_timeout",
|
||||
"client_close_before_hello",
|
||||
"document_unloaded_before_hello",
|
||||
"runtime_error_before_hello",
|
||||
] {
|
||||
assert!(page.body.contains(event));
|
||||
}
|
||||
assert_eq!(page.body.matches("/api/v1/diagnostic").count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bridge_diagnostic_hooks_preserve_native_and_recovery_contracts() {
|
||||
let page = render_diagnostic_page("KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK");
|
||||
let status_report = page.body.find("clientDiagnostics.statusPosted()").unwrap();
|
||||
let native_status = page
|
||||
.body
|
||||
.find("port.postMessage({t:'status',state})")
|
||||
.unwrap();
|
||||
let hello_report = page.body.find("clientDiagnostics.helloReceived()").unwrap();
|
||||
let create_started = page.body.find("createStarted=true;if(helloTimer)").unwrap();
|
||||
let boundary_initialized = page.body.find("initialized=true;port=nextPort;").unwrap();
|
||||
let boundary_activated = page
|
||||
.body
|
||||
.find("clientDiagnostics.boundaryActivated()")
|
||||
.unwrap();
|
||||
let port_handler = page.body.find("port.onmessage=message=>").unwrap();
|
||||
let bootstrap_replaced = page.body.find("bootstrap=policy.bootstrap;").unwrap();
|
||||
let diagnostic_rebound = page
|
||||
.body
|
||||
.find("clientDiagnostics.setBootstrap(bootstrap)")
|
||||
.unwrap();
|
||||
let limits_replaced = page
|
||||
.body
|
||||
.find("batchLimit=policy.limits.carrier_batch_bytes")
|
||||
.unwrap();
|
||||
|
||||
assert!(native_status < status_report);
|
||||
assert!(hello_report < create_started);
|
||||
assert!(boundary_initialized < boundary_activated);
|
||||
assert!(boundary_activated < port_handler);
|
||||
assert!(bootstrap_replaced < diagnostic_rebound);
|
||||
assert!(diagnostic_rebound < limits_replaced);
|
||||
assert!(page.body.contains("clientDiagnostics.helloTimeout()"));
|
||||
assert!(!page.body.contains("port.postMessage({t:'status',state,"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user