6b55f8f65d6c

remove chirp pairing: audio exchange, ggwave loader and assets, the popup orchestrator, the Capacitor pairing server, the chirp flow UI and its phase types, and the audioCapture permission (v4.0.28)

time
2026-07-24 22:28 UTC · 3 months ago
machine
koh2
agent
claude-opus-5
session
pairing code transport
parent
ad1ba5b1a3e7
23 files manifest.firefox.json+1 −2manifest.json+1 −4package.json+1 −1public/ggwave/ggwave.js+0 −29src/background/chirp-sync-static.test.ts+11 −13src/popup/App.tsx+5 −9src/popup/chirp-pair-capacitor.test.ts+0 −108src/popup/chirp-pair-capacitor.ts+0 −213src/popup/chirp-pair.adopt-source.test.ts+0 −265src/popup/chirp-pair.test.ts+0 −218src/popup/chirp-pair.ts+0 −547src/popup/components/ChirpPairingFlow.test.tsx+0 −102src/popup/components/ChirpPairingFlow.tsx+6 −388src/popup/views/ChirpSync.tsx+21 −288src/popup/views/Settings.tsx+1 −1src/shared/chirp-pairing.test.ts+0 −191src/shared/chirp-pairing.ts+0 −291src/shared/chirp-payload.test.ts+0 −106src/shared/chirp-payload.ts+0 −139src/shared/chirp-transport.probe.test.ts+0 −244src/shared/ggwave.test.ts+0 −131src/shared/ggwave.ts+0 −187src/shared/sync-types.ts+0 −26
manifest.firefox.json+1 −2
@@ -1,8 +1,7 @@11 {2-  "_comment_audio_capture": "Firefox does not honor the Chrome 'audioCapture' optional_permission — getUserMedia in WebExtensions is gated solely by the runtime permission prompt. Microphone access for chirp pairing works from the popup under the existing '<all_urls>' host permission; no manifest entry is required or accepted here.",32   "manifest_version": 3,43   "name": "Peach Password Manager",5-  "version": "4.0.27",4+  "version": "4.0.28",65   "description": "Zero-knowledge password manager",76   "browser_specific_settings": {87     "gecko": {

view file

manifest.json+1 −4
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "4.0.27",4+  "version": "4.0.28",55   "description": "Zero-knowledge password manager",66   "action": {77     "default_popup": "src/popup/index.html",@@ -64,9 +64,6 @@6464     "webAuthenticationProxy",6565     "offscreen",6666     "mdns"67-  ],68-  "optional_permissions": [69-    "audioCapture"7067   ],7168   "host_permissions": [7269     "<all_urls>"

view file

package.json+1 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "4.0.27",3+  "version": "4.0.28",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

public/ggwave/ggwave.js+0 −29
@@ -1,29 +0,0 @@1-/* PEACH-PATCH LV-7 (2026-06-09): createNamedFunction rewritten to avoid2-   the dynamic Function constructor — CSP 'wasm-unsafe-eval' forbids it.3-   Re-apply this patch if ggwave.js is ever re-vendored. See src/drafts/AUDIT-FIXES. */4-5-// PATCHED for peach-browser: appended `globalThis.ggwave_factory = ggwave_factory` at EOF so the loader in src/shared/ggwave.ts can pick up the factory when this file is evaluated as an ES module via dynamic import().6-var ggwave_factory = (() => {7-  var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;8-  if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename;9-  return (10-function(ggwave_factory) {11-  ggwave_factory = ggwave_factory || {};12-13-var Module=typeof ggwave_factory!=="undefined"?ggwave_factory:{};var objAssign=Object.assign;var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides=objAssign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=typeof window==="object";var ENVIRONMENT_IS_WORKER=typeof importScripts==="function";var ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;function logExceptionOnExit(e){if(e instanceof ExitStatus)return;let toLog=e;err("exiting due to exception: "+toLog)}var fs;var nodePath;var requireNodeFS;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}requireNodeFS=(()=>{if(!nodePath){fs=require("fs");nodePath=require("path")}});read_=function shell_read(filename,binary){var ret=tryParseAsDataURI(filename);if(ret){return binary?ret:ret.toString()}requireNodeFS();filename=nodePath["normalize"](filename);return fs.readFileSync(filename,binary?null:"utf8")};readBinary=(filename=>{var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}return ret});readAsync=((filename,onload,onerror)=>{var ret=tryParseAsDataURI(filename);if(ret){onload(ret)}requireNodeFS();filename=nodePath["normalize"](filename);fs.readFile(filename,function(err,data){if(err)onerror(err);else onload(data.buffer)})});if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",function(reason){throw reason});quit_=((status,toThrow)=>{if(keepRuntimeAlive()){process["exitCode"]=status;throw toThrow}logExceptionOnExit(toThrow);process["exit"](status)});Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!=="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=(url=>{try{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText}catch(err){var data=tryParseAsDataURI(url);if(data){return intArrayToString(data)}throw err}});if(ENVIRONMENT_IS_WORKER){readBinary=(url=>{try{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}catch(err){var data=tryParseAsDataURI(url);if(data){return data}throw err}})}readAsync=((url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=(()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}var data=tryParseAsDataURI(url);if(data){onload(data.buffer);return}onerror()});xhr.onerror=onerror;xhr.send(null)})}setWindowTitle=(title=>document.title=title)}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);objAssign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var tempRet0=0;var setTempRet0=value=>{tempRet0=value};var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!=="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}var UTF8Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heap,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heap[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heap.subarray&&UTF8Decoder){return UTF8Decoder.decode(heap.subarray(idx,endPtr))}else{var str="";while(idx<endPtr){var u0=heap[idx++];if(!(u0&128)){str+=String.fromCharCode(u0);continue}var u1=heap[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}var u2=heap[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u0=(u0&7)<<18|u1<<12|u2<<6|heap[idx++]&63}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i<str.length;++i){var u=str.charCodeAt(i);if(u>=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i<str.length;++i){var u=str.charCodeAt(i);if(u>=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}var UTF16Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf-16le"):undefined;function UTF16ToString(ptr,maxBytesToRead){var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder){return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr))}else{var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str}}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite<str.length*2?maxBytesToWrite/2:str.length;for(var i=0;i<numCharsToWrite;++i){var codeUnit=str.charCodeAt(i);HEAP16[outPtr>>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i<str.length;++i){var codeUnit=str.charCodeAt(i);if(codeUnit>=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i<str.length;++i){var codeUnit=str.charCodeAt(i);if(codeUnit>=55296&&codeUnit<=57343)++i;len+=4}return len}function alignUp(x,multiple){if(x%multiple>0){x+=multiple-x%multiple}return x}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeKeepaliveCounter=0;function keepRuntimeAlive(){return noExitRuntime||runtimeKeepaliveCounter>0}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["preloadedImages"]={};Module["preloadedAudios"]={};function abort(what){{if(Module["onAbort"]){Module["onAbort"](what)}}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -s ASSERTIONS=1 for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}function isFileURI(filename){return filename.startsWith("file://")}var wasmBinaryFile;wasmBinaryFile="data:application/octet-stream;base64,AGFzbQEAAAAB5QEhYAN/f38AYAF/AX9gAX8AYAN/f38Bf2AFf39/f38AYAR/f39/AGAGf39/f39/AGAAAGACf38AYAV/f39/fwF/YAJ/fwF/YAN/fn8BfmAEf39/fwF/YAF8AX1gAAF/YAF9AX1gAXwBfGACfH8BfGAGf3x/f39/AX9gCn9/f39/f39/f38AYAN/f3wAYAd/f39/f39/AGACfn8Bf2ADfHx/AXxgAnx8AXxgBX99f39/AX9gBn9/f39/fwF/YAJ9fwF/YAJ8fwF/YAd/f39/f39/AX9gBH9/fn4AYAN/f30AYAJ/fwF9Ap0BGgFhAWEAAAFhAWIAAAFhAWMABgFhAWQAEwFhAWUABAFhAWYAFAFhAWcACgFhAWgABwFhAWkAAAFhAWoAAgFhAWsAAgFhAWwADAFhAW0ACAFhAW4AAAFhAW8ABQFhAXAAAgFhAXEABgFhAXIACQFhAXMAFQFhAXQAAgFhAXUAAQFhAXYAAQFhAXcAAwFhAXgACAFhAXkABAFhAXoACANragMDAAEABAMCAgENDQ4BAAEWDwIQFxgZBA8RBwYFABADAAwBBQcFAwoAAAEaBREBAgAbHAkHAAEDAAkdCgAAHgQFBQUDCAEHChIBCwELAwEfIAECAQIFAAICCAgABwcCAAMECQYGBgQECQ4EBQFwATQ0BQcBAYACgIACBgkBfwFBsO3AAgsHIQgBQQIAAUIAPgFDAQABRAAhAUUAJwFGAGsBRwBOAUgAfAk9AQBBAQszgwFyVU9qaVVPY25hbV5sggF7enl4d3Z1dHNxaGdmZWRiXyxJSUAsQCxdfYABXCx+gQFbLH9ZWgrtuQRqgQQBA38gAkGABE8EQCAAIAEgAhAWGiAADwsgACACaiEDAkAgACABc0EDcUUEQAJAIABBA3FFBEAgACECDAELIAJFBEAgACECDAELIAAhAgNAIAIgAS0AADoAACABQQFqIQEgAkEBaiICQQNxRQ0BIAIgA0kNAAsLAkAgA0F8cSIEQcAASQ0AIAIgBEFAaiIFSw0AA0AgAiABKAIANgIAIAIgASgCBDYCBCACIAEoAgg2AgggAiABKAIMNgIMIAIgASgCEDYCECACIAEoAhQ2AhQgAiABKAIYNgIYIAIgASgCHDYCHCACIAEoAiA2AiAgAiABKAIkNgIkIAIgASgCKDYCKCACIAEoAiw2AiwgAiABKAIwNgIwIAIgASgCNDYCNCACIAEoAjg2AjggAiABKAI8NgI8IAFBQGshASACQUBrIgIgBU0NAAsLIAIgBE8NAQNAIAIgASgCADYCACABQQRqIQEgAkEEaiICIARJDQALDAELIANBBEkEQCAAIQIMAQsgACADQQRrIgRLBEAgACECDAELIAAhAgNAIAIgAS0AADoAACACIAEtAAE6AAEgAiABLQACOgACIAIgAS0AAzoAAyABQQRqIQEgAkEEaiICIARNDQALCyACIANJBEADQCACIAEtAAA6AAAgAUEBaiEBIAJBAWoiAiADRw0ACwsgAAvyAgICfwF+AkAgAkUNACAAIAE6AAAgACACaiIDQQFrIAE6AAAgAkEDSQ0AIAAgAToAAiAAIAE6AAEgA0EDayABOgAAIANBAmsgAToAACACQQdJDQAgACABOgADIANBBGsgAToAACACQQlJDQAgAEEAIABrQQNxIgRqIgMgAUH/AXFBgYKECGwiATYCACADIAIgBGtBfHEiBGoiAkEEayABNgIAIARBCUkNACADIAE2AgggAyABNgIEIAJBCGsgATYCACACQQxrIAE2AgAgBEEZSQ0AIAMgATYCGCADIAE2AhQgAyABNgIQIAMgATYCDCACQRBrIAE2AgAgAkEUayABNgIAIAJBGGsgATYCACACQRxrIAE2AgAgBCADQQRxQRhyIgRrIgJBIEkNACABrUKBgICAEH4hBSADIARqIQEDQCABIAU3AxggASAFNwMQIAEgBTcDCCABIAU3AwAgAUEgaiEBIAJBIGsiAkEfSw0ACwsgAAsjAQF/IwBBEGsiAyQAIAMgAjYCDCAAIAEgAhBCIANBEGokAAszAQF/IABBASAAGyEAAkADQCAAECciAQ0BQaTtACgCACIBBEAgAREHAAwBCwsQBwALIAELFwAgAC0AAEEgcUUEQCABIAIgABA5GgsLbwEBfyMAQYACayIFJAACQCACIANMDQAgBEGAwARxDQAgBSABQf8BcSACIANrIgJBgAIgAkGAAkkiARsQGxogAUUEQANAIAAgBUGAAhAeIAJBgAJrIgJB/wFLDQALCyAAIAUgAhAeCyAFQYACaiQAC3QBAX8gAkUEQCAAKAIEIAEoAgRGDwsgACABRgRAQQEPCyABKAIEIgItAAAhAQJAIAAo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LwEYakEAIAAtABYQGxogACgCKCgCACAALwEkakEAIAAtACIQGxoCQCAALQANBEAgACgCNCgCACAALwEwaiAAKAIIIAAtAAFBAWoiA0H/AXEQGhogACADOgAsDAELIAAoAjQoAgAgAC8BMGpBAToAACAAQQI6ADggAEEBOgAsIAAtAAEEQANAIAAoAkAoAgAgAC8BPGpBAToAACAAKAJAKAIAIAAvATxqIAlBGHRBGHUiA0H/AWogAyADQQBIG0HwKmotAAA6AAEgACAALQAsIAAtADhqQQFrIgM6AEQgACgCTCgCACAALwFIakEAIANB/wFxEBsaIAAtACwhAwJAIAAtADgiBUUNAEEAIQRBASEIIANFBEBBACEDDAELA0BBACEGIAhB/wFxBH8DQEEAIQMCQCAAKAI0KAIAIAAvATBqIAZqLQAAIgVFDQAgAC8BPCAAKAJAKAIAIARqai0AACIIRQ0AIAhB8ChqLQAAIAVB8ChqLQAAakHwKmotAAAhAwsgACgCTCgCACAALwFIaiAEIAZqQf8BcWoiBSAFLQAAIANzOgAAIAZBAWoiBiAALQAsIgNJDQALIAAtADghBSADBUEACyEIIARBAWoiBCAFSQ0ACwsgACADIAAtAEQiBCADIARLGyIDOgAsIAAoAjQoAgAgAC8BMGogACgCTCgCACAALwFIaiADQf8BcRAaGiAAIAM6ACwgAC0AASAJQQFqIglBGHRBGHVKDQALCyAAKAIIIAAoAjQoAgAgAC8BMGogAC0ALBAaGiAAQQE6AA0LIAAoAhwoAgAgAC8BGGogASAALQAAIgMQGhogACADOgAUIAAoAigoAgAgAC8BJGogASAALQAAEBoaIAAgAC0AASIBIAAtABRqOgAgIAAtAAAiAwRAA0ACQCAAKAIoKAIAIAAvASRqIAdqLQAAIgRFDQBBASEBIAAtACxBAkkNAANAIAAoAigoAgAgAC8BJGogASAHakH/AXFqIgMgACgCNCgCACAALwEwaiABai0AACIGBH8gBEHwKGotAAAgBkHwKGotAABqQfAqai0AAAVBAAsgAy0AAHM6AAAgAUEBaiIBIAAtACxJDQALIAAtAAAhAwsgB0EBaiIHIANJDQALIAMhByAALQABIQELIAIgACgCKCgCACAALwEkaiAHQf8BcWogAUH/AXEQGhoLzAYBA38jAEHQAGsiBSQAAkAgAUEASARAQQAhAkHA2QAoAgAiAEUNASAFIAE2AgAgAEHWICAFEBwMAQsCQCAALQBNBEACQCAALQBEBH8gACgCSAVBjAELIgYgAU4EQCABIQYMAQtBwNkAKAIAIgdFDQAgBSAGNgJEIAUgATYCQCAHQcMZIAVBQGsQHAsgBEHlAE8EQEEAIQJBwNkAKAIAIgBFDQMgBSAENgIQIABBriEgBUEQahAcDAMLIABBADoAtAQgACgC7ARBACAAKALwBBAbGiAAKAJUQQAgACgCWBAbGiAGQQBMDQEgA0EWTwRAQQAhAkHA2QAoAgAiAEUNAyAFIAM2AjAgAEHCISAFQTBqEBwMAwsgACADQQxsaiIBLQCJBUUEQEEAIQJBwNkAKAIAIgBFDQMgBSADNgIgIABBixsgBUEgahAcDAMLIAAtAEQhAwJ/AkAgAS0AiAVBAkYEQCADBEAgACABKQKABTcC9AQgACABKAKIBTYC/AQMAgtBACECQcDZACgCACIARQ0FQd0bQTsgABAoDAULIAAgASkCgAU3AvQEIAAgASgCiAU2AvwEIAYgA0UNARoLIAAoAkgLIQEgACABNgK8BCAAIAS3RAAAAAAAAFlAo7Y4ArgEIAAoAuwEIAE6AABBACEBIAAoArwEQQBKBEADQEEAIQQgAUEBaiIDIAAoAuwEaiABIAZIBH8gASACai0AAAVBAAs6AAAgAC0AUARAIAAoAuwEIANqIgQgBC0AACABQT9xQbAoai0AAHM6AAALIAMiASAAKAK8BEgNAAsLIABBAToAtAQMAQsgAUUNAEHA2QAoAgAiAUUNAEGKGkHAACABECgLIAAtAExFBEBBASECDAELIABBADsBbCAAQgA3AoABIABCADcCiAEgACgCsAFBACAAKAK0AUECdBAbGiAAKAK4AUEAIAAoArwBQQJ0EBsaQQEhAgJAIAAoAoQEIgFBAEwNACAAKAKIBCIDQQBMDQAgACgCgARBACABIANsQQJ0EBsaCyAAKALUAUEAIAAoAtgBEBsaIAAoApwEIgFBAEwNACAAKAKgBCIDQQBMDQAgACgCmARBACABIANsEBsaCyAFQdAAaiQAIAIL60UDDn8IfQR8IwBBMGsiEiQAAkAgAEECdEGw2QBqKAIAIgdFBEBBfyEBQcDZACgCACICRQ0BIBIgADYCACACQdYcIBIQHAwBCyAHIAIgASADIAQQU0UEQEF/IQFBwNkAKAIAIgJFDQEgEiAANgIgIAJBmRwgEkEgahAcDAELAkACQAJAIAYOAgIAAQsgBy0AtAQEfyAHKAIMIQAgBy0ATgR/IAdBuAdqIAcqAgggByoCBJUgACAHKAKIB0EAEDBBAWoFIAALIAcsAPoEIAcsAPwEIAcsAPsEIgEgBygCPAJ/QQIgBygCvAQiAEEESA0AGkEEIABBD0kNABogAEEFbkEBdAsgAGpqakEBayABbWxsIAcoAjhBAXRqbAVBAAsgBygCGGwhAQwCCyAHLQC0BEUEQEEAIQEMAgsgBygCDCEAIActAE4EfyAHQbgHaiAHKgIIIAcqAgSVIAAgBygCiAdBABAwQQFqBSAACyAHLAD6BCAHLAD8BCAHLAD7BCIBIAcoAjwCf0ECIAcoArwEIgBBBEgNABpBBCAAQQ9JDQAaIABBBW5BAXQLIABqampBAWsgAW1sbCAHKAI4QQF0amwhAQwBCyMAQeABayIBJAACQCAHLQBNRQRAQQAhAkHA2QAoAgAiA0UNAUGKGkHAACADECgMAQsgBy0ATgRAIAdCADcD6AcgB0IANwPgByAHQgA3A9gHIAcoAsgHQQAgBygCzAdBAnQQGxogBygCwAdBACAHKALEB0ECdBAbGiAHKALQB0EAIAcoAtQHQQJ0EBsaCwJ/QQIgBygCvAQiAkEESA0AGkEEIAJBD0kNABogAkEFbkEBdAshCSAHLAD7BCIDIAcoAjwiBiACIAlqampBAWsgA20hFCAHLAD8BCEIIAcsAPoEIQwgBy0AREUEQCAHKAJcIQIgAUEANgLcASABQQA6ANQBIAFBADYC0AEgAUEAOgDIASABQQA2AsQBIAFBADoAvAEgAUEANgK4ASABQQA6ALABIAFBADYCrAEgAUEAOgCkASABQQA2AqABIAFBADoAmAEgAUEANgKUASABQQA6AIwBIAFBADYCiAEgAUEAOgCAASABQQA2AnwgAUEAOgB0IAFBADYCcCABQQA6AGggAUEANgJkIAFBADoAXCABQQA2AlggAUEAOgBQIAFBADYCTCABQQA6AEQgAUFAa0EANgIAIAFBADoAOCABQQA2AjQgAUEAOgAsIAFBADYCKCABQQA6ACAgAUEANgIcIAFBADoAFCABIAZBAWsiAzoAASABQQE6AAAgAUEAOwEMIAJFBEBBASEKIANB/wFxIgIgAkEcbGpBBGoQJyECCyABQQA7ARggASAGOgAWIAEgBkH/AXEiCzsBJCABIAY6ACIgASADQQF0IgM6AC4gAUEDOgA5IAEgAzoAOiABIAtBAXQiEzsBMCABIBMgA0H+AXEiBGoiEzsBPCABIAo6AAwgASACNgIEIAEgAjYCCCABQQA7ARQgAUGAAjsBICABQYAEOwEsIAEgAUEQaiICNgIcIAEgAjYCKCABIAI2AjQgASAEIBNqIgo7AUggASADOgBGIAEgBjoAUiABIAM6AF4gASADOgBqIAFBCDoAdSABIAQgCmoiBjsBVCABIAYgC2oiBjsBYCABIAQgBmoiBjsBbCABIAQgBmoiBjsBeCABQYAIOwFEIAEgAjYCQCABQQA6ADggASACNgJMIAFBgAo7AVAgASACNgJYIAFBgAw7AVwgASACNgJkIAFBgA47AWggASACNgJwIAEgAzoAdiABIAQgBmoiBjsBhAEgASADOgCCASABIAM6AI4BIAEgAzoAmgEgAUEMOgClASABIAM6AKYBIAEgBCAGaiIGOwGQASABIAQgBmoiBjsBnAEgASAEIAZqIgY7AagBIAFBADoAdCABIAI2AnwgAUGAEjsBgAEgASACNgKIASABQYAUOwGMASABIAI2ApQBIAFBgBY7AZgBIAEgAjYCoAEgASAEIAZqIgY7AbQBIAFBDToAsQEgASACNgKsASABQQA6AKQBIAEgAzoAsgEgASAEIAZqIgY7AcABIAFBDjoAvQEgASACNgK4ASABQQA6ALABIAEgAzoAvgEgASAEIAZqIgY7AcwBIAFBDzoAyQEgASACNgLEASABQQA6ALwBIAEgAzoAygEgASAEIAZqOwHYASABQRA6ANUBIAEgAjYC0AEgAUEAOgDIASABIAM6ANYBIAEgAjYC3AEgAUEAOgDUASABIAcoAuwEIgIgBygCVCACIAEtAAAQGiABLQAAahBSAkAgAS0ADEUNACABKAIEIgJFDQAgAhAhCyAHLQC8BCECCyAHKAJkIQRBACEGIAFBADYC3AEgAUEAOgDUASABQQA2AtABIAFBADoAyAEgAUEANgLEASABQQA6ALwBIAFBADYCuAEgAUEAOgCwASABQQA2AqwBIAFBADoApAEgAUEANgKgASABQQA6AJgBIAFBADYClAEgAUEAOgCMASABQQA2AogBIAFBADoAgAEgAUEANgJ8IAFBADoAdCABQQA2AnAgAUEAOgBoIAFBADYCZCABQQA6AFwgAUEANgJYIAFBADoAUCABQQA2AkwgAUEAOgBEIAFBQGtBADYCACABQQA6ADggAUEANgI0IAFBADoALCABQQA2AiggAUEAOgAgIAFBADYCHCABQQA6ABQgASAJOgABIAEgAjoAACABQQA7AQwgBEUEQEEBIQ0gCUH+AXEiA0EcbCADaiACQf8BcUEDbGpBAWoQJyEECyAIIBRsIAxsIRQgAUEAOwEYIAEgAiAJaiILOgAWIAEgCzoAIiABIAlBAXQiAzoALiABQQM6ADkgASADOgA6IAEgC0H/AXEiCTsBJCABIAlBAXQiAjsBMCABIAIgA0H+AXEiCmoiCDsBPCABIA06AAwgASAENgIEIAEgBDYCCCABQQA7ARQgAUGAAjsBICABQYAEOwEsIAEgAUEQaiICNgIcIAEgAjYCKCABIAI2AjQgASAIIApqIgQ7AUggASADOgBGIAEgCzoAUiABIAM6AF4gASADOgBqIAFBCDoAdSABIAQgCmoiBDsBVCABIAQgCWoiBDsBYCABIAQgCmoiBDsBbCABIAQgCmoiBDsBeCABQYAIOwFEIAEgAjYCQCABQQA6ADggASACNgJMIAFBgAo7AVAgASACNgJYIAFBgAw7AVwgASACNgJkIAFBgA47AWggASACNgJwIAEgAzoAdiABIAQgCmoiBDsBhAEgASADOgCCASABIAM6AI4BIAEgAzoAmgEgAUEMOgClASABIAM6AKYBIAEgBCAKaiIEOwGQASABIAQgCmoiBDsBnAEgASAEIApqIgQ7AagBIAFBADoAdCABIAI2AnwgAUGAEjsBgAEgASACNgKIASABQYAUOwGMASABIAI2ApQBIAFBgBY7AZgBIAEgAjYCoAEgASAEIApqIgQ7AbQBIAFBDToAsQEgASACNgKsASABQQA6AKQBIAEgAzoAsgEgASAEIApqIgQ7AcABIAFBDjoAvQEgASACNgK4ASABQQA6ALABIAEgAzoAvgEgASAEIApqIgQ7AcwBIAFBDzoAyQEgASACNgLEASABQQA6ALwBIAEgAzoAygEgASAEIApqOwHYASABQRA6ANUBIAEgAjYC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ECACDAELIABBADYCHCAAQgA3AxAgACAAKAIAQSByNgIAQQAgB0ECRg0AGiACIAEoAgRrCyEAIARBIGokACAACwkAIAAoAjwQFQsPACABIAAoAgBqIAI4AgALDQAgASAAKAIAaioCAAudAQEDfwJ/IAAoAgQiAiEAAkAgAkEDcQRAA0AgAC0AAEUNAiAAQQFqIgBBA3ENAAsLA0AgACIBQQRqIQAgASgCACIDQX9zIANBgYKECGtxQYCBgoR4cUUNAAsgASACayADQf8BcUUNARoDQCABLQABIQMgAUEBaiIAIQEgAw0ACwsgACACawtBAWoiABAnIgEEfyABIAIgABAaBUEACwttAQN/IwBBEGsiAiQAAkACQCAAQQNLDQAgAEECdEGw2QBqIgMoAgAiAUUNACABKALwByIABEAgABAhCyABECEgA0EANgIADAELQcDZACgCACIBRQ0AIAIgADYCACABQcodIAIQHAsgAkEQaiQAC8ETAwl/An0BfiMAQTBrIgQkAAJAAkBBsNkAKAIARQRAQbDZACEHDAELQbTZACgCAEUEQEG02QAhB0EBIQgMAQtBuNkAKAIARQRAQbjZACEHQQIhCAwBC0G82QAoAgBFBEBBvNkAIQdBAyEIDAELQX8hCEHA2QAoAgAiAEUNASAEQQQ2AgAgAEGRIiAEEBwMAQtB+AcQHSEBIAQgACgCADYCCCAEIAAqAgQ4AgwgBCAAKgIIOAIQIAQgACoCDDgCFCAEIAAoAhA2AhggBCAAKgIUOAIcIAQgACgCGDYCICAEIAAoAhw2AiQgBCAAKAIgNgIoIAFBfzYCSCABQQA6AEQgAUGAgID8ezYCQCABQn83AzggAUKAgID8ezcDMCABQoCAgPx7NwMoIAFCgICAgICAgMC/fzcDICABQv////8PNwMYIAFCgICA/Hs3AxAgAUKAgID8ezcDCCABQoCAgPyLgIDAv383AwAgAUIANwCnASABQgA3AqABIAFCADcCmAEgAUIANwKQASABQgA3AogBIAFCADcCgAEgAUIANwJ4IAFCADcCcCABQQA2AtgBIAFCADcC0AEgAUIANwLIASABQgA3AsABIAFCADcCuAEgAUIANwKwASABQQA2AkwgAUEAOgBQIAFCADcCVCABQgA3AlwgAUIANwJkIAFBADsBbCABQfQDakEAQcEAEBsa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OgC0ASADQQA2ArABIANBADoAqAEgA0EANgKkASADQQA6AJwBIANBADYCmAEgA0EAOgCQASADQQA2AowBIANBADoAhAEgA0EANgKAASADQQA6AHggA0EANgJ0IANBADoAbCADQQA2AmggA0EAOgBgIANBADYCXCADQQA6AFQgAyAAQQFrIgo6AEEgA0EBOgBAIANBADsBTEEAIQ4gBkUEQEEBIQ4gCkH/AXEiBiAGQRxsakEEahAnIQYLIAMgDjoATCADIAY2AkQgA0EAOwFYIAMgBjYCSCADIAA6AFYgAyAJNgJcIANBADsBVCADIAA6AGIgAyAJNgJoIANBgAI7AWAgA0EDOgB5IAMgCTYCdCADQYAEOwFsIAMgCkEBdCIGOgBuIAMgBjoAeiADIABB/wFxIg47AWQgAyAOQQF0Ig07AXAgAyANIAZB/gFxIgpqIg07AXwgA0GACDsBhAEgAyAJNgKAASADQQA6AHggAyAGOgCGASADIAk2AowBIANBgAo7AZABIAMgADoAkgEgAyAJNgKYASADQYAMOwGcASADIAY6AJ4BIAMgCTYCpAEgA0GADjsBqAEgAyAGOgCqASADIAk2ArABIANBCDoAtQEgAyAKIA1qIgA7AYgBIAMgACAKaiIAOwGUASADIAAgDmoiADsBoAEgAyAAIApqIgA7AawBIAMgACAKaiIAOwG4ASADQQA6ALQBIAMgBjoAtgEgAyAJNgK8ASADIAY6AMIBIANBgBI7AcABIAMgCTYCyAEgAyAGOgDOASADQYAUOwHMASADIAk2AtQBIAMgBjoA2gEgA0GAFjsB2AEgA0EMOgDlASADIAk2AuABIAMgBjoA5gEgAyAAIApqIgA7AcQBIAMgACAKaiIAOwHQASADIAAgCmoiADsB3AEgAyAAIApqIgA7AegBIANBDToA8QEgAyAJNgLsASADQQA6AOQBIAMgACAKaiIAOwH0ASADIAY6APIBIANBDjoA/QEgAyAJNgL4ASADQQA6APABIAMgACAKaiIAOwGAAiADIAY6AP4BIANBDzoAiQIgAyAJNgKEAiADQQA6APwBIAMgACAKaiIAOwGMAiADIAY6AIoCIAMgACAKajsBmAIgA0EQOgCVAiADIAk2ApACIANBADoAiAIgAyAGOgCWAiADIAk2ApwCIANBADoAlAJBFyEGAkAgA0FA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Y3RvcgB1bnNpZ25lZCBjaGFyAHNhbXBsZUZvcm1hdElucABzYW1wbGVSYXRlSW5wAF9fY3hhX2d1YXJkX2FjcXVpcmUgZGV0ZWN0ZWQgcmVjdXJzaXZlIGluaXRpYWxpemF0aW9uAG5hbgBib29sAHR4VG9nZ2xlUHJvdG9jb2wAcnhUb2dnbGVQcm90b2NvbABlbXNjcmlwdGVuOjp2YWwAW1VdIE5vcm1hbABbTVRdIE5vcm1hbABbRFRdIE5vcm1hbABwYXlsb2FkTGVuZ3RoAGRpc2FibGVMb2cAZW5hYmxlTG9nAHVuc2lnbmVkIGxvbmcAc3RkOjp3c3RyaW5nAGJhc2ljX3N0cmluZwBzdGQ6OnN0cmluZwBzdGQ6OnUxNnN0cmluZwBzdGQ6OnUzMnN0cmluZwBpbmYAc2FtcGxlUmF0ZQBzYW1wbGVzUGVyRnJhbWUAZG91YmxlAGZyZWUAZW5jb2RlAGRlY29kZQBvcGVyYXRpbmdNb2RlAHNvdW5kTWFya2VyVGhyZXNob2xkAHZvaWQAUHJvdG9jb2xJZABHR1dBVkVfT1BFUkFUSU5HX01PREVfVFgAR0dXQVZFX09QRVJBVElOR19NT0RFX1JYX0FORF9UWABHR1dBVkVfT1BFUkFUSU5HX01PREVfUlgAR0dXQVZFX1BST1RPQ09MX0RUX0ZBU1RFU1QAR0dXQVZFX1BST1RPQ09MX0FVRElCTEVfRkFTVEVTVABHR1dBVkVfUFJPVE9DT0xfVUxUUkFTT1VORF9GQVNURVNUAEdHV0FWRV9QUk9UT0NPTF9EVF9GQVNUAEdHV0FWRV9QUk9UT0NPTF9BVURJQkxFX0ZBU1QAR0dXQVZFX1BST1RPQ09MX1VMVFJBU09VTkRfRkFTVABHR1dBVkVfT1BFUkFUSU5HX01PREVfVVNFX0RTUwBHR1dBVkVfT1BFUkFUSU5HX01PREVfVFhfT05MWV9UT05FUwBOQU4AR0dXQVZFX1BST1RPQ09MX0RUX05PUk1BTABHR1dBVkVfUFJPVE9DT0xfQVVESUJMRV9OT1JNQUwAR0dXQVZFX1BST1RPQ09MX1VMVFJBU09VTkRfTk9STUFMAElORgBHR1dBVkVfU0FNUExFX0ZPUk1BVF9VTkRFRklORUQAZW1zY3JpcHRlbjo6bWVtb3J5X3ZpZXc8c2hvcnQ+AGVtc2NyaXB0ZW46Om1lbW9yeV92aWV3PHVuc2lnbmVkIHNob3J0PgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzxpbnQ+AGVtc2NyaXB0ZW46Om1lbW9yeV92aWV3PHVuc2lnbmVkIGludD4AZW1zY3JpcHRlbjo6bWVtb3J5X3ZpZXc8ZmxvYXQ+AGVtc2NyaXB0ZW46Om1lbW9yeV92aWV3PHVpbnQ4X3Q+AGVtc2NyaXB0ZW46Om1lbW9yeV92aWV3PGludDhfdD4AZW1zY3JpcHRlbjo6bWVtb3J5X3ZpZXc8dWludDE2X3Q+AGVtc2NyaXB0ZW46Om1lbW9yeV92aWV3PGludDE2X3Q+AGVtc2NyaXB0ZW46Om1lbW9yeV92aWV3PHVpbnQzMl90PgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzxpbnQzMl90PgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzxjaGFyPgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzx1bnNpZ25lZCBjaGFyPgBzdGQ6OmJhc2ljX3N0cmluZzx1bnNpZ25lZCBjaGFyPgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzxzaWduZWQgY2hhcj4AZW1zY3JpcHRlbjo6bWVtb3J5X3ZpZXc8bG9uZz4AZW1zY3JpcHRlbjo6bWVtb3J5X3ZpZXc8dW5zaWduZWQgbG9uZz4AZW1zY3JpcHRlbjo6bWVtb3J5X3ZpZXc8ZG91YmxlPgBHR1dBVkVfUFJPVE9DT0xfQ1VTVE9NXzkAR0dXQVZFX1BST1RPQ09MX0NVU1RPTV84AEdHV0FWRV9TQU1QTEVfRk9STUFUX1U4AEdHV0FWRV9TQU1QTEVfRk9STUFUX0k4AEdHV0FWRV9QUk9UT0NPTF9DVVNUT01fNwBHR1dBVkVfUFJPVE9DT0xfQ1VTVE9NXzYAR0dXQVZFX1NBTVBMRV9GT1JNQVRfVTE2AEdHV0FWRV9TQU1QTEVfRk9STUFUX0kxNgBHR1dBVkVfUFJPVE9DT0xfQ1VTVE9NXzUAR0dXQVZFX1BST1RPQ09MX0NVU1RPTV80AEdHV0FWRV9QUk9UT0NPTF9DVVNUT01fMwBHR1dBVkVfUFJPVE9DT0xfQ1VTVE9NXzIAR0dXQVZFX1NBTVBMRV9GT1JNQVRfRjMyAEdHV0FWRV9QUk9UT0NPTF9DVVNUT01fMQBHR1dBVkVfUFJPVE9DT0xfQ1VTVE9NXzAALgAobnVsbCkARXJyb3I6IGNhcHR1cmUgc2FtcGxlIHJhdGUgKCVnIEh6KSBtdXN0IGJlID49ICVnIEh6CgBFcnJvcjogY2FwdHVyZSBzYW1wbGUgcmF0ZSAoJWcgSHopIG11c3QgYmUgPD0gJWcgSHoKAEVycm9yOiBmYWlsZWQgdG8gY29tcHV0ZSB0aGUgc2l6ZSBvZiB0aGUgcmVxdWlyZWQgbWVtb3J5CgBUcnVuY2F0aW5nIGRhdGEgZnJvbSAlZCB0byAlZCBieXRlcwoAQ2Fubm90IGRlY29kZSB3aGlsZSB0cmFuc21pdHRpbmcKAFR4IGlzIGRpc2FibGVkIC0gY2Fubm90IHRyYW5zbWl0IGRhdGEgd2l0aCB0aGlzIEdHV2F2ZSBpbnN0YW5jZQoAUnggaXMgZGlzYWJsZWQgLSBjYW5ub3QgcmVjZWl2ZSBkYXRhIHdpdGggdGhpcyBHR1dhdmUgaW5zdGFuY2UKAFByb3RvY29sICVkIGlzIG5vdCBlbmFibGVkIC0gbWFrZSBzdXJlIHRvIGVuYWJsZSBpdCBiZWZvcmUgY3JlYXRpbmcgdGhlIGluc3RhbmNlCgBNb25vLXRvbmUgcHJvdG9jb2xzIHdpdGggdmFyaWFibGUgbGVuZ3RoIGFyZSBub3Qgc3VwcG9ydGVkCgBGYWlsZWQgdG8gaW5pdGlhbGl6ZSBUeCB0cmFuc21pc3Npb24gZm9yIEdHV2F2ZSBpbnN0YW5jZSAlZAoASW52YWxpZCBHR1dhdmUgaW5zdGFuY2UgJWQKAEZhaWxlZCB0byBlbmNvZGUgZGF0YSAtIEdHV2F2ZSBpbnN0YW5jZSAlZAoARmFpbGVkIHRvIGRlY29kZSBkYXRhIC0gR0dXYXZlIGluc3RhbmNlICVkCgBGYWlsZWQgdG8gZnJlZSBHR1dhdmUgaW5zdGFuY2UgLSBpbnZhbGlkIEdHV2F2ZSBpbnN0YW5jZSBpZCAlZAoAbiA9ICVkLCBuQWN0dWFsID0gJWQKAFJlY2VpdmVkIGVuZCBtYXJrZXIuIEZyYW1lcyBsZWZ0ID0gJWQsIHJlY29yZGVkID0gJWQKAEVycm9yOiBmYWlsZWQgdG8gYWxsb2NhdGUgdGhlIHJlcXVpcmVkIG1lbW9yeTogJWQKAEludmFsaWQgcGF5bG9hZCBsZW5ndGg6ICVkLCBtYXg6ICVkCgBJbnZhbGlkIHNhbXBsZXMgcGVyIGZyYW1lOiAlZCwgbWF4OiAlZAoASW52YWxpZCBvciB1bnN1cHBvcnRlZCBwbGF5YmFjayBzYW1wbGUgZm9ybWF0OiAlZAoASW52YWxpZCBvciB1bnN1cHBvcnRlZCBjYXB0dXJlIHNhbXBsZSBmb3JtYXQ6ICVkCgBJbnZhbGlkIHNhbXBsZSBmb3JtYXQ6ICVkCgBOZWdhdGl2ZSBkYXRhIHNpemU6ICVkCgBFcnJvcjogZmFpbGVkIHRvIGFsbG9jYXRlIG1lbW9yeSAtIGhlYXBTaXplMDogJWQsIGhlYXBTaXplOiAlZAoASW52YWxpZCB2b2x1bWU6ICVkCgBJbnZhbGlkIHByb3RvY29sIElEOiAlZAoAUmVjZWl2aW5nIHNvdW5kIGRhdGEgLi4uCgBBbmFseXppbmcgY2FwdHVyZWQgZGF0YSAuLgoARmFpbGVkIHRvIGNyZWF0ZSBHR1dhdmUgaW5zdGFuY2UgLSByZWFjaGVkIG1heGltdW0gbnVtYmVyIG9mIGluc3RhbmNlcyAoJWQpCgBGYWlsdXJlIGR1cmluZyBjYXB0dXJlIC0gcHJvdmlkZWQgYnl0ZXMgKCVkKSBhcmUgbm90IG11bHRpcGxlIG9mIHNhbXBsZSBzaXplICglZCkKAERlY29kZWQgbGVuZ3RoID0gJWQsIHByb3RvY29sID0gJyVzJyAoJWQpCgBFcnJvcjogdG90YWwgbGVuZ3RoICVkIChwYXlsb2FkICVkICsgRUNDICVkIGJ5dGVzKSBpcyB0b28gbGFyZ2UgKCA+ICVkKQoARmFpbGVkIHRvIGNhcHR1cmUgc291bmQgZGF0YS4gUGxlYXNlIHRyeSBhZ2FpbiAobGVuZ3RoID0gJWQpCgBSZWNlaXZlZCBzb3VuZCBkYXRhIHN1Y2Nlc3NmdWxseTogJyVzJwoAMTlnZ3dhdmVfU2FtcGxlRm9ybWF0AAAAAPwoAACLEgAAMTdnZ3dhdmVfUHJvdG9jb2xJZAD8KAAArBIAADE3Z2d3YXZlX1BhcmFtZXRlcnMASCkAAMgSAABpAHZpAGlpaQB2aWlpAGZpaQB2aWlmAADcEgAAaWkAAJgoAADcEgAARCgAAJgoAAB2aWkATjEwZW1zY3JpcHRlbjExbWVtb3J5X3ZpZXdJY0VFAABIKQAAGBMAAGgTAACYKAAA4BMAAMASAACYKAAATjEwZW1zY3JpcHRlbjN2YWxFAABIKQAAVBMAAE5TdDNfXzIxMmJhc2ljX3N0cmluZ0ljTlNfMTFjaGFyX3RyYWl0c0ljRUVOU185YWxsb2NhdG9ySWNFRUVFAE5TdDNfXzIyMV9fYmFzaWNfc3RyaW5nX2NvbW1vbklMYjFFRUUAAAAASCkAAK8TAADMKQAAcBMAAAAAAAABAAAA2BMAAAAAAABpaWlpaWkAAGgTAACYKAAA4BMAAGlpaWkAAAAARCgAAEQoAADAEgAAmCgAQbAoC6cilp+0rxuR3sVFdeguDzJKX7RWlct/alRqSPILe837k208d17DM0fA8XEyMyc1aEcfTqwjQl8AN6RQbUgkkXyhTgAAARkCMhrGA98z7htox0sEZOAONI3vgRzBafjICExxBYplL+EkDyE1k47a8BKCRR21wn1qJ/m5yZoJeE3kcqYGv4tiZt0w/eKYJbMQkSKINtCUzo+W273x0hNcgzhGQB5CtqPDSH5uazooVPqFuj3KXpufChV5K07U5axz86dXB3DA94yAYw1nSt7tMcX+GOOlmXcmuLR8EUSS2SMgiS43P9FblbzPzZCHl7Lc/L5h8lbTqxQqXZ6EPDlTR21Boh8tQ9i3e6R2xBdJ7H8Mb/ZsoTtSKZ1VqvtghrG7zD5ay1lfsJypoFEL9RbrenUs10+u1enm563odNb06qhQWK8BAgQIECBAgB06dOjNhxMmTJgtWrR16smPAwYMGDBgwJ0nTpwlSpQ1atS1d+7BnyNGjAUKFChQoF26adK5b96hX75hwpkvXrxlyokPHjx48P3n07tr1rF//uHfo1u2ceLZr0OGESJEiA0aNGjQvWfOgR8+fPjtx5M7duzFlzNmzIUXLly4bdqpT54hQoQVKlSoTZopUqRVqkmSOXLk1bdz5tG/Y8aRP3785deze/bx/+Pbq0uWMWLElTdu3KVXrkGCGTJkyI0HDhw4cODdp1OmUaJZsnny+e/DmytWrEWKCRIkSJA9evT19/P768uLCxYsWLB9+unPgxs2bNitR44BAgQIECBAgB06dOjNhxMmTJgtWrR16smPAwYMGDBgwJ0nTpwlSpQ1atS1d+7BnyNGjAUKFChQoF26adK5b96hX75hwpkvXrxlyokPHjx48P3n07tr1rF//uHfo1u2ceLZr0OGESJEiA0aNGjQvWfOgR8+fPjtx5M7duzFlzNmzIUXLly4bdqpT54hQoQVKlSoTZopUqRVqkmSOXLk1bdz5tG/Y8aRP3785deze/bx/+Pbq0uWMWLElTdu3KVXrkGCGTJkyI0HDhw4cODdp1OmUaJZsnny+e/DmytWrEWKCRIkSJA9evT19/P768uLCxYsWLB9+unPgxs2bNitR44BAgAAAAABAAAAAQAAAAIAAAACAAAABAAAAE5TdDNfXzIxMmJhc2ljX3N0cmluZ0loTlNfMTFjaGFyX3RyYWl0c0loRUVOU185YWxsb2NhdG9ySWhFRUVFAADMKQAAiBcAAAAAAAABAAAA2BMAAAAAAABOU3QzX18yMTJiYXNpY19zdHJpbmdJd05TXzExY2hhcl90cmFpdHNJd0VFTlNfOWFsbG9jYXRvckl3RUVFRQAAzCkAAOAXAAAAAAAAAQAAANgTAAAAAAAATlN0M19fMjEyYmFzaWNfc3RyaW5nSURzTlNfMTFjaGFyX3RyYWl0c0lEc0VFTlNfOWFsbG9jYXRvcklEc0VFRUUAAADMKQAAOBgAAAAAAAABAAAA2BMAAAAAAABOU3QzX18yMTJiYXNpY19zdHJpbmdJRGlOU18xMWNoYXJfdHJhaXRzSURpRUVOU185YWxsb2NhdG9ySURpRUVFRQAAAMwpAACUGAAAAAAAAAEAAADYEwAAAAAAAE4xMGVtc2NyaXB0ZW4xMW1lbW9yeV92aWV3SWFFRQAASCkAAPAYAABOMTBlbXNjcmlwdGVuMTFtZW1vcnlfdmlld0loRUUAAEgpAAAYGQAATjEwZW1zY3JpcHRlbjExbWVtb3J5X3ZpZXdJc0VFAABIKQAAQBkAAE4xMGVtc2NyaXB0ZW4xMW1lbW9yeV92aWV3SXRFRQAASCkAAGgZAABOMTBlbXNjcmlwdGVuMTFtZW1vcnlfdmlld0lpRUUAAEgpAACQGQAATjEwZW1zY3JpcHRlbjExbWVtb3J5X3ZpZXdJakVFAABIKQAAuBkAAE4xMGVtc2NyaXB0ZW4xMW1l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";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}var binary=tryParseAsDataURI(file);if(binary){return binary}if(readBinary){return readBinary(file)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch==="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary(wasmBinaryFile)})}else{if(readAsync){return new Promise(function(resolve,reject){readAsync(wasmBinaryFile,function(response){resolve(new Uint8Array(response))},reject)})}}}return Promise.resolve().then(function(){return getBinary(wasmBinaryFile)})}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["A"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["C"];addOnInit(Module["asm"]["B"]);removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(function(instance){return instance}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming==="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&typeof fetch==="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiationResult,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiationResult)})})}else{return instantiateArrayBuffer(receiveInstantiationResult)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync().catch(readyPromiseReject);return{}}function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback(Module);continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){getWasmTableEntry(func)()}else{getWasmTableEntry(func)(callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}var wasmTableMirror=[];function getWasmTableEntry(funcPtr){var func=wasmTableMirror[funcPtr];if(!func){if(funcPtr>=wasmTableMirror.length)wasmTableMirror.length=funcPtr+1;wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func}var structRegistrations={};function runDestructors(destructors){while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}}function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAPU32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var char_0=48;var char_9=57;function makeLegalFunctionName(name){if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return"_"+name}else{return name}}function createNamedFunction(name,body){name=makeLegalFunctionName(name);var f=function(){"use strict";return body.apply(this,arguments)};try{Object.defineProperty(f,"name",{value:name})}catch(e){}return f}function extendError(baseErrorType,errorName){var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return this.name+": "+this.message}};return errorClass}var InternalError=undefined;function throwInternalError(message){throw new InternalError(message)}function whenDependentTypesAreResolved(myTypes,dependentTypes,getTypeConverters){myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i<myTypes.length;++i){registerType(myTypes[i],myTypeConverters[i])}}var typeConverters=new Array(dependentTypes.length);var unregisteredTypes=[];var registered=0;dependentTypes.forEach(function(dt,i){if(registeredTypes.hasOwnProperty(dt)){typeConverters[i]=registeredTypes[dt]}else{unregisteredTypes.push(dt);if(!awaitingDependencies.hasOwnProperty(dt)){awaitingDependencies[dt]=[]}awaitingDependencies[dt].push(function(){typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}});if(0===unregisteredTypes.length){onComplete(typeConverters)}}function __embind_finalize_value_object(structType){var reg=structRegistrations[structType];delete structRegistrations[structType];var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;var fieldRecords=reg.fields;var fieldTypes=fieldRecords.map(function(field){return field.getterReturnType}).concat(fieldRecords.map(function(field){return field.setterArgumentType}));whenDependentTypesAreResolved([structType],fieldTypes,function(fieldTypes){var fields={};fieldRecords.forEach(function(field,i){var fieldName=field.fieldName;var getterReturnType=fieldTypes[i];var getter=field.getter;var getterContext=field.getterContext;var setterArgumentType=fieldTypes[i+fieldRecords.length];var setter=field.setter;var setterContext=field.setterContext;fields[fieldName]={read:function(ptr){return getterReturnType["fromWireType"](getter(getterContext,ptr))},write:function(ptr,o){var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,o));runDestructors(destructors)}}});return[{name:reg.name,"fromWireType":function(ptr){var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},"toWireType":function(destructors,o){for(var fieldName in fields){if(!(fieldName in o)){throw new TypeError('Missing field:  "'+fieldName+'"')}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:rawDestructor}]})}function __embind_register_bigint(primitiveType,name,size,minRange,maxRange){}function getShiftFromSize(size){switch(size){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError("Unknown type size: "+size)}}function embind_init_charCodes(){var codes=new Array(256);for(var i=0;i<256;++i){codes[i]=String.fromCharCode(i)}embind_charCodes=codes}var embind_charCodes=undefined;function readLatin1String(ptr){var ret="";var c=ptr;while(HEAPU8[c]){ret+=embind_charCodes[HEAPU8[c++]]}return ret}var BindingError=undefined;function throwBindingError(message){throw new BindingError(message)}function registerType(rawType,registeredInstance,options={}){if(!("argPackAdvance"in registeredInstance)){throw new TypeError("registerType registeredInstance requires argPackAdvance")}var name=registeredInstance.name;if(!rawType){throwBindingError('type "'+name+'" must have a positive integer typeid pointer')}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError("Cannot register type '"+name+"' twice")}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(function(cb){cb()})}}function __embind_register_bool(rawType,name,size,trueValue,falseValue){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(wt){return!!wt},"toWireType":function(destructors,o){return o?trueValue:falseValue},"argPackAdvance":8,"readValueFromPointer":function(pointer){var heap;if(size===1){heap=HEAP8}else if(size===2){heap=HEAP16}else if(size===4){heap=HEAP32}else{throw new TypeError("Unknown boolean type size: "+name)}return this["fromWireType"](heap[pointer>>shift])},destructorFunction:null})}function __embind_register_constant(name,type,value){name=readLatin1String(name);whenDependentTypesAreResolved([],[type],function(type){type=type[0];Module[name]=type["fromWireType"](value);return[]})}var emval_free_list=[];var emval_handle_array=[{},{value:undefined},{value:null},{value:true},{value:false}];function __emval_decref(handle){if(handle>4&&0===--emval_handle_array[handle].refcount){emval_handle_array[handle]=undefined;emval_free_list.push(handle)}}function count_emval_handles(){var count=0;for(var i=5;i<emval_handle_array.length;++i){if(emval_handle_array[i]!==undefined){++count}}return count}function get_first_emval(){for(var i=5;i<emval_handle_array.length;++i){if(emval_handle_array[i]!==undefined){return emval_handle_array[i]}}return null}function init_emval(){Module["count_emval_handles"]=count_emval_handles;Module["get_first_emval"]=get_first_emval}var Emval={toValue:function(handle){if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handle_array[handle].value},toHandle:function(value){switch(value){case undefined:{return 1}case null:{return 2}case true:{return 3}case false:{return 4}default:{var handle=emval_free_list.length?emval_free_list.pop():emval_handle_array.length;emval_handle_array[handle]={refcount:1,value:value};return handle}}}};function __embind_register_emval(rawType,name){name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(handle){var rv=Emval.toValue(handle);__emval_decref(handle);return rv},"toWireType":function(destructors,value){return Emval.toHandle(value)},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:null})}function ensureOverloadTable(proto,methodName,humanName){if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(){if(!proto[methodName].overloadTable.hasOwnProperty(arguments.length)){throwBindingError("Function '"+humanName+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+proto[methodName].overloadTable+")!")}return proto[methodName].overloadTable[arguments.length].apply(this,arguments)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}}function exposePublicSymbol(name,value,numArguments){if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError("Cannot register public name '"+name+"' twice")}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError("Cannot register multiple overloads of a function with the same number of arguments ("+numArguments+")!")}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}}function enumReadValueFromPointer(name,shift,signed){switch(shift){case 0:return function(pointer){var heap=signed?HEAP8:HEAPU8;return this["fromWireType"](heap[pointer])};case 1:return function(pointer){var heap=signed?HEAP16:HEAPU16;return this["fromWireType"](heap[pointer>>1])};case 2:return function(pointer){var heap=signed?HEAP32:HEAPU32;return this["fromWireType"](heap[pointer>>2])};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_enum(rawType,name,size,isSigned){var shift=getShiftFromSize(size);name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name:name,constructor:ctor,"fromWireType":function(c){return this.constructor.values[c]},"toWireType":function(destructors,c){return c.value},"argPackAdvance":8,"readValueFromPointer":enumReadValueFromPointer(name,shift,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)}function getTypeName(type){var ptr=___getTypeName(type);var rv=readLatin1String(ptr);_free(ptr);return rv}function requireRegisteredType(rawType,humanName){var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl}function __embind_register_enum_value(rawEnumType,name,enumValue){var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(enumType.name+"_"+name,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value}function floatReadValueFromPointer(name,shift){switch(shift){case 2:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 3:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError("Unknown float type: "+name)}}function __embind_register_float(rawType,name,size){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(value){return value},"toWireType":function(destructors,value){return value},"argPackAdvance":8,"readValueFromPointer":floatReadValueFromPointer(name,shift),destructorFunction:null})}function new_(constructor,argumentList){if(!(constructor instanceof Function)){throw new TypeError("new_ called with constructor type "+typeof constructor+" which is not a function")}var dummy=createNamedFunction(constructor.name||"unknownFunctionName",function(){});dummy.prototype=constructor.prototype;var obj=new dummy;var r=constructor.apply(obj,argumentList);return r instanceof Object?r:obj}function craftInvokerFunction(humanName,argTypes,classType,cppInvokerFunc,cppTargetFunc){var argCount=argTypes.length;if(argCount<2){throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!")}var isClassMethodFunc=argTypes[1]!==null&&classType!==null;var needsDestructorStack=false;for(var i=1;i<argTypes.length;++i){if(argTypes[i]!==null&&argTypes[i].destructorFunction===undefined){needsDestructorStack=true;break}}var returns=argTypes[0].name!=="void";var argsList="";var argsListWired="";for(var i=0;i<argCount-2;++i){argsList+=(i!==0?", ":"")+"arg"+i;argsListWired+=(i!==0?", ":"")+"arg"+i+"Wired"}var invokerFnBody="return function "+makeLegalFunctionName(humanName)+"("+argsList+") {\n"+"if (arguments.length !== "+(argCount-2)+") {\n"+"throwBindingError('function "+humanName+" called with ' + arguments.length + ' arguments, expected "+(argCount-2)+" args!');\n"+"}\n";if(needsDestructorStack){invokerFnBody+="var destructors = [];\n"}var dtorStack=needsDestructorStack?"destructors":"null";var args1=["throwBindingError","invoker","fn","runDestructors","retType","classParam"];var args2=[throwBindingError,cppInvokerFunc,cppTargetFunc,runDestructors,argTypes[0],argTypes[1]];if(isClassMethodFunc){invokerFnBody+="var thisWired = classParam.toWireType("+dtorStack+", this);\n"}for(var i=0;i<argCount-2;++i){invokerFnBody+="var arg"+i+"Wired = argType"+i+".toWireType("+dtorStack+", arg"+i+"); // "+argTypes[i+2].name+"\n";args1.push("argType"+i);args2.push(argTypes[i+2])}if(isClassMethodFunc){argsListWired="thisWired"+(argsListWired.length>0?", ":"")+argsListWired}invokerFnBody+=(returns?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i<argTypes.length;++i){var paramName=i===1?"thisWired":"arg"+(i-2)+"Wired";if(argTypes[i].destructorFunction!==null){invokerFnBody+=paramName+"_dtor("+paramName+"); // "+argTypes[i].name+"\n";args1.push(paramName+"_dtor");args2.push(argTypes[i].destructorFunction)}}}if(returns){invokerFnBody+="var ret = retType.fromWireType(rv);\n"+"return ret;\n"}else{}invokerFnBody+="}\n";args1.push(invokerFnBody);var invokerFunction=new_(Function,args1).apply(null,args2);return invokerFunction}function heap32VectorToArray(count,firstElement){var array=[];for(var i=0;i<count;i++){array.push(HEAP32[(firstElement>>2)+i])}return array}function replacePublicSymbol(name,value,numArguments){if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}}function dynCallLegacy(sig,ptr,args){var f=Module["dynCall_"+sig];return args&&args.length?f.apply(null,[ptr].concat(args)):f.call(null,ptr)}function dynCall(sig,ptr,args){if(sig.includes("j")){return dynCallLegacy(sig,ptr,args)}return getWasmTableEntry(ptr).apply(null,args)}function getDynCaller(sig,ptr){var argCache=[];return function(){argCache.length=arguments.length;for(var i=0;i<arguments.length;i++){argCache[i]=arguments[i]}return dynCall(sig,ptr,argCache)}}function embind__requireFunction(signature,rawFunction){signature=readLatin1String(signature);function makeDynCaller(){if(signature.includes("j")){return getDynCaller(signature,rawFunction)}return getWasmTableEntry(rawFunction)}var fp=makeDynCaller();if(typeof fp!=="function"){throwBindingError("unknown function pointer with signature "+signature+": "+rawFunction)}return fp}var UnboundTypeError=undefined;function throwUnboundTypeError(message,types){var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(message+": "+unboundTypes.map(getTypeName).join([", "]))}function __embind_register_function(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn){var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=readLatin1String(name);rawInvoker=embind__requireFunction(signature,rawInvoker);exposePublicSymbol(name,function(){throwUnboundTypeError("Cannot call "+name+" due to unbound types",argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn),argCount-1);return[]})}function integerReadValueFromPointer(name,shift,signed){switch(shift){case 0:return signed?function readS8FromPointer(pointer){return HEAP8[pointer]}:function readU8FromPointer(pointer){return HEAPU8[pointer]};case 1:return signed?function readS16FromPointer(pointer){return HEAP16[pointer>>1]}:function readU16FromPointer(pointer){return HEAPU16[pointer>>1]};case 2:return signed?function readS32FromPointer(pointer){return HEAP32[pointer>>2]}:function readU32FromPointer(pointer){return HEAPU32[pointer>>2]};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var shift=getShiftFromSize(size);var fromWireType=value=>value;if(minRange===0){var bitshift=32-8*size;fromWireType=(value=>value<<bitshift>>>bitshift)}var isUnsignedType=name.includes("unsigned");var checkAssertions=(value,toTypeName)=>{};var toWireType;if(isUnsignedType){toWireType=function(destructors,value){checkAssertions(value,this.name);return value>>>0}}else{toWireType=function(destructors,value){checkAssertions(value,this.name);return value}}registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":toWireType,"argPackAdvance":8,"readValueFromPointer":integerReadValueFromPointer(name,shift,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){handle=handle>>2;var heap=HEAPU32;var size=heap[handle];var data=heap[handle+1];return new TA(buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":8,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})}function __embind_register_std_string(rawType,name){name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var str;if(stdStringIsUTF8){var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i<length;++i){a[i]=String.fromCharCode(HEAPU8[value+4+i])}str=a.join("")}_free(value);return str},"toWireType":function(destructors,value){if(value instanceof ArrayBuffer){value=new Uint8Array(value)}var getLength;var valueIsOfTypeString=typeof value==="string";if(!(valueIsOfTypeString||value instanceof Uint8Array||value instanceof Uint8ClampedArray||value instanceof Int8Array)){throwBindingError("Cannot pass non-string to std::string")}if(stdStringIsUTF8&&valueIsOfTypeString){getLength=(()=>lengthBytesUTF8(value))}else{getLength=(()=>value.length)}var length=getLength();var ptr=_malloc(4+length+1);HEAPU32[ptr>>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr+4,length+1)}else{if(valueIsOfTypeString){for(var i=0;i<length;++i){var charCode=value.charCodeAt(i);if(charCode>255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+4+i]=charCode}}else{for(var i=0;i<length;++i){HEAPU8[ptr+4+i]=value[i]}}}if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_std_wstring(rawType,charSize,name){name=readLatin1String(name);var decodeString,encodeString,getHeap,lengthBytesUTF,shift;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16;getHeap=(()=>HEAPU16);shift=1}else if(charSize===4){decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32;getHeap=(()=>HEAPU32);shift=2}registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":function(destructors,value){if(!(typeof value==="string")){throwBindingError("Cannot pass non-string to C++ string type "+name)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_value_object(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}}function __embind_register_value_object_field(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_void(rawType,name){name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":function(){return undefined},"toWireType":function(destructors,o){return undefined}})}function __emval_incref(handle){if(handle>4){emval_handle_array[handle].refcount+=1}}function __emval_take_value(type,argv){type=requireRegisteredType(type,"_emval_take_value");var v=type["readValueFromPointer"](argv);return Emval.toHandle(v)}function _abort(){abort("")}function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;var maxHeapSize=2147483648;if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var SYSCALLS={mappings:{},buffers:[null,[],[]],printChar:function(stream,curr){var buffer=SYSCALLS.buffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer,0));buffer.length=0}else{buffer.push(curr)}},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},get64:function(low,high){return low}};function _fd_close(fd){return 0}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){}function _fd_write(fd,iov,iovcnt,pnum){var num=0;for(var i=0;i<iovcnt;i++){var ptr=HEAP32[iov>>2];var len=HEAP32[iov+4>>2];iov+=8;for(var j=0;j<len;j++){SYSCALLS.printChar(fd,HEAPU8[ptr+j])}num+=len}HEAP32[pnum>>2]=num;return 0}function _setTempRet0(val){setTempRet0(val)}InternalError=Module["InternalError"]=extendError(Error,"InternalError");embind_init_charCodes();BindingError=Module["BindingError"]=extendError(Error,"BindingError");init_emval();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");var ASSERTIONS=false;function intArrayToString(array){var ret=[];for(var i=0;i<array.length;i++){var chr=array[i];if(chr>255){if(ASSERTIONS){assert(false,"Character code "+chr+" ("+String.fromCharCode(chr)+")  at offset "+i+" not in 0x00-0xFF.")}chr&=255}ret.push(String.fromCharCode(chr))}return ret.join("")}var decodeBase64=typeof atob==="function"?atob:function(input){var keyStr="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";var output="";var chr1,chr2,chr3;var enc1,enc2,enc3,enc4;var i=0;input=input.replace(/[^A-Za-z0-9\+\/\=]/g,"");do{enc1=keyStr.indexOf(input.charAt(i++));enc2=keyStr.indexOf(input.charAt(i++));enc3=keyStr.indexOf(input.charAt(i++));enc4=keyStr.indexOf(input.charAt(i++));chr1=enc1<<2|enc2>>4;chr2=(enc2&15)<<4|enc3>>2;chr3=(enc3&3)<<6|enc4;output=output+String.fromCharCode(chr1);if(enc3!==64){output=output+String.fromCharCode(chr2)}if(enc4!==64){output=output+String.fromCharCode(chr3)}}while(i<input.length);return output};function intArrayFromBase64(s){if(typeof ENVIRONMENT_IS_NODE==="boolean"&&ENVIRONMENT_IS_NODE){var buf=Buffer.from(s,"base64");return new Uint8Array(buf["buffer"],buf["byteOffset"],buf["byteLength"])}try{var decoded=decodeBase64(s);var bytes=new Uint8Array(decoded.length);for(var i=0;i<decoded.length;++i){bytes[i]=decoded.charCodeAt(i)}return bytes}catch(_){throw new Error("Converting base64 string to bytes failed.")}}function tryParseAsDataURI(filename){if(!isDataURI(filename)){return}return intArrayFromBase64(filename.slice(dataURIPrefix.length))}var asmLibraryArg={"p":__embind_finalize_value_object,"s":__embind_register_bigint,"y":__embind_register_bool,"f":__embind_register_constant,"x":__embind_register_emval,"o":__embind_register_enum,"a":__embind_register_enum_value,"n":__embind_register_float,"c":__embind_register_function,"e":__embind_register_integer,"b":__embind_register_memory_view,"m":__embind_register_std_string,"i":__embind_register_std_wstring,"q":__embind_register_value_object,"d":__embind_register_value_object_field,"z":__embind_register_void,"j":__emval_decref,"k":__emval_incref,"g":__emval_take_value,"h":_abort,"w":_emscripten_memcpy_big,"u":_emscripten_resize_heap,"v":_fd_close,"r":_fd_seek,"l":_fd_write,"t":_setTempRet0};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["B"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["D"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["E"]).apply(null,arguments)};var ___getTypeName=Module["___getTypeName"]=function(){return(___getTypeName=Module["___getTypeName"]=Module["asm"]["F"]).apply(null,arguments)};var ___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=function(){return(___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=Module["asm"]["G"]).apply(null,arguments)};var dynCall_jiji=Module["dynCall_jiji"]=function(){return(dynCall_jiji=Module["dynCall_jiji"]=Module["asm"]["H"]).apply(null,arguments)};var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run();14-15-16-  return ggwave_factory.ready17-}18-);19-})();20-if (typeof exports === 'object' && typeof module === 'object')21-  module.exports = ggwave_factory;22-else if (typeof define === 'function' && define['amd'])23-  define([], function() { return ggwave_factory; });24-else if (typeof exports === 'object')25-  exports["ggwave_factory"] = ggwave_factory;26-27-// peach-browser patch: expose factory on globalThis for ES-module dynamic import().28-if (typeof globalThis !== 'undefined') globalThis.ggwave_factory = ggwave_factory;29-

view file at the parent

src/background/chirp-sync-static.test.ts+11 −13
@@ -64,23 +64,21 @@6464     expect(entrySrc).toContain("productionSyncRunner");6565   });6666 67-  it("sync-trigger filters to trustSource:'direct' — vouched devices excluded", () => {67+  it("sync-trigger syncs both direct and user-confirmed vouched hosts", () => {6868     const triggerSrc = src("./sync-trigger.ts");69-    expect(triggerSrc).toContain('"direct"');70-    expect(triggerSrc).toMatch(/trustSource.*direct|direct.*trustSource/);69+    expect(triggerSrc).toMatch(/trustSource === "direct"/);70+    // Mesh introduction is pointless if a device the user confirmed out of the71+    // MV-7 pending queue is then never dialled.72+    expect(triggerSrc).toMatch(/trustSource === "vouched"/);7173   });7274 73-  it("App.tsx wires startMDNSListener with onCleanup registered before the import resolves", () => {75+  it("the browser never listens — no pairing server or audio-chirp module survives", () => {7476     const appSrc = src("../popup/App.tsx");75-    expect(appSrc).toContain("startMDNSListener");76-    expect(appSrc).toContain("onCleanup");77-    // onCleanup must appear BEFORE the .then() call that starts the listener,78-    // i.e. stopMDNS must be captured into the closure before dynamic import.79-    const cleanupIdx = appSrc.indexOf("onCleanup(() => stopMDNS");80-    const thenIdx = appSrc.indexOf("startMDNSListener");81-    expect(cleanupIdx).toBeGreaterThan(-1);82-    expect(thenIdx).toBeGreaterThan(-1);83-    expect(cleanupIdx).toBeLessThan(thenIdx);77+    expect(appSrc).not.toContain("startMDNSListener");78+    expect(appSrc).not.toContain("openPairingServer");79+    expect(appSrc).not.toContain("chirp-pair-capacitor");80+    // Adoption dials out like everything else now.81+    expect(appSrc).toContain("dialPairingChannel");8482   });8583 8684   it("vault_id uses the full 64-char BLAKE3 hex digest", () => {

view file

src/popup/App.tsx+5 −9
@@ -199,7 +199,7 @@199199   const [settingsInitialSection, setSettingsInitialSection] = createSignal<string | null>(null);200200   // When the sync card's "Chirp"/"Code" buttons open the sync view, deep-link201201   // straight into that pairing path instead of the rest screen.202-  const [syncPairIntent, setSyncPairIntent] = createSignal<"chirp" | "desktop" | null>(null);202+  const [syncPairIntent, setSyncPairIntent] = createSignal<"desktop" | null>(null);203203   const [settingsActiveCard, setSettingsActiveCard] = createSignal<string>("protection");204204   const [upgradeHighlight, setUpgradeHighlight] = createSignal<ProFeature | null>(null);205205   const [sortOrder, setSortOrder] = createSignal<SortOrder>("url-first");@@ -581,11 +581,10 @@581581         }582582       });583583 584-      // mDNS peer-discovery trigger. Register onCleanup synchronously so585-      // SolidJS captures it within the reactive owner — onCleanup inside a586-      // .then() callback may miss the capture window and leak the listener.587-      let stopMDNS: () => void = () => undefined;588-      onCleanup(() => stopMDNS());584+      // The mDNS peer-discovery trigger went with the Capacitor pairing585+      // plugin. Reconnection is address-cache first, subnet probe second586+      // (chirp-sync-runner.ts); relocating a peer that changed networks is an587+      // Android-side discovery concern now.589588       if (isNativePlatform()) {590589         // Android runs the background/sync dispatcher in-process with the591590         // popup, so the production runner has to be registered from the@@ -597,9 +596,6 @@597596           })598597         );599598       }600-      void import("./chirp-pair-capacitor").then(({ startMDNSListener }) => {601-        stopMDNS = startMDNSListener();602-      });603599     }604600 605601     // Background auto-lock broadcasts VAULT_LOCKED so the popup drops stale

view file

src/popup/chirp-pair-capacitor.test.ts+0 −108
@@ -1,108 +0,0 @@1-import { beforeEach, afterEach, describe, expect, it, vi } from "vitest";2-import { startMDNSListener } from "./chirp-pair-capacitor";3-4-interface RegisteredListener {5-  event: string;6-  handler: (data: unknown) => void;7-}8-9-function installMockCapacitor(opts: {10-  withSyncPlugin?: boolean;11-  withPeerDiscoveredEvent?: boolean;12-  appActive?: boolean;13-} = {}): { listeners: RegisteredListener[]; removeCount: { v: number } } {14-  const listeners: RegisteredListener[] = [];15-  const removeCount = { v: 0 };16-  const syncPlugin = opts.withSyncPlugin === false ? undefined : {17-    startPairingServer: vi.fn(),18-    stopPairingServer: vi.fn(),19-    sendFrame: vi.fn(),20-    addListener: opts.withPeerDiscoveredEvent === false21-      ? undefined22-      : vi.fn((event: string, handler: (data: unknown) => void) => {23-          listeners.push({ event, handler });24-          return { remove: () => { removeCount.v++; } };25-        }),26-  };27-  const appPlugin = {28-    getState: vi.fn(async () => ({ isActive: opts.appActive ?? true })),29-  };30-  (globalThis as { Capacitor?: unknown }).Capacitor = {31-    isNativePlatform: () => true,32-    Plugins: { SyncPlugin: syncPlugin, App: appPlugin },33-  };34-  return { listeners, removeCount };35-}36-37-describe("startMDNSListener", () => {38-  let sentMessages: Array<{ type: string; payload?: unknown }>;39-40-  beforeEach(() => {41-    sentMessages = [];42-    (globalThis as { chrome?: unknown }).chrome = {43-      runtime: {44-        sendMessage: vi.fn(async (msg: { type: string; payload?: unknown }) => {45-          sentMessages.push(msg);46-        }),47-      },48-    };49-  });50-51-  afterEach(() => {52-    delete (globalThis as { Capacitor?: unknown }).Capacitor;53-    delete (globalThis as { chrome?: unknown }).chrome;54-  });55-56-  it("is a no-op when the SyncPlugin is unavailable", () => {57-    installMockCapacitor({ withSyncPlugin: false });58-    const stop = startMDNSListener();59-    expect(typeof stop).toBe("function");60-    stop();61-  });62-63-  it("registers a peerDiscovered listener when the plugin exposes addListener", () => {64-    const { listeners } = installMockCapacitor();65-    startMDNSListener();66-    expect(listeners.length).toBe(1);67-    expect(listeners[0].event).toBe("peerDiscovered");68-  });69-70-  it("fires TRIGGER_SYNC with reason 'mdns' when peerDiscovered arrives in the foreground", async () => {71-    const { listeners } = installMockCapacitor({ appActive: true });72-    startMDNSListener();73-    listeners[0].handler({ deviceId: "pixel-7", ip: "192.168.1.42", port: 8765 });74-    // sendMessage runs inside the handler's `await`; let microtasks flush.75-    await new Promise((r) => setTimeout(r, 0));76-    expect(sentMessages).toEqual([77-      {78-        type: "TRIGGER_SYNC",79-        payload: { reason: "mdns", deviceId: "pixel-7", discoveredIp: "192.168.1.42", discoveredPort: 8765 },80-      },81-    ]);82-  });83-84-  it("does NOT fire TRIGGER_SYNC when the app is backgrounded", async () => {85-    const { listeners } = installMockCapacitor({ appActive: false });86-    startMDNSListener();87-    listeners[0].handler({ deviceId: "pixel-7", ip: "192.168.1.42", port: 8765 });88-    await new Promise((r) => setTimeout(r, 0));89-    expect(sentMessages).toEqual([]);90-  });91-92-  it("ignores malformed events (missing deviceId or ip)", async () => {93-    const { listeners } = installMockCapacitor({ appActive: true });94-    startMDNSListener();95-    listeners[0].handler({ ip: "192.168.1.42" });96-    listeners[0].handler({ deviceId: "pixel-7" });97-    listeners[0].handler(null);98-    await new Promise((r) => setTimeout(r, 0));99-    expect(sentMessages).toEqual([]);100-  });101-102-  it("returns a teardown that removes the native listener", () => {103-    const { removeCount } = installMockCapacitor();104-    const stop = startMDNSListener();105-    stop();106-    expect(removeCount.v).toBe(1);107-  });108-});

view file at the parent

src/popup/chirp-pair-capacitor.ts+0 −213
@@ -1,213 +0,0 @@1-// Capacitor adapter for the initiator-role pairing server, plus the2-// mDNS peer-discovery listener that fires the sync trigger when a known3-// paired device appears on the local network.4-//5-// The native SyncPlugin on Android (and eventually iOS) owns the6-// WebSocket server on CHIRP_PAIRING_PORT. JS does not have raw socket7-// listeners. This adapter wraps the plugin's start/stop calls and its8-// event stream into the PairingTransport shape the orchestrator expects.9-//10-// Protocol with native:11-//   - startPairingServer({port}) opens the server and returns immediately12-//   - The plugin emits 'peach:pair-connected' when the first peer connects:13-//       { connectionId: string, peerIp: string }14-//   - The plugin emits 'peach:pair-frame' for each inbound binary frame:15-//       { connectionId: string, frameB64: string }16-//   - The plugin emits 'peach:pair-closed' when the connection drops:17-//       { connectionId: string }18-//   - sendFrame({connectionId, frameB64}) sends a binary frame out19-//   - stopPairingServer() closes the server and any open connection20-//21-// The 90 s accept timeout matches the chirp window. If no peer connects22-// in that window the adapter rejects with a `pair_timeout` error so the23-// orchestrator surfaces a `handshake_failed` phase.24-25-import type { PairingTransport } from "./chirp-pair";26-import { CHIRP_PAIRING_PORT } from "./chirp-pair";27-28-const ACCEPT_TIMEOUT_MS = 90_000;29-30-interface SyncPluginShape {31-  startPairingServer(opts: { port: number }): Promise<void>;32-  stopPairingServer(): Promise<void>;33-  sendFrame(opts: { connectionId: string; frameB64: string }): Promise<void>;34-  addListener(event: string, handler: (data: unknown) => void): { remove: () => void };35-}36-37-function getSyncPlugin(): SyncPluginShape | null {38-  const cap = (globalThis as { Capacitor?: { Plugins?: Record<string, unknown> } }).Capacitor;39-  const plugin = cap?.Plugins?.SyncPlugin as SyncPluginShape | undefined;40-  return plugin ?? null;41-}42-43-export async function openCapacitorPairingServer(): Promise<{44-  transport: PairingTransport;45-  peerIp: string;46-}> {47-  const plugin = getSyncPlugin();48-  if (!plugin) throw new Error("SyncPlugin unavailable — not running on Capacitor");49-50-  await plugin.startPairingServer({ port: CHIRP_PAIRING_PORT });51-52-  let connectionId: string | null = null;53-  const inbox: Uint8Array[] = [];54-  const waiters: Array<(b: Uint8Array | null) => void> = [];55-  let closed = false;56-57-  const onConnected = plugin.addListener("peach:pair-connected", (data) => {58-    const d = data as { connectionId: string; peerIp: string };59-    if (connectionId !== null) return; // ignore extra connections — first wins60-    connectionId = d.connectionId;61-    acceptResolve?.({ connectionId: d.connectionId, peerIp: d.peerIp });62-  });63-64-  const onFrame = plugin.addListener("peach:pair-frame", (data) => {65-    const d = data as { connectionId: string; frameB64: string };66-    if (d.connectionId !== connectionId) return;67-    const bytes = base64ToBytes(d.frameB64);68-    const w = waiters.shift();69-    if (w) w(bytes); else inbox.push(bytes);70-  });71-72-  const onClosed = plugin.addListener("peach:pair-closed", (data) => {73-    const d = data as { connectionId: string };74-    if (d.connectionId !== connectionId) return;75-    closed = true;76-    while (waiters.length > 0) waiters.shift()!(null);77-  });78-79-  let acceptResolve: ((v: { connectionId: string; peerIp: string }) => void) | null = null;80-  let acceptReject: ((e: Error) => void) | null = null;81-  const acceptPromise = new Promise<{ connectionId: string; peerIp: string }>((res, rej) => {82-    acceptResolve = res;83-    acceptReject = rej;84-  });85-  const timer = setTimeout(() => acceptReject?.(new Error("pair_timeout")), ACCEPT_TIMEOUT_MS);86-87-  let accepted: { connectionId: string; peerIp: string };88-  try {89-    accepted = await acceptPromise;90-  } catch (err) {91-    clearTimeout(timer);92-    onConnected.remove(); onFrame.remove(); onClosed.remove();93-    await plugin.stopPairingServer().catch(() => undefined);94-    throw err;95-  }96-  clearTimeout(timer);97-98-  const transport: PairingTransport = {99-    async send(msg: Uint8Array) {100-      await plugin.sendFrame({101-        connectionId: accepted.connectionId,102-        frameB64: bytesToBase64(msg),103-      });104-    },105-    async receive(): Promise<Uint8Array> {106-      if (closed) throw new Error("pair_transport_closed");107-      const v = inbox.shift();108-      if (v) return v;109-      return new Promise<Uint8Array>((resolve, reject) => {110-        waiters.push((bytes) => {111-          if (bytes === null) reject(new Error("pair_transport_closed"));112-          else resolve(bytes);113-        });114-      });115-    },116-    async close() {117-      onConnected.remove(); onFrame.remove(); onClosed.remove();118-      await plugin.stopPairingServer().catch(() => undefined);119-    },120-  };121-122-  return { transport, peerIp: accepted.peerIp };123-}124-125-/**126- * Subscribe to mDNS peer-discovery events from the native SyncPlugin.127- * Returns a teardown that should be called from `onCleanup`. Each128- * `peerDiscovered` payload identifies a paired device by deviceId plus the129- * (ip, port) the native side just resolved; we update the device's cached130- * IP and fire a `TRIGGER_SYNC` with reason "mdns".131- *132- * Gracefully no-op if the SyncPlugin doesn't expose the event yet — the133- * Android side may or may not have shipped the emitter; the JS-side wiring134- * stays in place either way so we don't need a coordinated rollout.135- */136-export function startMDNSListener(): () => void {137-  const plugin = getSyncPluginExtended();138-  if (!plugin?.addListener) return () => undefined;139-  let sub: { remove: () => void } | null = null;140-  try {141-    sub = plugin.addListener("peerDiscovered", (data: unknown) => {142-      void handlePeerDiscovered(data);143-    });144-  } catch {145-    return () => undefined;146-  }147-  return () => { try { sub?.remove(); } catch { /* ignore */ } };148-}149-150-async function handlePeerDiscovered(data: unknown): Promise<void> {151-  const event = data as { deviceId?: string; ip?: string; port?: number } | null | undefined;152-  if (!event?.deviceId || typeof event.ip !== "string") return;153-154-  // Foreground guard — when the OS has backgrounded the app, mDNS resolves155-  // are still delivered but firing a sync now would burn battery for no156-  // user-visible benefit. The next foreground entry will trigger sync via157-  // the existing popup-foreground path.158-  const appActive = await isAppActive();159-  if (!appActive) return;160-161-  try {162-    // Pass the resolved address so the background can update lastKnownIp163-    // before attempting the session — saves the runner from a stale-IP164-    // probe on the very next sync round.165-    await chrome.runtime.sendMessage({166-      type: "TRIGGER_SYNC",167-      payload: {168-        reason: "mdns",169-        deviceId: event.deviceId,170-        discoveredIp: event.ip,171-        discoveredPort: typeof event.port === "number" ? event.port : undefined,172-      },173-    });174-  } catch {175-    // Background may not be listening — non-fatal; next vault-change or176-    // foreground entry will pick up the work.177-  }178-}179-180-interface ExtendedSyncPlugin extends SyncPluginShape {181-  addListener(event: "peerDiscovered", handler: (data: unknown) => void): { remove: () => void };182-}183-184-function getSyncPluginExtended(): ExtendedSyncPlugin | null {185-  return getSyncPlugin() as ExtendedSyncPlugin | null;186-}187-188-async function isAppActive(): Promise<boolean> {189-  const cap = (globalThis as { Capacitor?: { Plugins?: Record<string, unknown> } }).Capacitor;190-  const appPlugin = cap?.Plugins?.App as191-    | { getState?: () => Promise<{ isActive: boolean }> }192-    | undefined;193-  if (!appPlugin?.getState) return true;194-  try {195-    const state = await appPlugin.getState();196-    return state.isActive !== false;197-  } catch {198-    return true;199-  }200-}201-202-function base64ToBytes(b64: string): Uint8Array {203-  const bin = atob(b64);204-  const out = new Uint8Array(bin.length);205-  for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);206-  return out;207-}208-209-function bytesToBase64(bytes: Uint8Array): string {210-  let bin = "";211-  for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);212-  return btoa(bin);213-}

view file at the parent

src/popup/chirp-pair.adopt-source.test.ts+0 −265
@@ -1,265 +0,0 @@1-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";2-import { computeVaultId } from "../shared/sync-identity";3-import { routeSourcePostSASFrame, runChirpAdoptionReceiver } from "./chirp-pair";4-import {5-  memoryTransportPair,6-  runPairingResponderOpenFrame,7-  type LocalIdentity,8-  type PairingTransport,9-} from "../shared/chirp-handshake";10-import { createPairingAttempt } from "../shared/chirp-pairing";11-import { __setGgwaveModuleForTest } from "../shared/ggwave";12-import { encodeChirpPayload } from "../shared/chirp-payload";13-import { CHIRP_PAYLOAD_LENGTH, DEVICE_TYPE_EXTENSION, type ChirpPayload } from "../shared/sync-types";14-15-const localIdentity: LocalIdentity = {16-  deviceId: "source-device",17-  deviceName: "Source",18-  deviceType: "extension",19-  longTermPublicKeyHex: "ab".repeat(32),20-  signingPublicKeyHex: "cd".repeat(32),21-};22-23-function channel(): PairingTransport & { sent: Uint8Array[]; close: ReturnType<typeof vi.fn> } {24-  const sent: Uint8Array[] = [];25-  return {26-    sent,27-    close: vi.fn(async () => {}),28-    async send(msg) { sent.push(msg); },29-    async receive() {30-      return new TextEncoder().encode(JSON.stringify({31-        type: "ADOPT_IDENTITY",32-        device_id: "receiver-device",33-        device_name: "Receiver",34-        device_type: "extension",35-        long_term_public_key_hex: "ef".repeat(32),36-      }));37-    },38-  };39-}40-41-function fakeGgwave() {42-  return {43-    ProtocolId: { GGWAVE_PROTOCOL_ULTRASOUND_FASTEST: 5 },44-    GGWAVE_OPERATING_MODE_RX: 1,45-    GGWAVE_OPERATING_MODE_TX: 2,46-    GGWAVE_OPERATING_MODE_RX_AND_TX: 3,47-    getDefaultParameters() {48-      return {49-        sampleRateInp: 48000,50-        sampleRateOut: 48000,51-        samplesPerFrame: 1024,52-        soundMarkerThreshold: 3,53-        payloadLength: -1,54-        operatingMode: 0,55-      };56-    },57-    init() { return 1; },58-    free() {},59-    encode(_inst: number, data: Uint8Array | string) {60-      const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;61-      const out = new Float32Array(Math.max(bytes.length, 128));62-      for (let i = 0; i < bytes.length; i++) out[i] = bytes[i] / 255;63-      for (let i = bytes.length; i < out.length; i++) out[i] = 99;64-      return out;65-    },66-    decode(_inst: number, samples: Float32Array | Int16Array) {67-      if (samples.length < CHIRP_PAYLOAD_LENGTH) return null;68-      const buf = new Uint8Array(CHIRP_PAYLOAD_LENGTH);69-      for (let i = 0; i < CHIRP_PAYLOAD_LENGTH; i++) {70-        const v = (samples as Float32Array)[i];71-        if (v === 99) return null;72-        buf[i] = Math.round(v * 255);73-      }74-      return buf;75-    },76-  };77-}78-79-function fakeAudio(injectedChirp: ChirpPayload) {80-  const bytes = encodeChirpPayload(injectedChirp);81-  let pending = new Float32Array(128);82-  for (let i = 0; i < bytes.length; i++) pending[i] = bytes[i] / 255;83-  for (let i = bytes.length; i < pending.length; i++) pending[i] = 99;84-  return async () => ({85-    sampleRate: 48000,86-    async play() { /* no-op */ },87-    async pullSamples() {88-      const out = pending;89-      pending = new Float32Array(0);90-      return out;91-    },92-    async close() { /* no-op */ },93-  });94-}95-96-describe("source-side ADOPT_REQUEST routing", () => {97-  beforeEach(() => __setGgwaveModuleForTest(fakeGgwave()));98-  afterEach(() => __setGgwaveModuleForTest(null));99-100-  it("T-A26: routes ADOPT_REQUEST to grantAdoption after SAS confirmation and consent", async () => {101-    const rootKeyHex = "77".repeat(32);102-    const ch = channel();103-    const completePairing = vi.fn();104-    const result = await routeSourcePostSASFrame(105-      {106-        sas: "895245",107-        frame: {108-          type: "ADOPT_REQUEST",109-          device_id: "receiver-ephemeral",110-          device_name: "Receiver",111-          device_type: "extension",112-        },113-        channel: ch,114-      },115-      localIdentity,116-      {117-        confirmSAS: vi.fn(async () => true),118-        completePairing,119-        grantAdoptionConsent: vi.fn(async () => true),120-        fetchAdoptionMaterial: vi.fn(async () => ({121-          rootKeyHex,122-          vaultId: await computeVaultId(Uint8Array.from(Buffer.from(rootKeyHex, "hex"))),123-          license: "PPK-test",124-        })),125-        onProgress: vi.fn(),126-      },127-    );128-129-    const grant = JSON.parse(new TextDecoder().decode(ch.sent[0]));130-    expect(result).toEqual(expect.objectContaining({ deviceId: "receiver-device" }));131-    expect(grant).toMatchObject({132-      type: "ADOPT_GRANT",133-      root_key_hex: rootKeyHex,134-      vault_id: await computeVaultId(Uint8Array.from(Buffer.from(rootKeyHex, "hex"))),135-      license: "PPK-test",136-    });137-    expect(completePairing).toHaveBeenCalledWith(expect.objectContaining({ deviceId: "receiver-device" }));138-  });139-140-  it("T-A27: routes PAIR_IDENTITY to the existing pairing path", async () => {141-    const ch = channel();142-    const completePairing = vi.fn();143-    const result = await routeSourcePostSASFrame(144-      {145-        sas: "895245",146-        frame: {147-          type: "PAIR_IDENTITY",148-          device_id: "phone",149-          device_name: "Phone",150-          device_type: "android",151-          long_term_public_key_hex: "aa".repeat(32),152-        },153-        channel: ch,154-      },155-      localIdentity,156-      {157-        confirmSAS: vi.fn(async () => true),158-        completePairing,159-        onProgress: vi.fn(),160-      },161-    );162-163-    const response = JSON.parse(new TextDecoder().decode(ch.sent[0]));164-    expect(response.type).toBe("PAIR_IDENTITY");165-    expect(result).toEqual(expect.objectContaining({ deviceId: "phone" }));166-    expect(completePairing).toHaveBeenCalledWith(expect.objectContaining({ deviceId: "phone" }));167-  });168-169-  it("T-A28: unknown frame closes the channel gracefully", async () => {170-    const ch = channel();171-    await expect(routeSourcePostSASFrame(172-      { sas: "895245", frame: { type: "WHAT_IS_THIS" }, channel: ch },173-      localIdentity,174-      {175-        confirmSAS: vi.fn(async () => true),176-        completePairing: vi.fn(),177-        onProgress: vi.fn(),178-      },179-    )).rejects.toThrow("unexpected_pairing_frame");180-181-    expect(ch.close).toHaveBeenCalledTimes(1);182-  });183-184-  it("T-A29: source root key bytes are zeroed after grantAdoption regardless of outcome", async () => {185-    const fillSpy = vi.spyOn(Uint8Array.prototype, "fill");186-    await routeSourcePostSASFrame(187-      {188-        sas: "895245",189-        frame: {190-          type: "ADOPT_REQUEST",191-          device_id: "receiver-ephemeral",192-          device_name: "Receiver",193-          device_type: "extension",194-        },195-        channel: channel(),196-      },197-      localIdentity,198-      {199-        confirmSAS: vi.fn(async () => true),200-        completePairing: vi.fn(),201-        grantAdoptionConsent: vi.fn(async () => true),202-        fetchAdoptionMaterial: vi.fn(async () => ({203-          rootKeyHex: "88".repeat(32),204-          vaultId: await computeVaultId(Uint8Array.from(Buffer.from("88".repeat(32), "hex"))),205-        })),206-        onProgress: vi.fn(),207-      },208-    );209-210-    expect(fillSpy).toHaveBeenCalledWith(0);211-  });212-213-  it("T-A33: receiver onboarding orchestrator sends ADOPT_REQUEST and persists the granted source", async () => {214-    const sourceAttempt = createPairingAttempt(DEVICE_TYPE_EXTENSION);215-    const [receiverTransport, sourceTransport] = memoryTransportPair();216-    const rootKeyHex = "99".repeat(32);217-    const vaultId = await computeVaultId(Uint8Array.from(Buffer.from(rootKeyHex, "hex")));218-    const receiverUpsert = vi.fn();219-    const sourceUpsert = vi.fn();220-221-    const source = (async () => {222-      const postNoise = await runPairingResponderOpenFrame(223-        sourceAttempt.ephemeralPrivateKey,224-        sourceAttempt.ephemeralPublicKey,225-        sourceTransport,226-      );227-      return routeSourcePostSASFrame(postNoise, localIdentity, {228-        confirmSAS: vi.fn(async () => true),229-        completePairing: sourceUpsert,230-        grantAdoptionConsent: vi.fn(async () => true),231-        fetchAdoptionMaterial: vi.fn(async () => ({ rootKeyHex, vaultId })),232-        onProgress: vi.fn(),233-      });234-    })();235-236-    const receiver = await runChirpAdoptionReceiver({237-      deviceName: "Receiver",238-      deviceType: "extension",239-      openAudio: fakeAudio(sourceAttempt.outgoingPayload),240-      openPairingServer: vi.fn(async () => ({241-        transport: { ...receiverTransport, close: vi.fn(async () => {}) },242-        peerIp: "127.0.0.1",243-      })),244-      confirmSAS: vi.fn(async () => true),245-      createVaultFromRootKey: vi.fn(async (adoptedRootKeyHex) => {246-        expect(adoptedRootKeyHex).toBe(rootKeyHex);247-        return { vaultId };248-      }),249-      deriveLocalIdentity: vi.fn(async () => ({250-        deviceId: "receiver-device",251-        deviceName: "Receiver",252-        deviceType: "extension" as const,253-        longTermPublicKeyHex: "ef".repeat(32),254-        signingPublicKeyHex: "12".repeat(32),255-      })),256-      completePairing: receiverUpsert,257-      onProgress: vi.fn(),258-    });259-    await source;260-261-    expect(receiver).toEqual(expect.objectContaining({ deviceId: "source-device" }));262-    expect(receiverUpsert).toHaveBeenCalledWith(expect.objectContaining({ deviceId: "source-device" }));263-    expect(sourceUpsert).toHaveBeenCalledWith(expect.objectContaining({ deviceId: "receiver-device" }));264-  });265-});

view file at the parent

src/popup/chirp-pair.test.ts+0 −218
@@ -1,218 +0,0 @@1-import { afterEach, beforeEach, describe, expect, it } from "vitest";2-import { runChirpPairing, ChirpPairingFailure, gateOnSAS } from "./chirp-pair";3-import { __setGgwaveModuleForTest } from "../shared/ggwave";4-import { encodeChirpPayload } from "../shared/chirp-payload";5-import { generateKeyPair } from "@stablelib/x25519";6-import {7-  CHIRP_PAYLOAD_LENGTH,8-  DEVICE_TYPE_ANDROID,9-  type ChirpPayload,10-  type PairingProgress,11-} from "../shared/sync-types";12-13-function fakeGgwave() {14-  return {15-    ProtocolId: { GGWAVE_PROTOCOL_ULTRASOUND_FASTEST: 5 },16-    GGWAVE_OPERATING_MODE_RX: 1, GGWAVE_OPERATING_MODE_TX: 2, GGWAVE_OPERATING_MODE_RX_AND_TX: 3,17-    getDefaultParameters() {18-      return { sampleRateInp: 48000, sampleRateOut: 48000, samplesPerFrame: 1024, soundMarkerThreshold: 3, payloadLength: -1, operatingMode: 0 };19-    },20-    init() { return 1; },21-    free() {},22-    encode(_inst: number, data: Uint8Array | string) {23-      const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;24-      const out = new Float32Array(Math.max(bytes.length, 128));25-      for (let i = 0; i < bytes.length; i++) out[i] = bytes[i] / 255;26-      for (let i = bytes.length; i < out.length; i++) out[i] = 99;27-      return out;28-    },29-    decode(_inst: number, samples: Float32Array | Int16Array) {30-      if (samples.length < CHIRP_PAYLOAD_LENGTH) return null;31-      const buf = new Uint8Array(CHIRP_PAYLOAD_LENGTH);32-      for (let i = 0; i < CHIRP_PAYLOAD_LENGTH; i++) {33-        const v = (samples as Float32Array)[i];34-        if (v === 99) return null;35-        buf[i] = Math.round(v * 255);36-      }37-      return buf;38-    },39-  };40-}41-42-function fakeAudio(injectedChirp: ChirpPayload | null) {43-  let pending: Float32Array;44-  if (injectedChirp) {45-    const bytes = encodeChirpPayload(injectedChirp);46-    pending = new Float32Array(128);47-    for (let i = 0; i < bytes.length; i++) pending[i] = bytes[i] / 255;48-    for (let i = bytes.length; i < pending.length; i++) pending[i] = 99;49-  } else {50-    pending = new Float32Array(0);51-  }52-  return async () => ({53-    sampleRate: 48000,54-    async play() { /* no-op */ },55-    async pullSamples() { const out = pending; pending = new Float32Array(0); return out; },56-    async close() { /* no-op */ },57-  });58-}59-60-describe("runChirpPairing (popup orchestrator)", () => {61-  beforeEach(() => __setGgwaveModuleForTest(fakeGgwave()));62-  afterEach(() => __setGgwaveModuleForTest(null));63-64-  it("emits listening progress before audio I/O starts", async () => {65-    const trail: PairingProgress[] = [];66-    // Use a short-timeout chirp exchange by injecting no-chirp audio.67-    // The orchestrator will time out, but we only assert on early progress.68-    try {69-      await runChirpPairing({70-        async fetchLocalIdentity() {71-          return { deviceId: "ext", deviceName: "Firefox", deviceType: "extension", longTermPublicKeyHex: "0".repeat(64) };72-        },73-        async completePairing() { /* unreached */ },74-        async confirmSAS() { return true; },75-        async getLocalSubnet() { return "192.168.1"; },76-        chirpTimeoutMs: 200,77-        openAudio: fakeAudio(null),78-        async openPairingTransport() { throw new Error("unreached"); },79-        onProgress(p) { trail.push(p); },80-      });81-    } catch { /* expected timeout */ }82-83-    expect(trail[0].phase).toBe("idle");84-    expect(trail[1].phase).toBe("listening");85-  });86-87-  it("emits handshake then handshake_failed when no peer responds on the subnet", async () => {88-    const peerEphemeral = generateKeyPair();89-    const peerChirp: ChirpPayload = {90-      version: 0x01,91-      deviceType: DEVICE_TYPE_ANDROID,92-      ephemeralPublicKey: peerEphemeral.publicKey,93-      sessionNonce: new Uint8Array(16).fill(3),94-    };95-    const trail: PairingProgress[] = [];96-97-    await expect(98-      runChirpPairing({99-        async fetchLocalIdentity() {100-          return { deviceId: "ext", deviceName: "Firefox", deviceType: "extension", longTermPublicKeyHex: "0".repeat(64) };101-        },102-        async completePairing() { /* unreached */ },103-        async confirmSAS() { return true; },104-        async getLocalSubnet() { return "192.168.1"; },105-        openAudio: fakeAudio(peerChirp),106-        async openPairingTransport() { throw new Error("connection refused"); },107-        onProgress(p) { trail.push(p); },108-      }),109-    ).rejects.toBeInstanceOf(ChirpPairingFailure);110-111-    expect(trail.some((p) => p.phase === "handshake")).toBe(true);112-    expect(trail.at(-1)?.phase).toBe("handshake_failed");113-  }, 30_000);114-115-  it("returns handshake_failed when getLocalSubnet returns null", async () => {116-    const peerEphemeral = generateKeyPair();117-    const peerChirp: ChirpPayload = {118-      version: 0x01,119-      deviceType: DEVICE_TYPE_ANDROID,120-      ephemeralPublicKey: peerEphemeral.publicKey,121-      sessionNonce: new Uint8Array(16).fill(2),122-    };123-    const trail: PairingProgress[] = [];124-125-    await expect(126-      runChirpPairing({127-        async fetchLocalIdentity() {128-          return { deviceId: "ext", deviceName: "Firefox", deviceType: "extension", longTermPublicKeyHex: "0".repeat(64) };129-        },130-        async completePairing() { /* unreached */ },131-        async confirmSAS() { return true; },132-        async getLocalSubnet() { return null; },133-        openAudio: fakeAudio(peerChirp),134-        async openPairingTransport() { throw new Error("unreached"); },135-        onProgress(p) { trail.push(p); },136-      }),137-    ).rejects.toMatchObject({ phase: "handshake_failed" });138-    expect(trail.at(-1)?.errorMessage).toBe("no_subnet");139-  });140-141-  it("rejects responder role with non-extension local identity", async () => {142-    await expect(143-      runChirpPairing({144-        async fetchLocalIdentity() {145-          return { deviceId: "android", deviceName: "Pixel", deviceType: "android", longTermPublicKeyHex: "0".repeat(64) };146-        },147-        async completePairing() { /* unreached */ },148-        async confirmSAS() { return true; },149-        async getLocalSubnet() { return "192.168.1"; },150-        chirpTimeoutMs: 200,151-        openAudio: fakeAudio(null),152-        async openPairingTransport() { throw new Error("unreached"); },153-        onProgress() { /* noop */ },154-      }, "responder"),155-    ).rejects.toThrow(/responder requires extension/);156-  });157-158-  it("rejects initiator role with extension local identity", async () => {159-    await expect(160-      runChirpPairing({161-        async fetchLocalIdentity() {162-          return { deviceId: "ext", deviceName: "Firefox", deviceType: "extension", longTermPublicKeyHex: "0".repeat(64) };163-        },164-        async completePairing() { /* unreached */ },165-        async confirmSAS() { return true; },166-        async getLocalSubnet() { return null; },167-        chirpTimeoutMs: 200,168-        openAudio: fakeAudio(null),169-        async openPairingTransport() { throw new Error("unreached"); },170-        async openPairingServer() { throw new Error("unreached"); },171-        onProgress() { /* noop */ },172-      }, "initiator"),173-    ).rejects.toThrow(/initiator requires non-extension/);174-  });175-176-  it("rejects initiator role when openPairingServer dep is missing", async () => {177-    await expect(178-      runChirpPairing({179-        async fetchLocalIdentity() {180-          return { deviceId: "android", deviceName: "Pixel", deviceType: "android", longTermPublicKeyHex: "0".repeat(64) };181-        },182-        async completePairing() { /* unreached */ },183-        async confirmSAS() { return true; },184-        async getLocalSubnet() { return null; },185-        chirpTimeoutMs: 200,186-        openAudio: fakeAudio(null),187-        async openPairingTransport() { throw new Error("unreached"); },188-        onProgress() { /* noop */ },189-      }, "initiator"),190-    ).rejects.toThrow(/openPairingServer/);191-  });192-});193-194-describe("gateOnSAS (PCH-01-024 — the SAS gate is MANDATORY before persist)", () => {195-  const device = { deviceId: "peer" } as unknown as Parameters<typeof gateOnSAS>[1]["device"];196-197-  function makeDeps(confirm: boolean) {198-    const progress: PairingProgress[] = [];199-    const deps = {200-      confirmSAS: async () => confirm,201-      onProgress: (p: PairingProgress) => progress.push(p),202-    } as unknown as Parameters<typeof gateOnSAS>[0];203-    return { deps, progress };204-  }205-206-  it("returns the device when the user confirms the SAS", async () => {207-    const { deps } = makeDeps(true);208-    await expect(gateOnSAS(deps, { device, sas: "123456" })).resolves.toBe(device);209-  });210-211-  it("aborts with handshake_failed when the user rejects the SAS", async () => {212-    const { deps, progress } = makeDeps(false);213-    await expect(gateOnSAS(deps, { device, sas: "123456" })).rejects.toBeInstanceOf(214-      ChirpPairingFailure,215-    );216-    expect(progress.at(-1)?.phase).toBe("handshake_failed");217-  });218-});

view file at the parent

src/popup/chirp-pair.ts+0 −547
@@ -1,547 +0,0 @@1-// Popup-side orchestrator for Phase-1 chirp pairing.2-//3-// Runs end-to-end inside the popup or the settings page (it depends on4-// `getUserMedia`, which is unavailable in the MV3 service worker). The5-// background's role is limited to handing us the local identity and6-// persisting the resulting PairedDevice record.7-//8-// Flow:9-//   1. fetch local identity via CHIRP_GET_LOCAL_IDENTITY10-//   2. open Web Audio + microphone11-//   3. encode our chirp and broadcast on a 4s loop while listening12-//   4. on chirp decoded, transition to handshake phase13-//   5. probe local subnet on the well-known pairing port for the peer's14-//      WebSocket; complete Noise NK over it; exchange long-term identities15-//   6. send CHIRP_COMPLETE_PAIRING to persist the new pair16-//17-// The orchestrator notifies the UI through an `onProgress` callback so18-// `ChirpPairingFlow` can drive its three-act animation in lockstep with19-// the real protocol state.20-21-import {22-  ChirpExchangeError,23-  createPairingAttempt,24-  openBrowserAudioIO,25-  runChirpExchange,26-} from "../shared/chirp-pairing";27-import {28-  handlePairIdentityFrame,29-  runPairingInitiatorOpenChannel,30-  runPairingInitiator,31-  runPairingResponderOpenFrame,32-  type LocalIdentity,33-  type PairingHandshakeResult,34-  type PostNoiseFrame,35-} from "../shared/chirp-handshake";36-import { computeVaultId } from "../shared/sync-identity";37-import { grantAdoption, isAdoptRequest, requestAdoption, type AdoptRequest } from "../shared/chirp-adopt";38-import {39-  DEVICE_TYPE_EXTENSION,40-  DEVICE_TYPE_ANDROID,41-  DEVICE_TYPE_IOS,42-  type DeviceType,43-  type PairedDevice,44-  type PairingProgress,45-} from "../shared/sync-types";46-47-/**48- * Well-known TCP port used during Phase-1 pairing. Android opens a49- * WebSocket server on this port only while the pairing screen is active;50- * the extension scans the /24 for it after the chirp exchange completes.51- * The persistent sync port (random high port) is announced via the52- * encrypted PAIR_IDENTITY frame and stored on the PairedDevice.53- */54-export const CHIRP_PAIRING_PORT = 49142;55-56-export interface PairingTransport {57-  send(msg: Uint8Array): Promise<void>;58-  receive(): Promise<Uint8Array>;59-  close(): Promise<void>;60-}61-62-export interface ChirpPairingDeps {63-  /** Background round-trip — returns the local public identity. */64-  fetchLocalIdentity(): Promise<LocalIdentity>;65-  /** Background round-trip — persists the paired device. */66-  completePairing(paired: PairedDevice): Promise<void>;67-  /**68-   * Show the Short Authentication String and resolve true only when the user69-   * confirms it matches the code displayed on the OTHER device. Pairing is70-   * aborted (the peer is NOT persisted) on false. This is the out-of-band71-   * mutual-authentication step that defeats a LAN/audio-range attacker who72-   * completes the Noise NK handshake in place of the real device — their73-   * handshake produces a different SAS than the genuine peer shows, so the74-   * user declines (audit HIGH — pairing initiator authentication).75-   */76-  confirmSAS(sas: string): Promise<boolean>;77-  /** Optional — cache the peer's IP for next session's fast path. */78-  cacheLastKnownIp?(pubkeyHex: string, ip: string): Promise<void>;79-  /**80-   * Optional — activate a Pro license the peer sent in its identity frame.81-   * Called ONLY after the SAS is confirmed. The implementation verifies the82-   * signature and ignores it if this device is already Pro. This is how one83-   * Pro device grants Pro to the other when pairing (either direction).84-   */85-  activatePeerLicense?(license: string): Promise<void>;86-  /** Source-device adoption consent gate, shown only after SAS confirmation. */87-  grantAdoptionConsent?(request: AdoptRequest, sas: string): Promise<boolean>;88-  /** Source-device grant material, fetched only after SAS and consent. */89-  fetchAdoptionMaterial?(): Promise<{ rootKeyHex: string; vaultId: string; license?: string }>;90-  /**91-   * RESPONDER ONLY — returns "192.168.1" for a /24; null if not detectable.92-   * Used to scan the LAN for the initiator's pairing-port WebSocket server.93-   */94-  getLocalSubnet(): Promise<string | null>;95-  /**96-   * RESPONDER ONLY — connect to the initiator's WebSocket server at the97-   * given endpoint. Used during the /24 scan and again for the real98-   * handshake transport.99-   */100-  openPairingTransport(endpoint: { ip: string; port: number }): Promise<PairingTransport>;101-  /**102-   * INITIATOR ONLY — open a WebSocket server on CHIRP_PAIRING_PORT and103-   * resolve with a transport when the responder connects. Backed by a104-   * native plugin on Capacitor; not used in the extension build.105-   *106-   * The promise resolves once a peer connection has been accepted. If no107-   * peer arrives before the chirp window expires, reject so the orchestrator108-   * can surface a `handshake_failed` phase. Close the server in `.close()`.109-   */110-  openPairingServer?(): Promise<{111-    transport: PairingTransport;112-    peerIp: string;113-  }>;114-  /** Override the chirp exchange's `AudioIO` for tests. */115-  openAudio?(): Promise<Awaited<ReturnType<typeof openBrowserAudioIO>>>;116-  /** Override the audio-exchange timeout — defaults to 90s per the brief. */117-  chirpTimeoutMs?: number;118-  onProgress(p: PairingProgress): void;119-}120-121-export type PairingRole = "initiator" | "responder";122-123-export class ChirpPairingFailure extends Error {124-  readonly phase: PairingProgress["phase"];125-  constructor(phase: PairingProgress["phase"], message: string) {126-    super(message);127-    this.phase = phase;128-  }129-}130-131-export async function runChirpPairing(132-  deps: ChirpPairingDeps,133-  role: PairingRole = "responder",134-): Promise<PairedDevice> {135-  deps.onProgress({ phase: "idle" });136-137-  const localIdentity = await deps.fetchLocalIdentity();138-139-  // Each role only runs on the matching platform — the extension is always140-  // responder (it scans the LAN), Android/iOS/desktop are always initiator141-  // (they open the WS server). Mismatches indicate a wiring bug upstream.142-  if (role === "responder" && localIdentity.deviceType !== "extension") {143-    throw new ChirpPairingFailure("handshake_failed", "responder requires extension identity");144-  }145-  if (role === "initiator" && localIdentity.deviceType === "extension") {146-    throw new ChirpPairingFailure("handshake_failed", "initiator requires non-extension identity");147-  }148-  if (role === "initiator" && !deps.openPairingServer) {149-    throw new ChirpPairingFailure("handshake_failed", "initiator requires openPairingServer dep");150-  }151-152-  deps.onProgress({ phase: "listening" });153-154-  const deviceTypeByte =155-    localIdentity.deviceType === "android" ? DEVICE_TYPE_ANDROID :156-    localIdentity.deviceType === "ios" ? DEVICE_TYPE_IOS :157-    DEVICE_TYPE_EXTENSION;158-  const attempt = createPairingAttempt(deviceTypeByte);159-160-  let remoteChirpPublicKey: Uint8Array;161-  try {162-    const audio = await (deps.openAudio ?? openBrowserAudioIO)();163-    const result = await runChirpExchange(audio, { attempt, timeoutMs: deps.chirpTimeoutMs });164-    remoteChirpPublicKey = result.remoteChirp.ephemeralPublicKey;165-  } catch (err) {166-    if (err instanceof ChirpExchangeError) {167-      const phase: PairingProgress["phase"] =168-        err.kind === "timeout" ? "timeout" :169-        err.kind === "version_mismatch" ? "version_mismatch" :170-        err.kind === "mic_denied" ? "mic_denied" :171-        "handshake_failed";172-      deps.onProgress({ phase, errorMessage: err.message });173-      throw new ChirpPairingFailure(phase, err.message);174-    }175-    throw err;176-  }177-178-  deps.onProgress({ phase: "handshake" });179-180-  if (role === "responder") {181-    return runResponderHandshake(deps, attempt, localIdentity);182-  }183-  return runInitiatorHandshake(deps, remoteChirpPublicKey, localIdentity);184-}185-186-export interface ChirpAdoptionReceiverDeps {187-  deviceName: string;188-  deviceType?: DeviceType;189-  generateDeviceId?(): string;190-  createVaultFromRootKey(rootKeyHex: string): Promise<{ vaultId: string }>;191-  deriveLocalIdentity(): Promise<LocalIdentity>;192-  completePairing(paired: PairedDevice): Promise<void>;193-  applyLicense?(license: string): Promise<void>;194-  confirmSAS(sas: string): Promise<boolean>;195-  openPairingServer(): Promise<{ transport: PairingTransport; peerIp: string }>;196-  openAudio?(): Promise<Awaited<ReturnType<typeof openBrowserAudioIO>>>;197-  chirpTimeoutMs?: number;198-  onProgress(p: PairingProgress): void;199-}200-201-export async function runChirpAdoptionReceiver(202-  deps: ChirpAdoptionReceiverDeps,203-): Promise<PairedDevice> {204-  deps.onProgress({ phase: "idle" });205-  deps.onProgress({ phase: "listening" });206-207-  const attempt = createPairingAttempt(deviceTypeByteFor(deps.deviceType ?? "extension"));208-  let remoteChirpPublicKey: Uint8Array;209-  try {210-    const audio = await (deps.openAudio ?? openBrowserAudioIO)();211-    const result = await runChirpExchange(audio, { attempt, timeoutMs: deps.chirpTimeoutMs });212-    remoteChirpPublicKey = result.remoteChirp.ephemeralPublicKey;213-  } catch (err) {214-    if (err instanceof ChirpExchangeError) {215-      const phase: PairingProgress["phase"] =216-        err.kind === "timeout" ? "timeout" :217-        err.kind === "version_mismatch" ? "version_mismatch" :218-        err.kind === "mic_denied" ? "mic_denied" :219-        "handshake_failed";220-      deps.onProgress({ phase, errorMessage: err.message });221-      throw new ChirpPairingFailure(phase, err.message);222-    }223-    throw err;224-  }225-226-  deps.onProgress({ phase: "handshake" });227-  let server: { transport: PairingTransport; peerIp: string };228-  try {229-    server = await deps.openPairingServer();230-  } catch (err) {231-    deps.onProgress({ phase: "handshake_failed", errorMessage: (err as Error).message });232-    throw new ChirpPairingFailure("handshake_failed", (err as Error).message);233-  }234-235-  try {236-    const postNoise = await runPairingInitiatorOpenChannel(remoteChirpPublicKey, server.transport);237-    const confirmed = await deps.confirmSAS(postNoise.sas);238-    if (!confirmed) {239-      deps.onProgress({ phase: "handshake_failed", errorMessage: "sas_rejected" });240-      throw new ChirpPairingFailure("handshake_failed", "sas_rejected");241-    }242-    const source = await requestAdoption(postNoise.channel, {243-      deviceName: deps.deviceName,244-      generateDeviceId: deps.generateDeviceId ?? generateUuid,245-      createVaultFromRootKey: deps.createVaultFromRootKey,246-      deriveLocalIdentity: deps.deriveLocalIdentity,247-      upsertPairedDevice: deps.completePairing,248-      applyLicense: deps.applyLicense,249-      onError(error) {250-        throw new Error(error);251-      },252-    });253-    if (!source) throw new Error("adoption_failed");254-    deps.onProgress({ phase: "paired", peerDeviceName: source.deviceName });255-    return source;256-  } catch (err) {257-    if (err instanceof ChirpPairingFailure) throw err;258-    deps.onProgress({ phase: "handshake_failed", errorMessage: (err as Error).message });259-    throw new ChirpPairingFailure("handshake_failed", (err as Error).message);260-  } finally {261-    await server.transport.close().catch(() => undefined);262-  }263-}264-265-export interface SourcePostSASDeps {266-  confirmSAS(sas: string): Promise<boolean>;267-  completePairing(device: PairedDevice): Promise<void>;268-  grantAdoptionConsent?(request: AdoptRequest, sas: string): Promise<boolean>;269-  fetchAdoptionMaterial?(): Promise<{ rootKeyHex: string; vaultId: string; license?: string }>;270-  activatePeerLicense?(license: string): Promise<void>;271-  onProgress(p: PairingProgress): void;272-}273-274-export async function routeSourcePostSASFrame(275-  postNoise: PostNoiseFrame,276-  localIdentity: LocalIdentity,277-  deps: SourcePostSASDeps,278-): Promise<PairedDevice | null> {279-  if (isAdoptRequest(postNoise.frame)) {280-    const sasConfirmed = await deps.confirmSAS(postNoise.sas);281-    if (!sasConfirmed) {282-      await closeIfPossible(postNoise.channel);283-      throw new ChirpPairingFailure("handshake_failed", "sas_rejected");284-    }285-    if (!deps.grantAdoptionConsent || !deps.fetchAdoptionMaterial) {286-      await closeIfPossible(postNoise.channel);287-      throw new ChirpPairingFailure("handshake_failed", "adoption_not_supported");288-    }289-290-    const approved = await deps.grantAdoptionConsent(postNoise.frame, postNoise.sas);291-    if (!approved) {292-      await closeIfPossible(postNoise.channel);293-      throw new ChirpPairingFailure("handshake_failed", "adoption_rejected");294-    }295-296-    const material = await deps.fetchAdoptionMaterial();297-    if (!/^[0-9a-f]{64}$/.test(material.rootKeyHex)) throw new Error("ADOPT_INVALID_ROOT_KEY");298-    const rootKeyBytes = hexToBytes(material.rootKeyHex);299-    try {300-      const expectedVaultId = await computeVaultId(rootKeyBytes);301-      if (expectedVaultId !== material.vaultId) throw new Error("ADOPT_VAULT_ID_MISMATCH");302-      const adopted = await grantAdoption(postNoise.channel, postNoise.frame, {303-        sourceIdentity: localIdentity,304-        rootKeyHex: async () => material.rootKeyHex,305-        vaultId: async () => material.vaultId,306-        license: async () => material.license,307-        onConsent: async () => true,308-        upsertPairedDevice: deps.completePairing,309-      });310-      if (adopted) deps.onProgress({ phase: "paired", peerDeviceName: adopted.deviceName });311-      return adopted;312-    } finally {313-      rootKeyBytes.fill(0);314-    }315-  }316-317-  if ((postNoise.frame as { type?: unknown } | null)?.type === "PAIR_IDENTITY") {318-    const result = await handlePairIdentityFrame(319-      postNoise.frame,320-      postNoise.channel,321-      localIdentity,322-      postNoise.sas,323-    );324-    const paired = await gateOnSAS(deps, result);325-    await activatePeerLicenseIfAny(deps, result);326-    await deps.completePairing(paired);327-    return paired;328-  }329-330-  await closeIfPossible(postNoise.channel);331-  throw new ChirpPairingFailure("handshake_failed", "unexpected_pairing_frame");332-}333-334-async function runResponderHandshake(335-  deps: ChirpPairingDeps,336-  attempt: ReturnType<typeof createPairingAttempt>,337-  localIdentity: LocalIdentity,338-): Promise<PairedDevice> {339-  // The peer is somewhere on the LAN; we don't know its IP yet because340-  // the chirp payload deliberately omits it. Scan the /24 for the341-  // pairing-mode WebSocket port.342-  const subnet = await deps.getLocalSubnet();343-  if (!subnet) {344-    deps.onProgress({ phase: "handshake_failed", errorMessage: "no_subnet" });345-    throw new ChirpPairingFailure("handshake_failed", "could not determine local subnet");346-  }347-348-  const peerEndpoint = await firstResponsivePeer(deps, subnet, CHIRP_PAIRING_PORT);349-  if (!peerEndpoint) {350-    deps.onProgress({ phase: "handshake_failed", errorMessage: "peer_not_found" });351-    throw new ChirpPairingFailure("handshake_failed", "could not reach paired peer on subnet");352-  }353-354-  const transport = await deps.openPairingTransport(peerEndpoint);355-  let postNoise: PostNoiseFrame;356-  try {357-    postNoise = await runPairingResponderOpenFrame(358-      attempt.ephemeralPrivateKey,359-      attempt.ephemeralPublicKey,360-      transport,361-    );362-  } catch (err) {363-    await transport.close();364-    deps.onProgress({ phase: "handshake_failed", errorMessage: (err as Error).message });365-    throw new ChirpPairingFailure("handshake_failed", (err as Error).message);366-  }367-  const paired = await routeSourcePostSASFrame(postNoise, localIdentity, deps);368-  await transport.close();369-  if (!paired) {370-    throw new ChirpPairingFailure("handshake_failed", "adoption_rejected");371-  }372-  await deps.cacheLastKnownIp?.(paired.longTermPublicKeyHex ?? "", peerEndpoint.ip);373-  deps.onProgress({ phase: "paired", peerDeviceName: paired.deviceName });374-  return paired;375-}376-377-/**378- * Surface the SAS for user confirmation and abort the pairing if it is not379- * confirmed to match the peer device. Shared by both roles.380- */381-// Exported for the PCH-01-024 regression test that pins the SAS gate as382-// MANDATORY (the chirp-handshake.ts security comment depends on this staying true).383-export async function gateOnSAS(384-  deps: Pick<ChirpPairingDeps, "confirmSAS" | "onProgress">,385-  result: PairingHandshakeResult,386-): Promise<PairedDevice> {387-  let confirmed: boolean;388-  try {389-    confirmed = await deps.confirmSAS(result.sas);390-  } catch (err) {391-    deps.onProgress({ phase: "handshake_failed", errorMessage: (err as Error).message });392-    throw new ChirpPairingFailure("handshake_failed", (err as Error).message);393-  }394-  if (!confirmed) {395-    deps.onProgress({ phase: "handshake_failed", errorMessage: "sas_rejected" });396-    throw new ChirpPairingFailure(397-      "handshake_failed",398-      "pairing rejected — the verification codes did not match",399-    );400-  }401-  return result.device;402-}403-404-/**405- * If the (now SAS-confirmed) peer advertised a Pro license, hand it to the406- * background to verify + activate. Best-effort: a bad/duplicate license never407- * fails the pairing — the device link still succeeds.408- */409-async function activatePeerLicenseIfAny(410-  deps: Pick<ChirpPairingDeps, "activatePeerLicense">,411-  result: PairingHandshakeResult,412-): Promise<void> {413-  if (!result.peerLicense || !deps.activatePeerLicense) return;414-  try {415-    await deps.activatePeerLicense(result.peerLicense);416-  } catch {417-    // Invalid signature, already-Pro, or storage error — pairing stands.418-  }419-}420-421-async function closeIfPossible(channel: unknown): Promise<void> {422-  await (channel as { close?: () => Promise<void> }).close?.();423-}424-425-function hexToBytes(hex: string): Uint8Array {426-  const out = new Uint8Array(hex.length / 2);427-  for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);428-  return out;429-}430-431-function deviceTypeByteFor(type: DeviceType) {432-  return type === "android" ? DEVICE_TYPE_ANDROID :433-    type === "ios" ? DEVICE_TYPE_IOS :434-    DEVICE_TYPE_EXTENSION;435-}436-437-function generateUuid(): string {438-  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {439-    return crypto.randomUUID();440-  }441-  const bytes = new Uint8Array(16);442-  crypto.getRandomValues(bytes as Uint8Array<ArrayBuffer>);443-  bytes[6] = (bytes[6] & 0x0f) | 0x40;444-  bytes[8] = (bytes[8] & 0x3f) | 0x80;445-  const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");446-  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;447-}448-449-async function runInitiatorHandshake(450-  deps: ChirpPairingDeps,451-  remoteChirpPublicKey: Uint8Array,452-  localIdentity: LocalIdentity,453-): Promise<PairedDevice> {454-  // Initiator opened a WebSocket server on CHIRP_PAIRING_PORT before the455-  // chirp exchange started; here we wait for the responder (extension)456-  // to finish its /24 scan and connect to us.457-  let server: { transport: PairingTransport; peerIp: string };458-  try {459-    server = await deps.openPairingServer!();460-  } catch (err) {461-    deps.onProgress({ phase: "handshake_failed", errorMessage: (err as Error).message });462-    throw new ChirpPairingFailure("handshake_failed", (err as Error).message);463-  }464-465-  let result: PairingHandshakeResult;466-  try {467-    result = await runPairingInitiator(468-      remoteChirpPublicKey,469-      localIdentity,470-      server.transport,471-    );472-  } catch (err) {473-    await server.transport.close();474-    deps.onProgress({ phase: "handshake_failed", errorMessage: (err as Error).message });475-    throw new ChirpPairingFailure("handshake_failed", (err as Error).message);476-  }477-  await server.transport.close();478-479-  // Same out-of-band SAS confirmation as the responder side — the initiator480-  // (mobile vault host) must not sync to an unverified peer either.481-  const paired = await gateOnSAS(deps, result);482-  await activatePeerLicenseIfAny(deps, result);483-484-  await deps.completePairing(paired);485-  await deps.cacheLastKnownIp?.(paired.longTermPublicKeyHex ?? "", server.peerIp);486-  deps.onProgress({ phase: "paired", peerDeviceName: paired.deviceName });487-  return paired;488-}489-490-async function firstResponsivePeer(491-  deps: ChirpPairingDeps,492-  subnet: string,493-  port: number,494-): Promise<{ ip: string; port: number } | null> {495-  // We don't have the peer's long-term key yet (that's what we're about496-  // to exchange), so we can't run a Noise XX probe like the steady-state497-  // sync path does. The pairing-port server accepts any incoming Noise NK498-  // request during its short pairing window; the first WebSocket that499-  // accepts our connection is the peer.500-  //501-  // Per-IP timeout strategy: browser WebSocket constructors against an502-  // unbound TCP port can hang for several seconds before erroring, which503-  // would stretch a /24 scan to minutes. Each probe is raced against a504-  // 250ms timer via Promise.race — if the timer wins, we treat the IP as505-  // a miss and close the underlying socket if it lands after the loss so506-  // we don't leak connections. With 16-wide batching this bounds the full507-  // scan to ~254 * 250ms / 16 ≈ 4 seconds in the worst case.508-  const PROBE_TIMEOUT_MS = 250;509-  const batch = 16;510-  for (let start = 1; start <= 254; start += batch) {511-    const end = Math.min(start + batch - 1, 254);512-    const ips: string[] = [];513-    for (let i = start; i <= end; i++) ips.push(`${subnet}.${i}`);514-515-    const results = await Promise.all(516-      ips.map(async (ip) => {517-        let timer: ReturnType<typeof setTimeout> | undefined;518-        const timeout = new Promise<null>((resolve) => {519-          timer = setTimeout(() => resolve(null), PROBE_TIMEOUT_MS);520-        });521-        const probe = (async () => {522-          try {523-            const t = await deps.openPairingTransport({ ip, port });524-            return { transport: t, ip, port };525-          } catch {526-            return null;527-          }528-        })();529-        const winner = await Promise.race([probe, timeout]);530-        if (winner && "transport" in winner) {531-          if (timer !== undefined) clearTimeout(timer);532-          await winner.transport.close();533-          return { ip: winner.ip, port: winner.port };534-        }535-        // Timer won — make sure a late-arriving open socket is closed so536-        // we don't leak a connection past the scan.537-        probe.then((late) => {538-          if (late) void late.transport.close();539-        }).catch(() => { /* already a miss */ });540-        return null;541-      }),542-    );543-    const hit = results.find((r): r is { ip: string; port: number } => r !== null);544-    if (hit) return hit;545-  }546-  return null;547-}

view file at the parent

src/popup/components/ChirpPairingFlow.test.tsx+0 −102
@@ -1,102 +0,0 @@1-// @vitest-environment jsdom2-3-import { afterEach, describe, expect, it, vi } from "vitest";4-import { ChirpPairingFlow } from "./ChirpPairingFlow";5-import type { PairingProgress } from "../../shared/sync-types";6-import { renderView } from "../views/view-test-utils";7-8-function progress(phase: PairingProgress["phase"], extra: Partial<PairingProgress> = {}): PairingProgress {9-  return { phase, ...extra };10-}11-12-describe("ChirpPairingFlow", () => {13-  const cleanups: Array<() => void> = [];14-  afterEach(() => {15-    while (cleanups.length) cleanups.pop()!();16-  });17-18-  function mount(progress: PairingProgress, handlers: { onCancel?: () => void; onRetry?: () => void; onDismiss?: () => void } = {}) {19-    const r = renderView(20-      <ChirpPairingFlow21-        progress={progress}22-        onCancel={handlers.onCancel ?? (() => {})}23-        onRetry={handlers.onRetry ?? (() => {})}24-        onDismiss={handlers.onDismiss ?? (() => {})}25-      />,26-    );27-    cleanups.push(r.dispose);28-    return r;29-  }30-31-  it("renders the listening copy in Act 1", () => {32-    const { container } = mount(progress("listening"));33-    expect(container.textContent).toContain("Hold your phone near your laptop");34-    expect(container.textContent).toContain("Peach will pair automatically");35-  });36-37-  it("renders the handshake copy in Act 2", () => {38-    const { container } = mount(progress("handshake"));39-    expect(container.textContent).toContain("Pairing…");40-  });41-42-  it("renders the paired copy with the peer device name", () => {43-    const { container } = mount(progress("paired", { peerDeviceName: "June's Pixel 9" }));44-    expect(container.textContent).toContain("Paired!");45-    expect(container.textContent).toContain("Connected to June's Pixel 9");46-  });47-48-  it("Cancel button calls onCancel in the listening state", () => {49-    const onCancel = vi.fn();50-    const { container } = mount(progress("listening"), { onCancel });51-    const btn = Array.from(container.querySelectorAll("button")).find(52-      (b) => b.textContent === "Cancel",53-    );54-    expect(btn).toBeTruthy();55-    btn!.click();56-    expect(onCancel).toHaveBeenCalled();57-  });58-59-  it("Try again button calls onRetry on timeout", () => {60-    const onRetry = vi.fn();61-    const { container } = mount(progress("timeout"), { onRetry });62-    expect(container.textContent).toContain("Couldn’t find your phone");63-    const btn = Array.from(container.querySelectorAll("button")).find(64-      (b) => b.textContent === "Try again",65-    );66-    expect(btn).toBeTruthy();67-    btn!.click();68-    expect(onRetry).toHaveBeenCalled();69-  });70-71-  it("shows the microphone-permission help text on mic_denied", () => {72-    const { container } = mount(progress("mic_denied"));73-    expect(container.textContent).toContain("Microphone access needed");74-    expect(container.textContent).toContain("No audio is recorded or stored");75-  });76-77-  it("shows the version-mismatch copy", () => {78-    const { container } = mount(progress("version_mismatch"));79-    expect(container.textContent).toContain("Update Peach on both devices");80-  });81-82-  it("auto-dismisses after 2.5s in the paired state", () => {83-    vi.useFakeTimers();84-    try {85-      const onDismiss = vi.fn();86-      mount(progress("paired", { peerDeviceName: "Pixel" }), { onDismiss });87-      // The createEffect that arms the dismissal runs synchronously on88-      // mount because the phase is already "paired"; advancing the timers89-      // by 2500ms fires the setTimeout callback.90-      vi.advanceTimersByTime(2500);91-      expect(onDismiss).toHaveBeenCalled();92-    } finally {93-      vi.useRealTimers();94-    }95-  });96-97-  it("data-phase attribute mirrors the progress phase", () => {98-    const { container } = mount(progress("chirping"));99-    const root = container.querySelector(".peach-chirp-pair") as HTMLElement;100-    expect(root.dataset.phase).toBe("chirping");101-  });102-});

view file at the parent

src/popup/components/ChirpPairingFlow.tsx+6 −388
@@ -1,393 +1,11 @@1-// ChirpPairingFlow — the audio pairing screen.1+// Pairing birds — the two illustrated characters used on the pairing screens.22 //3-// Renders the three-act bird animation specified in the brief §6.2:4-//   Act 1 (waiting)   — desktop finch chirps, soft rings pulse from its beak5-//   Act 2 (handshake) — mobile swallow hops in, sound-wave arcs fly between6-//                        the two birds, both heads tilt toward each other7-//   Act 3 (paired)    — waves resolve into a glowing connection dot; both8-//                        birds wing-flutter and bob together9-//10-// Phase mapping (PairingPhase from sync-types):11-//   idle | listening | chirping → Act 112-//   handshake                   → Act 213-//   paired                      → Act 314-//   timeout                     → desktop bird droops, retry CTA15-//   mic_denied                  → static stage, browser-settings hint16-//   version_mismatch            → static stage, update hint, retry CTA17-//   handshake_failed            → retry CTA18-//19-// The orchestrator (popup/chirp-pair.ts) pushes phase updates into20-// `progress` and the component renders them. This component owns no21-// microphone or socket state of its own.3+// These are all that remains of the audio-chirp pairing animation this file is4+// named after: the flow itself was removed with Chirp, but the birds outlived5+// it and still animate above the code-entry, connecting, SAS and paired6+// screens (ChirpSync.tsx's `desktopBirdStage`).227 23-import { Component, Show, createEffect, createMemo, createSignal, onCleanup } from "solid-js";24-import type { PairingPhase, PairingProgress } from "../../shared/sync-types";25-26-export interface ChirpPairingFlowProps {27-  progress: PairingProgress;28-  /** Called when the user taps Retry on a terminal-error state. */29-  onRetry: () => void;30-  /** Called when the user taps Cancel/back on the idle/listening state. */31-  onCancel: () => void;32-  /** Called once the paired-state dismissal animation completes. */33-  onDismiss: () => void;34-}35-36-const PAIRED_DISMISS_MS = 2_500;37-// During handshake, swap which bird the flight-wave emanates from so the38-// chirp feels reciprocated. Desktop wave first, then mobile.39-const HANDSHAKE_DESKTOP_WAVE_MS = 900;40-const HANDSHAKE_MOBILE_WAVE_MS = 1_900;41-42-export const ChirpPairingFlow: Component<ChirpPairingFlowProps> = (props) => {43-  const [dismissArmed, setDismissArmed] = createSignal(false);44-  const [waveSide, setWaveSide] = createSignal<"desktop" | "mobile" | null>(null);45-46-  createEffect(() => {47-    if (props.progress.phase === "paired" && !dismissArmed()) {48-      setDismissArmed(true);49-      const t = setTimeout(() => props.onDismiss(), PAIRED_DISMISS_MS);50-      onCleanup(() => clearTimeout(t));51-    }52-  });53-54-  // Swap which side the flight-wave plays from during Act 2.55-  createEffect(() => {56-    if (props.progress.phase !== "handshake") {57-      setWaveSide(null);58-      return;59-    }60-    setWaveSide("desktop");61-    const t1 = setTimeout(() => setWaveSide("mobile"), HANDSHAKE_DESKTOP_WAVE_MS);62-    const t2 = setTimeout(() => setWaveSide(null), HANDSHAKE_MOBILE_WAVE_MS);63-    onCleanup(() => { clearTimeout(t1); clearTimeout(t2); });64-  });65-66-  const phase = createMemo(() => props.progress.phase);67-  const desktopPose = createMemo(() => poseFor("desktop", phase()));68-  const mobilePose = createMemo(() => poseFor("mobile", phase()));69-70-  const showDesktopChirp = createMemo(() => {71-    const p = phase();72-    return p === "listening" || p === "chirping" || (p === "handshake" && waveSide() !== "mobile");73-  });74-  const showMobileChirp = createMemo(() => phase() === "handshake" && waveSide() === "mobile");75-76-  return (77-    <div78-      class="peach-chirp-pair"79-      data-phase={phase()}80-      role="dialog"81-      aria-label="Pair a device"82-      onClick={() => phase() === "paired" && props.onDismiss()}83-    >84-      <PairingHeader onBack={() => props.onCancel()} phase={phase()} />85-86-      <div class="peach-chirp-chip-row">87-        <StatusChip phase={phase()} />88-      </div>89-90-      <div class={`peach-chirp-stage act-${phase()}`} aria-hidden="true">91-        <div class="peach-chirp-bird-slot peach-chirp-bird-slot-desktop">92-          <DesktopBird pose={desktopPose()} />93-          <Show when={showDesktopChirp()}>94-            <ChirpArcs direction="right" />95-          </Show>96-        </div>97-        <div class="peach-chirp-bird-slot peach-chirp-bird-slot-mobile">98-          <MobileBird pose={mobilePose()} />99-          <Show when={showMobileChirp()}>100-            <ChirpArcs direction="left" />101-          </Show>102-        </div>103-        <div class="peach-chirp-flight-track">104-          <FlightWave from="desktop" visible={waveSide() === "desktop"} />105-          <FlightWave from="mobile" visible={waveSide() === "mobile"} />106-          <ConnectionDot visible={phase() === "paired"} />107-        </div>108-        <div class="peach-chirp-motes">109-          <For8>110-            {(i) => <span class="peach-chirp-mote" style={{ "--mote-i": i }} />}111-          </For8>112-        </div>113-      </div>114-115-      <PairingCopy progress={props.progress} />116-      <PairingAction117-        progress={props.progress}118-        onCancel={() => props.onCancel()}119-        onRetry={() => props.onRetry()}120-        onDone={() => props.onDismiss()}121-      />122-123-      <Show when={phase() === "mic_denied"}>124-        <p class="peach-chirp-mic-help">125-          Update your browser's site settings to allow Peach to access the microphone,126-          then try again.127-        </p>128-      </Show>129-130-      <PairingFooter phase={phase()} />131-132-      {/* Polite live region so the act transitions are announced to133-          assistive tech even though the stage itself is decorative. */}134-      <div class="peach-sr-only" aria-live="polite">{liveText(props.progress)}</div>135-    </div>136-  );137-};138-139-function poseFor(side: "desktop" | "mobile", phase: PairingPhase): string {140-  if (side === "desktop") {141-    switch (phase) {142-      case "listening":143-      case "chirping": return "chirping";144-      case "handshake": return "attentive";145-      case "paired": return "happy";146-      case "timeout":147-      case "handshake_failed":148-      case "version_mismatch": return "sad";149-      default: return "idle";150-    }151-  }152-  switch (phase) {153-    case "handshake": return "attentive";154-    case "paired": return "happy";155-    default: return "hidden";156-  }157-}158-159-function liveText(p: PairingProgress): string {160-  switch (p.phase) {161-    case "listening":162-    case "chirping": return "Listening for the chirp";163-    case "handshake": return "Pairing — exchanging keys";164-    case "paired": return p.peerDeviceName ? `Paired — connected to ${p.peerDeviceName}` : "Paired";165-    case "timeout": return "Couldn't find your phone";166-    case "mic_denied": return "Microphone access needed";167-    case "version_mismatch": return "Update Peach on both devices";168-    case "handshake_failed": return "Pairing failed — try again";169-    default: return "";170-  }171-}172-173-// ---------------------------------------------------------------------------174-// Header, chip row, footer175-// ---------------------------------------------------------------------------176-177-const PairingHeader: Component<{ onBack: () => void; phase: PairingPhase }> = (props) => (178-  <header class="peach-chirp-head">179-    <button class="peach-chirp-back" aria-label="Back" onClick={() => props.onBack()}>180-      <svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">181-        <path d="M10 3 L5 8 L10 13" />182-      </svg>183-    </button>184-    <div class="peach-chirp-title">Sync</div>185-    <div class="peach-chirp-head-spacer" />186-  </header>187-);188-189-const StatusChip: Component<{ phase: PairingPhase }> = (props) => {190-  const label = () => {191-    switch (props.phase) {192-      case "listening":193-      case "chirping":194-      case "idle": return "listening";195-      case "handshake": return "handshake";196-      case "paired": return "paired";197-      case "mic_denied": return "mic blocked";198-      case "version_mismatch": return "version mismatch";199-      case "timeout": return "no response";200-      case "handshake_failed": return "failed";201-    }202-  };203-  return (204-    <div class="peach-chirp-chip" data-phase={props.phase}>205-      <span class="peach-chirp-chip-dot" />206-      <span class="peach-chirp-chip-text">{label()}</span>207-    </div>208-  );209-};210-211-const PairingFooter: Component<{ phase: PairingPhase }> = (props) => (212-  <footer class="peach-chirp-foot">213-    <div class="peach-chirp-foot-divider" />214-    <div class="peach-chirp-foot-text">215-      {props.phase === "paired"216-        ? "Long-term sync keys exchanged over Noise NK. Stored locally on both devices."217-        : "Chirp Sync stays on your local network. No relay servers, no cloud accounts — just your devices in the same room."}218-    </div>219-  </footer>220-);221-222-// ---------------------------------------------------------------------------223-// Copy + action area (animates on phase change via key remount)224-// ---------------------------------------------------------------------------225-226-const PairingCopy: Component<{ progress: PairingProgress }> = (props) => {227-  // Remount on phase change so the copy-in keyframe replays.228-  const phaseKey = createMemo(() => props.progress.phase);229-  return (230-    <div class="peach-chirp-copy-host">231-      <Show when={phaseKey()} keyed>232-        {(_p) => (233-          <div class={`peach-chirp-copy peach-chirp-copy-${props.progress.phase}`}>234-            <div class="peach-chirp-headline">{headline(props.progress)}</div>235-            <div class="peach-chirp-sub">{subtext(props.progress)}</div>236-          </div>237-        )}238-      </Show>239-    </div>240-  );241-};242-243-function headline(p: PairingProgress): string {244-  switch (p.phase) {245-    case "idle":246-    case "listening":247-    case "chirping": return "Hold your phone near your laptop";248-    case "handshake": return "Pairing…";249-    case "paired": return "Paired!";250-    case "timeout": return "Couldn’t find your phone";251-    case "mic_denied": return "Microphone access needed";252-    case "version_mismatch": return "Update Peach on both devices";253-    case "handshake_failed": return "Something went wrong";254-  }255-}256-257-function subtext(p: PairingProgress): string {258-  switch (p.phase) {259-    case "idle":260-    case "listening":261-    case "chirping": return "Peach will pair automatically when your phone is close.";262-    case "handshake": return "Listening for the chirp.";263-    case "paired": return p.peerDeviceName ? `Connected to ${p.peerDeviceName}` : "Connected";264-    case "timeout": return "Make sure Peach is open on your phone and try again.";265-    case "mic_denied": return "Peach uses your microphone to pair. No audio is recorded or stored.";266-    case "version_mismatch": return "The other device is running a different protocol version.";267-    case "handshake_failed": return p.errorMessage ?? "Please try again.";268-  }269-}270-271-const PairingAction: Component<{272-  progress: PairingProgress;273-  onCancel: () => void;274-  onRetry: () => void;275-  onDone: () => void;276-}> = (props) => {277-  const phase = () => props.progress.phase;278-  return (279-    <div class="peach-chirp-action">280-      <Show when={phase() === "listening" || phase() === "chirping" || phase() === "idle"}>281-        <div class="peach-chirp-action-waiting">282-          <button class="peach-chirp-cancel peach-btn peach-btn-ghost" onClick={() => props.onCancel()}>283-            Cancel284-          </button>285-          <div class="peach-chirp-listening">286-            <span class="peach-chirp-listen-dot" />287-            <span class="peach-chirp-listen-dot" />288-            <span class="peach-chirp-listen-dot" />289-            <span class="peach-chirp-listen-label">listening</span>290-          </div>291-        </div>292-      </Show>293-294-      <Show when={phase() === "handshake"}>295-        <div class="peach-chirp-action-handshake">296-          <div class="peach-chirp-meter">297-            <span class="peach-chirp-meter-bar" />298-          </div>299-          <span class="peach-chirp-meter-label">handshake · noise NK</span>300-        </div>301-      </Show>302-303-      <Show when={phase() === "paired"}>304-        <div class="peach-chirp-action-paired">305-          <div class="peach-chirp-device-confirm">306-            <span class="peach-chirp-device-confirm-glyph" aria-hidden="true">307-              <svg viewBox="0 0 16 16" width="14" height="14">308-                <rect x="3" y="2" width="10" height="13" rx="2" fill="none" stroke="currentColor" stroke-width="1.2" />309-                <circle cx="8" cy="12" r="0.9" fill="currentColor" />310-              </svg>311-            </span>312-            <span class="peach-chirp-device-confirm-name">313-              {props.progress.peerDeviceName ?? "Paired device"}314-            </span>315-            <span class="peach-chirp-device-confirm-trust">direct</span>316-          </div>317-          <button class="peach-chirp-done peach-btn peach-btn-primary" onClick={() => props.onDone()}>318-            Done319-          </button>320-        </div>321-      </Show>322-323-      <Show when={isRetryable(phase())}>324-        <button class="peach-chirp-retry peach-btn peach-btn-primary" onClick={() => props.onRetry()}>325-          Try again326-        </button>327-      </Show>328-    </div>329-  );330-};331-332-function isRetryable(phase: PairingPhase): boolean {333-  return phase === "timeout" || phase === "handshake_failed" || phase === "version_mismatch" || phase === "mic_denied";334-}335-336-// ---------------------------------------------------------------------------337-// Stage children — chirp arcs, flight wave, connection dot338-// ---------------------------------------------------------------------------339-340-const ChirpArcs: Component<{ direction: "left" | "right" }> = (props) => (341-  <div class={`peach-chirp-arcs peach-chirp-arcs-${props.direction}`}>342-    <span class="peach-chirp-arc" style={{ "animation-delay": "0ms" }} />343-    <span class="peach-chirp-arc" style={{ "animation-delay": "560ms" }} />344-    <span class="peach-chirp-arc" style={{ "animation-delay": "1120ms" }} />345-  </div>346-);347-348-const FlightWave: Component<{ from: "desktop" | "mobile"; visible: boolean }> = (props) => (349-  <svg350-    class="peach-chirp-flight-wave"351-    classList={{ "is-visible": props.visible }}352-    data-from={props.from}353-    viewBox="0 0 220 80"354-    preserveAspectRatio="none"355-  >356-    <path class="peach-chirp-flight-path" d="M 10 50 Q 60 5, 110 40 T 210 30" />357-    <path class="peach-chirp-flight-path-2" d="M 10 55 Q 60 14, 110 45 T 210 35" />358-  </svg>359-);360-361-const ConnectionDot: Component<{ visible: boolean }> = (props) => (362-  <div class="peach-chirp-dot" classList={{ "is-visible": props.visible }}>363-    <span class="peach-chirp-dot-glow" />364-    <span class="peach-chirp-dot-core" />365-    <svg class="peach-chirp-dot-ring" viewBox="0 0 60 60">366-      <circle cx="30" cy="30" r="22" fill="none" stroke-width="1" stroke-dasharray="2 4" />367-    </svg>368-  </div>369-);370-371-// 8-mote helper. createMemo over a fixed array is overkill — index is enough.372-const For8: Component<{ children: (i: number) => unknown }> = (props) => (373-  <>374-    {props.children(0)}375-    {props.children(1)}376-    {props.children(2)}377-    {props.children(3)}378-    {props.children(4)}379-    {props.children(5)}380-    {props.children(6)}381-    {props.children(7)}382-  </>383-);384-385-// ---------------------------------------------------------------------------386-// Birds — Finch (desktop) and Swallow (mobile)387-// Ported from the Chirp Pairing prototype. Bird coral/apricot hexes are388-// illustration colours, not brand accent — they live alongside the brand389-// peach exception in §3.1.390-// ---------------------------------------------------------------------------8+import { Component } from "solid-js";3919 39210 const FINCH = {39311   hi: "#f4a6b6", mid: "#dc8294", lo: "#a85870", shade: "#7a3e54",

view file

src/popup/views/ChirpSync.tsx+21 −288
@@ -1,19 +1,17 @@11 // ChirpSync — the pairing surface, opened from the Settings → Sync card.22 //33 // Settings owns the sync "hub" (paired-device list, revoke, sync-now); this4-// view is purely the pairing flow, entered straight into a method via5-// `initialPair` and returning to Settings (`onBack`) when done or cancelled:4+// view is purely the pairing flow, returning to Settings (`onBack`) when done5+// or cancelled: the user types the pairing code their phone displays, the6+// browser dials out to the address inside it, and the NK handshake + SAS run7+// from there (enter code → connecting → SAS → paired).68 //7-//   - Locked behind Pro (free user) — soft upsell card, "Learn more" routes8-//     to the License section.9-//   - Code — the primary path: the user types the pairing code their phone10-//     displays, the browser dials out to the address inside it, and the NK11-//     handshake + SAS run from there (enter code → SAS → paired).12-//   - Chirp — the superseded audio-pairing flow. No entry point reaches it any13-//     more; the code below is still here pending the removal decision.9+// There is one path because there can only be one: the browser cannot listen,10+// so it is always the side that dials. The audio-chirp flow this file is named11+// after has been removed.1412 //15-// The NK pairing runs in the background (desktop-pairing.ts) and reaches this16-// view via the PAIR_*/DESKTOP_PAIR_* messages.13+// The pairing itself runs in the background (desktop-pairing.ts) and reaches14+// this view via the PAIR_WITH_DESKTOP / DESKTOP_PAIR_* messages.1715 1816 import {1917   Component,@@ -24,44 +22,25 @@2422   onMount,2523 } from "solid-js";2624 import { sendMessage } from "../../shared/bridge";27-import type {28-  PairedDevice,29-  PairingProgress,30-} from "../../shared/sync-types";31-import type { AdoptRequest } from "../../shared/chirp-adopt";32-import { ChirpPairingFlow, DesktopBird, MobileBird } from "../components/ChirpPairingFlow";33-import { AdoptionConsentModal } from "../components/AdoptionConsentModal";34-import {35-  ChirpPairingFailure,36-  runChirpPairing,37-  type ChirpPairingDeps,38-  type PairingRole,39-} from "../chirp-pair";25+import type { PairedDevice } from "../../shared/sync-types";26+import { DesktopBird, MobileBird } from "../components/ChirpPairingFlow";4027 import { formatPairingCode, parsePairingCode } from "../../shared/pairing-code";41-import { getLocalSubnet } from "../../shared/local-network";42-import { isNativePlatform } from "../../shared/native";43-import { openCapacitorPairingServer } from "../chirp-pair-capacitor";4428 4529 export interface ChirpSyncViewProps {4630   isPro: boolean;4731   onBack(): void;4832   onLearnPro(): void;4933   /**50-   * Open straight into a pairing path, skipping the rest screen — set when the51-   * user taps "Chirp" or "Code" on the Settings sync card. Read once on mount.34+   * Set when the user taps the pairing option on the Settings sync card. With35+   * no method there is nothing to do here, so the view returns to Settings.36+   * Read once on mount.5237    */53-  initialPair?: "chirp" | "desktop" | null;38+  initialPair?: "desktop" | null;5439   /** Called after a peer's Pro license is activated, so the app re-reads Pro status. */5540   onProChanged?: () => void;5641 }5742 58-// Which pairing path the user picked, passed in via `initialPair`. The birds59-// animate the same way on both paths.60-type PairKind = "chirp" | "desktop" | null;61-62-// Desktop helper (peach-sync) code-pairing phases. The SAS and success63-// screens are shared with the chirp path; "enter"/"connecting" are unique to64-// the typed-code flow. Mirrors the background DesktopPairingError union.43+// Code-pairing phases. Mirrors the background DesktopPairingError union.6544 type DesktopPhase = "enter" | "connecting" | "sas" | "paired" | "error";6645 type DesktopPairingError =6746   | "invalid_code"@@ -106,29 +85,10 @@10685 }10786 10887 export const ChirpSyncView: Component<ChirpSyncViewProps> = (props) => {109-  const [pairing, setPairing] = createSignal<PairingProgress>({ phase: "idle" });110-  // Out-of-band pairing verification: the 6-digit code both devices derive111-  // from the shared handshake; the user confirms it matches before the pair112-  // is persisted (audit HIGH — pairing initiator authentication).113-  const [sasCode, setSasCode] = createSignal<string | null>(null);114-  const [sasResolve, setSasResolve] = createSignal<((ok: boolean) => void) | null>(null);115-  const [adoptionConsent, setAdoptionConsent] = createSignal<{116-    request: AdoptRequest;117-    sas: string;118-    resolve: (ok: boolean) => void;119-  } | null>(null);120-121-  function resolveSAS(ok: boolean) {122-    const r = sasResolve();123-    setSasCode(null);124-    setSasResolve(null);125-    r?.(ok);126-  }127-128-  // --- Desktop helper (peach-sync) code pairing -------------------------12988   // The whole NK pairing runs in the background; this view drives the UI and130-  // the SAS confirmation round-trip via the four PAIR_*/DESKTOP_PAIR_* messages.131-  const [pairKind, setPairKind] = createSignal<PairKind>(props.initialPair ?? null);89+  // the out-of-band SAS confirmation round-trip via the PAIR_WITH_DESKTOP /90+  // DESKTOP_PAIR_* messages. The SAS gate is what authenticates the peer91+  // (audit HIGH — pairing initiator authentication), not the pairing token.13292   const [desktopPhase, setDesktopPhase] = createSignal<DesktopPhase>("enter");13393   const [desktopCode, setDesktopCode] = createSignal("");13494   const [desktopSas, setDesktopSas] = createSignal<string | null>(null);@@ -149,14 +109,8 @@149109     setDesktopDevice(undefined);150110     setDesktopBusy(false);151111     setDesktopPhase("enter");152-    setPairKind("desktop");153112   }154113 155-  function startChirpFlow(): void {156-    setPairKind("chirp");157-    void handleStartPairing();158-  }159-160114   function onDesktopCodeInput(value: string): void {161115     const cleaned = value.toUpperCase().replace(/[^0-9A-Z]/g, "").slice(0, 32);162116     // Group once the input outgrows a 6-digit helper code, so a pairing code@@ -213,8 +167,7 @@213167   onMount(() => {214168     // Opened straight into a pairing path from the Settings sync card. With no215169     // method there's nothing to do here — return to Settings (the hub).216-    if (props.initialPair === "chirp") startChirpFlow();217-    else if (props.initialPair === "desktop") startDesktopFlow();170+    if (props.initialPair === "desktop") startDesktopFlow();218171     else props.onBack();219172 220173     // Desktop pairing pushes from the background (bg → popup). The whole NK@@ -222,7 +175,6 @@222175     // result arrive here as runtime messages.223176     if (typeof chrome !== "undefined" && chrome.runtime?.onMessage) {224177       const handler = (msg: { type?: string; payload?: unknown }) => {225-        if (pairKind() !== "desktop") return;226178         if (msg?.type === "DESKTOP_PAIR_SAS") {227179           const p = msg.payload as { sas?: string } | undefined;228180           if (typeof p?.sas === "string") { setDesktopSas(p.sas); setDesktopPhase("sas"); }@@ -248,148 +200,6 @@248200     });249201   });250202 251-  async function handleStartPairing(): Promise<void> {252-    // audioCapture is declared as optional_permissions in the manifest.253-    // Chrome will silently deny getUserMedia unless we request it here,254-    // during a user gesture. Firefox ignores this API but getUserMedia works255-    // under its existing host permission grant.256-    if (typeof chrome !== "undefined" && chrome.permissions?.request) {257-      const granted = await chrome.permissions.request({258-        permissions: ["audioCapture" as chrome.runtime.ManifestPermissions],259-      });260-      if (!granted) {261-        setPairing({ phase: "mic_denied" });262-        return;263-      }264-    }265-266-    setPairing({ phase: "idle" });267-268-    const deps: ChirpPairingDeps = {269-      onProgress: (p) => setPairing(p),270-      fetchLocalIdentity: async () => {271-        const res = await sendMessage({ type: "CHIRP_GET_LOCAL_IDENTITY" });272-        const r = res as { ok: boolean; data?: Parameters<ChirpPairingDeps["onProgress"]>[0] & object; error?: string };273-        if (!r.ok || !r.data) throw new Error(r.error ?? "identity_unavailable");274-        // The handler returns a LocalIdentity; we trust the background's shape.275-        return r.data as unknown as Awaited<ReturnType<ChirpPairingDeps["fetchLocalIdentity"]>>;276-      },277-      completePairing: async (paired) => {278-        const res = await sendMessage({279-          type: "CHIRP_COMPLETE_PAIRING",280-          payload: { paired },281-        });282-        const r = res as { ok: boolean; error?: string };283-        if (!r.ok) throw new Error(r.error ?? "complete_pairing_failed");284-      },285-      confirmSAS: (sas) =>286-        new Promise<boolean>((resolve) => {287-          setSasResolve(() => resolve);288-          setSasCode(sas);289-        }),290-      // Receive Pro from an already-Pro peer (signature-verified in the bg).291-      // Skip if we're already Pro so we keep our own license.292-      activatePeerLicense: async (license) => {293-        if (props.isPro) return;294-        const res = (await sendMessage({295-          type: "ACTIVATE_LICENSE",296-          payload: { key: license },297-        })) as { ok: boolean; data?: { valid?: boolean; plan?: string } };298-        if (res.ok && res.data?.valid && res.data?.plan === "pro") props.onProChanged?.();299-      },300-      grantAdoptionConsent: (request, sas) =>301-        new Promise<boolean>((resolve) => {302-          setAdoptionConsent({ request, sas, resolve });303-        }),304-      fetchAdoptionMaterial: async () => {305-        const res = await sendMessage({ type: "CHIRP_GET_ADOPTION_MATERIAL" });306-        const r = res as {307-          ok: boolean;308-          data?: { rootKeyHex?: string; vaultId?: string; license?: string };309-          error?: string;310-        };311-        if (!r.ok || typeof r.data?.rootKeyHex !== "string" || typeof r.data?.vaultId !== "string") {312-          throw new Error(r.error ?? "adoption_material_unavailable");313-        }314-        return {315-          rootKeyHex: r.data.rootKeyHex,316-          vaultId: r.data.vaultId,317-          license: r.data.license,318-        };319-      },320-      getLocalSubnet,321-      openPairingTransport: async (endpoint) => {322-        // Native WebSocket transport for the pairing-mode peer scan. The323-        // chirp-pair orchestrator only uses send/receive/close, so a thin324-        // adapter over the platform WebSocket suffices.325-        return new Promise((resolve, reject) => {326-          let ws: WebSocket;327-          try {328-            ws = new WebSocket(`ws://${endpoint.ip}:${endpoint.port}/`);329-          } catch (err) {330-            reject(err);331-            return;332-          }333-          const inbox: Uint8Array[] = [];334-          const waiters: Array<(b: Uint8Array) => void> = [];335-          let closed = false;336-          ws.binaryType = "arraybuffer";337-          const timer = setTimeout(() => {338-            try { ws.close(); } catch { /* ignore */ }339-            reject(new Error("pairing_transport_timeout"));340-          }, 2_000);341-          ws.onopen = () => {342-            clearTimeout(timer);343-            resolve({344-              async send(data: Uint8Array) {345-                ws.send(data as Uint8Array<ArrayBuffer>);346-              },347-              receive(): Promise<Uint8Array> {348-                if (closed) return Promise.reject(new Error("closed"));349-                const v = inbox.shift();350-                if (v) return Promise.resolve(v);351-                return new Promise<Uint8Array>((res) => waiters.push(res));352-              },353-              async close() {354-                try { ws.close(); } catch { /* ignore */ }355-              },356-            });357-          };358-          ws.onmessage = (ev) => {359-            if (!(ev.data instanceof ArrayBuffer)) return;360-            const bytes = new Uint8Array(ev.data);361-            const w = waiters.shift();362-            if (w) w(bytes); else inbox.push(bytes);363-          };364-          ws.onerror = () => {365-            clearTimeout(timer);366-            reject(new Error("pairing_transport_error"));367-          };368-          ws.onclose = () => {369-            closed = true;370-            while (waiters.length > 0) waiters.shift()!(new Uint8Array(0));371-          };372-        });373-      },374-    };375-376-    const role: PairingRole = isNativePlatform() ? "initiator" : "responder";377-    if (role === "initiator") deps.openPairingServer = openCapacitorPairingServer;378-379-    try {380-      await runChirpPairing(deps, role);381-      // Success surfaces in `pairing()` as the "paired" phase; ChirpPairingFlow382-      // shows it and onDismiss returns to Settings.383-    } catch (err) {384-      if (err instanceof ChirpPairingFailure) {385-        // The orchestrator already pushed the terminal phase into `pairing()`;386-        // the inline error copy renders. The user can tap "Try again"/"Cancel".387-        return;388-      }389-      setPairing({ phase: "handshake_failed", errorMessage: (err as Error).message });390-    }391-  }392-393203   // --- Sub-views --------------------------------------------------394204 395205   const backArrow = (@@ -526,84 +336,7 @@526336     </div>527337   );528338 529-  const chirpPairingView = () => (530-    <Show531-      when={sasCode()}532-      fallback={533-        <ChirpPairingFlow534-          progress={pairing()}535-          onCancel={() => props.onBack()}536-          onRetry={() => void handleStartPairing()}537-          onDismiss={() => props.onBack()}538-        />539-      }540-    >541-      {/* Out-of-band verification gate — pairing is NOT persisted until the542-          user confirms this code matches the one on the other device. */}543-      <div544-        class="chirp-sas-verify"545-        style="display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 18px;text-align:center;"546-      >547-        <div style="font-size:14px;font-weight:600;color:var(--peach-text);">Verify it's really your device</div>548-        <div style="font-size:11.5px;line-height:1.45;color:var(--peach-text-muted);max-width:240px;">549-          Confirm this code matches the one shown on your other device before pairing.550-        </div>551-        <div style="font-size:30px;font-weight:700;letter-spacing:0.32em;font-family:'IBM Plex Mono',monospace;color:var(--peach-accent);padding:6px 0 2px;">552-          {sasCode()}553-        </div>554-        <div style="display:flex;flex-direction:column;gap:8px;width:100%;max-width:240px;">555-          <button556-            class="peach-btn peach-btn--accent peach-btn--md"557-            type="button"558-            onClick={() => resolveSAS(true)}559-          >560-            codes match — pair561-          </button>562-          <button563-            class="peach-btn peach-btn--ghost peach-btn--md"564-            type="button"565-            onClick={() => resolveSAS(false)}566-          >567-            they don't match568-          </button>569-        </div>570-      </div>571-    </Show>572-  );573-574-  // Pairing surface dispatch: the user already chose chirp vs. code on the rest575-  // screen (or the sync card), so there's no method-select step.576-  const pairingView = () => (577-    <Show when={pairKind() === "desktop"} fallback={chirpPairingView()}>578-      {desktopView()}579-    </Show>580-  );581-582339   // Sync is no longer Pro-gated: anyone may attempt to pair so a free device can583340   // receive Pro from an already-Pro peer. The upsell lives on the Settings card.584-  return (585-    <>586-      {pairingView()}587-      <Show when={adoptionConsent()}>588-        {(pending) => (589-          <AdoptionConsentModal590-            receiverDeviceName={pending().request.device_name}591-            sas={pending().sas}592-            verifyMasterPassword={async (password) => {593-              const res = await sendMessage({ type: "VERIFY_MAIN_PASSWORD", payload: { password } });594-              return Boolean((res as { ok: boolean; data?: boolean }).ok && (res as { data?: boolean }).data);595-            }}596-            onCancel={() => {597-              pending().resolve(false);598-              setAdoptionConsent(null);599-            }}600-            onApprove={async () => {601-              pending().resolve(true);602-              setAdoptionConsent(null);603-            }}604-          />605-        )}606-      </Show>607-    </>608-  );341+  return desktopView();609342 };

view file

src/popup/views/Settings.tsx+1 −1
@@ -61,7 +61,7 @@6161   onExport: () => void;6262   onBreachCheckDone: () => void;6363   onOpenHealth?: () => void;64-  onOpenSyncPairing?: (method?: "chirp" | "desktop") => void;64+  onOpenSyncPairing?: (method?: "desktop") => void;6565   onOpenFileImport?: () => void;6666   onOpenPanic?: () => void;6767   onOpenCodexGenerate?: () => void;

view file

src/shared/chirp-pairing.test.ts+0 −191
@@ -1,191 +0,0 @@1-import { afterEach, beforeEach, describe, expect, it } from "vitest";2-import { CHIRP_PAYLOAD_LENGTH, DEVICE_TYPE_ANDROID, DEVICE_TYPE_EXTENSION } from "./sync-types";3-import { encodeChirpPayload } from "./chirp-payload";4-import {5-  ChirpExchangeError,6-  createPairingAttempt,7-  runChirpExchange,8-  type AudioIO,9-} from "./chirp-pairing";10-import { __setGgwaveModuleForTest } from "./ggwave";11-12-function fakeGgwaveThatPassesThroughBytes() {13-  // Synthesize a ggwave that round-trips: encode → push raw bytes into the14-  // waveform, decode → return any waveform whose first 64 samples decode as15-  // a payload. This keeps the chirp-pairing logic in scope without needing16-  // a real FSK codec.17-  return {18-    ProtocolId: { GGWAVE_PROTOCOL_ULTRASOUND_FASTEST: 5 },19-    GGWAVE_OPERATING_MODE_RX: 1,20-    GGWAVE_OPERATING_MODE_TX: 2,21-    GGWAVE_OPERATING_MODE_RX_AND_TX: 3,22-    getDefaultParameters() {23-      return { sampleRateInp: 48000, sampleRateOut: 48000, samplesPerFrame: 1024, soundMarkerThreshold: 3, payloadLength: -1, operatingMode: 0 };24-    },25-    init() { return 1; },26-    free() { /* no-op */ },27-    encode(_instance: number, data: Uint8Array | string, _proto: number, _vol: number) {28-      const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;29-      // Pack bytes as float samples in the first N positions; the rest30-      // padded with sentinel value 99 so a partial read doesn't match.31-      const out = new Float32Array(Math.max(bytes.length, 128));32-      for (let i = 0; i < bytes.length; i++) out[i] = bytes[i] / 255;33-      for (let i = bytes.length; i < out.length; i++) out[i] = 99;34-      return out;35-    },36-    decode(_instance: number, samples: Float32Array | Int16Array) {37-      if (samples.length < CHIRP_PAYLOAD_LENGTH) return null;38-      // Read first 64 samples back as bytes if they look like our encoding.39-      // Sentinel 99 means "no chirp here".40-      const buf = new Uint8Array(CHIRP_PAYLOAD_LENGTH);41-      for (let i = 0; i < CHIRP_PAYLOAD_LENGTH; i++) {42-        const v = (samples as Float32Array)[i];43-        if (v === 99) return null;44-        buf[i] = Math.round(v * 255);45-      }46-      return buf;47-    },48-  };49-}50-51-function fakeIO(): AudioIO & {52-  injectIncoming(samples: Float32Array): void;53-  playedCount(): number;54-  closed(): boolean;55-} {56-  let pending = new Float32Array(0);57-  let playCount = 0;58-  let isClosed = false;59-  return {60-    sampleRate: 48000,61-    async play(_waveform: Float32Array) {62-      playCount++;63-    },64-    async pullSamples() {65-      const out = pending;66-      pending = new Float32Array(0);67-      return out;68-    },69-    async close() {70-      isClosed = true;71-    },72-    injectIncoming(samples: Float32Array) {73-      const next = new Float32Array(pending.length + samples.length);74-      next.set(pending, 0);75-      next.set(samples, pending.length);76-      pending = next;77-    },78-    playedCount: () => playCount,79-    closed: () => isClosed,80-  };81-}82-83-describe("createPairingAttempt", () => {84-  it("rolls fresh ephemerals and a 16-byte nonce each call", () => {85-    const a = createPairingAttempt(DEVICE_TYPE_EXTENSION);86-    const b = createPairingAttempt(DEVICE_TYPE_EXTENSION);87-    expect(a.ephemeralPublicKey).not.toEqual(b.ephemeralPublicKey);88-    expect(a.sessionNonce.length).toBe(16);89-    expect(a.outgoingPayload.ephemeralPublicKey).toEqual(a.ephemeralPublicKey);90-    expect(a.outgoingPayload.deviceType).toBe(DEVICE_TYPE_EXTENSION);91-  });92-});93-94-describe("runChirpExchange", () => {95-  beforeEach(() => __setGgwaveModuleForTest(fakeGgwaveThatPassesThroughBytes()));96-  afterEach(() => __setGgwaveModuleForTest(null));97-98-  it("returns the remote payload when a valid chirp is received", async () => {99-    const io = fakeIO();100-    const ours = createPairingAttempt(DEVICE_TYPE_EXTENSION);101-    const theirs = createPairingAttempt(DEVICE_TYPE_ANDROID);102-    const theirBytes = encodeChirpPayload(theirs.outgoingPayload);103-    // Convert the 64 bytes into the encoded waveform shape our fake produces.104-    const theirWave = new Float32Array(128);105-    for (let i = 0; i < theirBytes.length; i++) theirWave[i] = theirBytes[i] / 255;106-    for (let i = theirBytes.length; i < theirWave.length; i++) theirWave[i] = 99;107-    io.injectIncoming(theirWave);108-109-    const result = await runChirpExchange(io, { attempt: ours, timeoutMs: 2000 });110-    expect(result.remoteChirp.ephemeralPublicKey).toEqual(theirs.ephemeralPublicKey);111-    expect(result.remoteChirp.sessionNonce).toEqual(theirs.sessionNonce);112-    expect(result.remoteChirp.deviceType).toBe(DEVICE_TYPE_ANDROID);113-    expect(io.closed()).toBe(true);114-  });115-116-  it("ignores echo of our own chirp", async () => {117-    const io = fakeIO();118-    const ours = createPairingAttempt(DEVICE_TYPE_EXTENSION);119-    const ourBytes = encodeChirpPayload(ours.outgoingPayload);120-    const ourWave = new Float32Array(128);121-    for (let i = 0; i < ourBytes.length; i++) ourWave[i] = ourBytes[i] / 255;122-    for (let i = ourBytes.length; i < ourWave.length; i++) ourWave[i] = 99;123-    // Inject our own chirp — should be ignored124-    io.injectIncoming(ourWave);125-126-    // After 200ms inject a real peer chirp127-    const theirs = createPairingAttempt(DEVICE_TYPE_ANDROID);128-    const theirBytes = encodeChirpPayload(theirs.outgoingPayload);129-    const theirWave = new Float32Array(128);130-    for (let i = 0; i < theirBytes.length; i++) theirWave[i] = theirBytes[i] / 255;131-    for (let i = theirBytes.length; i < theirWave.length; i++) theirWave[i] = 99;132-    setTimeout(() => io.injectIncoming(theirWave), 150);133-134-    const result = await runChirpExchange(io, { attempt: ours, timeoutMs: 3000 });135-    expect(result.remoteChirp.ephemeralPublicKey).toEqual(theirs.ephemeralPublicKey);136-  });137-138-  it("times out after the configured deadline when no chirp arrives", async () => {139-    const io = fakeIO();140-    const ours = createPairingAttempt(DEVICE_TYPE_EXTENSION);141-    await expect(runChirpExchange(io, { attempt: ours, timeoutMs: 200 })).rejects.toMatchObject({142-      kind: "timeout",143-    });144-    expect(io.closed()).toBe(true);145-  });146-147-  it("raises version_mismatch on a peer with a different protocol version", async () => {148-    const io = fakeIO();149-    const ours = createPairingAttempt(DEVICE_TYPE_EXTENSION);150-    const theirs = createPairingAttempt(DEVICE_TYPE_ANDROID);151-    // Mint a chirp with a future protocol version, then re-stamp the CRC152-    const tweaked = { ...theirs.outgoingPayload, version: 0x02 };153-    const bytes = encodeChirpPayload({154-      ...tweaked,155-      // encodeChirpPayload writes a fresh CRC32 over the new bytes156-    });157-    const wave = new Float32Array(128);158-    for (let i = 0; i < bytes.length; i++) wave[i] = bytes[i] / 255;159-    for (let i = bytes.length; i < wave.length; i++) wave[i] = 99;160-    io.injectIncoming(wave);161-162-    await expect(runChirpExchange(io, { attempt: ours, timeoutMs: 2000 })).rejects.toMatchObject({163-      kind: "version_mismatch",164-    });165-  });166-167-  it("ignores noise chunks (no chirp detected) and keeps listening", async () => {168-    const io = fakeIO();169-    const ours = createPairingAttempt(DEVICE_TYPE_EXTENSION);170-    // Push pure noise — fake decoder returns null because samples have value 99171-    io.injectIncoming(new Float32Array(256).fill(99));172-173-    const theirs = createPairingAttempt(DEVICE_TYPE_ANDROID);174-    const theirBytes = encodeChirpPayload(theirs.outgoingPayload);175-    const theirWave = new Float32Array(128);176-    for (let i = 0; i < theirBytes.length; i++) theirWave[i] = theirBytes[i] / 255;177-    for (let i = theirBytes.length; i < theirWave.length; i++) theirWave[i] = 99;178-    setTimeout(() => io.injectIncoming(theirWave), 100);179-180-    const result = await runChirpExchange(io, { attempt: ours, timeoutMs: 3000 });181-    expect(result.remoteChirp.ephemeralPublicKey).toEqual(theirs.ephemeralPublicKey);182-  });183-});184-185-describe("ChirpExchangeError", () => {186-  it("carries a kind tag", () => {187-    const err = new ChirpExchangeError("timeout");188-    expect(err.kind).toBe("timeout");189-    expect(err).toBeInstanceOf(Error);190-  });191-});

view file at the parent

src/shared/chirp-pairing.ts+0 −291
@@ -1,291 +0,0 @@1-// Audio pairing driver — orchestrates the two-chirp exchange that2-// bootstraps trust between two devices in the same room.3-//4-// Both devices simultaneously:5-//   - play their chirp on a 4-second loop6-//   - listen to the microphone, attempting to decode an incoming chirp7-//8-// Once each side has decoded the OTHER side's PEAC chirp, the pairing9-// transitions to its post-chirp phase (Noise NK over WebSocket — driven10-// by the coordinator in `chirp-pairing-handshake.ts`, not by this module).11-//12-// This module is deliberately transport-agnostic at its boundaries: it13-// owns the Web Audio + getUserMedia layer and nothing else. The chirp14-// produced here is bit-identical with what the Android `ggwave-android`15-// JNI binding produces — the ggwave-fastest ultrasonic protocol is the16-// shared envelope.17-//18-// Replay protection note: the `sessionNonce` in the outgoing chirp is19-// distinct on every pairing attempt (`createPairingAttempt` rolls a fresh20-// 16-byte random nonce); a captured chirp replayed on retry produces a21-// nonce mismatch on the second-leg key exchange and is rejected.22-23-import { generateKeyPair } from "@stablelib/x25519";24-import { decodeChirpPayload, ChirpDecodeError } from "./chirp-payload";25-import { createChirpCodec } from "./ggwave";26-import {27-  CHIRP_PROTOCOL_VERSION,28-  type ChirpPayload,29-  type DeviceTypeByte,30-  CHIRP_PAYLOAD_LENGTH,31-} from "./sync-types";32-import { encodeChirpPayload } from "./chirp-payload";33-34-const CHIRP_LOOP_INTERVAL_MS = 4_000;35-const DEFAULT_TIMEOUT_MS = 90_000;36-const LISTEN_FRAME_SAMPLES = 4096;37-38-export interface PairingAttempt {39-  ephemeralPrivateKey: Uint8Array; // X25519 — never leaves this module40-  ephemeralPublicKey: Uint8Array;41-  sessionNonce: Uint8Array; // 16 bytes42-  outgoingPayload: ChirpPayload;43-}44-45-export function createPairingAttempt(deviceType: DeviceTypeByte): PairingAttempt {46-  const kp = generateKeyPair();47-  const nonce = new Uint8Array(16);48-  cryptoFill(nonce);49-  return {50-    ephemeralPrivateKey: kp.secretKey,51-    ephemeralPublicKey: kp.publicKey,52-    sessionNonce: nonce,53-    outgoingPayload: {54-      version: CHIRP_PROTOCOL_VERSION,55-      deviceType,56-      ephemeralPublicKey: kp.publicKey,57-      sessionNonce: nonce,58-    },59-  };60-}61-62-function cryptoFill(buf: Uint8Array): void {63-  if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {64-    crypto.getRandomValues(buf as Uint8Array<ArrayBuffer>);65-    return;66-  }67-  // SAFETY: every supported runtime (MV3 service worker, popup page,68-  // Capacitor WebView) exposes globalThis.crypto.getRandomValues. The69-  // fallback is therefore only ever hit in a broken environment, and70-  // failing closed is the right behaviour.71-  throw new Error("crypto.getRandomValues unavailable");72-}73-74-export interface ChirpExchangeResult {75-  remoteChirp: ChirpPayload;76-}77-78-export interface ChirpExchangeOptions {79-  attempt: PairingAttempt;80-  timeoutMs?: number;81-  /** Notified each time we abandon a malformed/non-PEAC frame. */82-  onDiscard?: (reason: ChirpDecodeError["kind"]) => void;83-  /** Notified each time we replay our own chirp. */84-  onChirpPlayed?: (count: number) => void;85-}86-87-/**88- * Audio I/O abstraction. Wired to real Web Audio + getUserMedia in89- * `runChirpExchange`; tests pass a deterministic fake so the play/listen90- * choreography can be exercised in jsdom.91- */92-export interface AudioIO {93-  sampleRate: number;94-  play(waveform: Float32Array): Promise<void>;95-  pullSamples(): Promise<Float32Array>;96-  close(): Promise<void>;97-}98-99-/**100- * Run the two-chirp exchange against a provided `AudioIO`. Returns once we101- * have decoded a valid PEAC chirp from a peer; throws on timeout, mic102- * failure, or a version-mismatch chirp from a too-new peer.103- *104- * The function continues chirping while it listens — the two devices105- * always overlap; ggwave's frequency sub-bands inside the ultrasonic106- * range let both sides transmit and receive simultaneously.107- */108-export async function runChirpExchange(109-  io: AudioIO,110-  opts: ChirpExchangeOptions,111-): Promise<ChirpExchangeResult> {112-  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;113-  const codec = await createChirpCodec({ sampleRate: io.sampleRate });114-  const ourBytes = encodeChirpPayload(opts.attempt.outgoingPayload);115-  const ourWaveform = codec.encode(ourBytes);116-117-  const deadline = Date.now() + timeoutMs;118-  let playCount = 0;119-  let stopped = false;120-  let wakePlayLoop: (() => void) | null = null;121-122-  function abortPlayLoop() {123-    stopped = true;124-    if (wakePlayLoop) wakePlayLoop();125-  }126-127-  function interruptibleSleep(ms: number): Promise<void> {128-    return new Promise<void>((resolve) => {129-      const t = setTimeout(resolve, ms);130-      wakePlayLoop = () => { clearTimeout(t); resolve(); };131-    });132-  }133-134-  const playLoop = (async () => {135-    while (!stopped && Date.now() < deadline) {136-      try {137-        await io.play(ourWaveform);138-        playCount++;139-        opts.onChirpPlayed?.(playCount);140-      } catch {141-        // A play failure (audio device contention, tab backgrounded) is142-        // not fatal — we keep listening; the peer may already be in range.143-      }144-      if (stopped) break;145-      const remaining = deadline - Date.now();146-      const wait = Math.min(CHIRP_LOOP_INTERVAL_MS, Math.max(0, remaining));147-      if (wait <= 0) break;148-      await interruptibleSleep(wait);149-    }150-  })();151-152-  try {153-    while (Date.now() < deadline && !stopped) {154-      const samples = await io.pullSamples();155-      if (samples.length === 0) {156-        await sleep(50);157-        continue;158-      }159-      const decoded = codec.decode(samples);160-      if (!decoded) continue;161-      if (decoded.length !== CHIRP_PAYLOAD_LENGTH) {162-        opts.onDiscard?.("length");163-        continue;164-      }165-      try {166-        const payload = decodeChirpPayload(decoded);167-        // Ignore an echo of our own chirp — happens routinely in tight168-        // ambient feedback. If the bytes match, drop and keep listening.169-        if (sameBytes(payload.ephemeralPublicKey, opts.attempt.ephemeralPublicKey)) {170-          continue;171-        }172-        if (payload.version !== CHIRP_PROTOCOL_VERSION) {173-          throw new ChirpExchangeError("version_mismatch");174-        }175-        stopped = true;176-        return { remoteChirp: payload };177-      } catch (err) {178-        if (err instanceof ChirpExchangeError) throw err;179-        if (err instanceof ChirpDecodeError) {180-          if (err.kind === "version") {181-            // A well-formed PEAC chirp with a protocol byte we don't speak182-            // is a real version mismatch — bubble it up so the UI can show183-            // the "Update Peach on both devices" copy. CRC validates first,184-            // so this isn't fired by a random RF glitch.185-            throw new ChirpExchangeError("version_mismatch");186-          }187-          opts.onDiscard?.(err.kind);188-          continue;189-        }190-        throw err;191-      }192-    }193-    throw new ChirpExchangeError("timeout");194-  } finally {195-    abortPlayLoop();196-    codec.free();197-    await playLoop.catch(() => undefined);198-    await io.close().catch(() => undefined);199-  }200-}201-202-export class ChirpExchangeError extends Error {203-  readonly kind: "timeout" | "version_mismatch" | "mic_denied" | "audio_unavailable";204-  constructor(kind: ChirpExchangeError["kind"], message?: string) {205-    super(message ?? kind);206-    this.kind = kind;207-  }208-}209-210-function sameBytes(a: Uint8Array, b: Uint8Array): boolean {211-  if (a.length !== b.length) return false;212-  for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;213-  return true;214-}215-216-function sleep(ms: number): Promise<void> {217-  return new Promise((res) => setTimeout(res, ms));218-}219-220-/**221- * Build a `AudioIO` backed by Web Audio + getUserMedia. Allocates an222- * AudioContext (caller must invoke from a user gesture — the click that223- * launched pairing — or playback is suspended on Chrome and Safari).224- *225- * `pullSamples` returns the latest accumulated mono buffer from the mic226- * and resets the internal accumulator. Calling repeatedly yields227- * non-overlapping slices of recent audio; ggwave's marker-based decoder228- * tolerates this chunking.229- */230-export async function openBrowserAudioIO(): Promise<AudioIO> {231-  if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {232-    throw new ChirpExchangeError("audio_unavailable", "getUserMedia not available");233-  }234-  const ctx: AudioContext = new (globalThis.AudioContext ??235-    (globalThis as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext)();236-  let stream: MediaStream;237-  try {238-    stream = await navigator.mediaDevices.getUserMedia({239-      audio: { echoCancellation: false, noiseSuppression: false, autoGainControl: false },240-    });241-  } catch (err) {242-    await ctx.close();243-    throw new ChirpExchangeError("mic_denied", (err as Error).message);244-  }245-  const source = ctx.createMediaStreamSource(stream);246-247-  // ScriptProcessorNode is deprecated but is the only widely available248-  // pull-style API in MV3 service workers and the Firefox popup; AudioWorklet249-  // would be cleaner but requires a separate module file. The buffer size250-  // 4096 corresponds to ~90ms at 48kHz — short enough that pairing feels251-  // responsive, long enough that ggwave's FSK windows align.252-  const processor = ctx.createScriptProcessor(LISTEN_FRAME_SAMPLES, 1, 1);253-  let accumulator: Float32Array = new Float32Array(0);254-255-  processor.onaudioprocess = (ev) => {256-    const input = ev.inputBuffer.getChannelData(0);257-    const next = new Float32Array(accumulator.length + input.length);258-    next.set(accumulator, 0);259-    next.set(input, accumulator.length);260-    accumulator = next;261-  };262-263-  source.connect(processor);264-  processor.connect(ctx.destination);265-266-  return {267-    sampleRate: ctx.sampleRate,268-    async play(waveform: Float32Array): Promise<void> {269-      const buffer = ctx.createBuffer(1, waveform.length, ctx.sampleRate);270-      buffer.copyToChannel(waveform as Float32Array<ArrayBuffer>, 0, 0);271-      const src = ctx.createBufferSource();272-      src.buffer = buffer;273-      src.connect(ctx.destination);274-      await new Promise<void>((resolve) => {275-        src.onended = () => resolve();276-        src.start();277-      });278-    },279-    async pullSamples(): Promise<Float32Array> {280-      const out = accumulator;281-      accumulator = new Float32Array(0);282-      return out;283-    },284-    async close(): Promise<void> {285-      processor.disconnect();286-      source.disconnect();287-      for (const track of stream.getTracks()) track.stop();288-      await ctx.close();289-    },290-  };291-}

view file at the parent

src/shared/chirp-payload.test.ts+0 −106
@@ -1,106 +0,0 @@1-import { describe, expect, it } from "vitest";2-import {3-  CHIRP_MAGIC_BYTES,4-  CHIRP_PAYLOAD_LENGTH,5-  CHIRP_PROTOCOL_VERSION,6-  DEVICE_TYPE_ANDROID,7-  DEVICE_TYPE_EXTENSION,8-} from "./sync-types";9-import {10-  ChirpDecodeError,11-  crc32,12-  decodeChirpPayload,13-  encodeChirpPayload,14-} from "./chirp-payload";15-16-function fixedPayload() {17-  return {18-    version: CHIRP_PROTOCOL_VERSION,19-    deviceType: DEVICE_TYPE_EXTENSION,20-    ephemeralPublicKey: Uint8Array.from(21-      Array.from({ length: 32 }, (_, i) => (i * 7 + 11) & 0xff),22-    ),23-    sessionNonce: Uint8Array.from(24-      Array.from({ length: 16 }, (_, i) => (i * 31 + 1) & 0xff),25-    ),26-  };27-}28-29-describe("crc32", () => {30-  it("matches known IEEE 802.3 vectors", () => {31-    expect(crc32(new Uint8Array())).toBe(0x00000000);32-    expect(crc32(new TextEncoder().encode("123456789"))).toBe(0xcbf43926);33-    expect(crc32(new TextEncoder().encode("a"))).toBe(0xe8b7be43);34-  });35-});36-37-describe("chirp payload codec", () => {38-  it("round-trips identity for a fixed payload", () => {39-    const original = fixedPayload();40-    const bytes = encodeChirpPayload(original);41-    expect(bytes.length).toBe(CHIRP_PAYLOAD_LENGTH);42-    const decoded = decodeChirpPayload(bytes);43-    expect(decoded.version).toBe(original.version);44-    expect(decoded.deviceType).toBe(original.deviceType);45-    expect(decoded.ephemeralPublicKey).toEqual(original.ephemeralPublicKey);46-    expect(decoded.sessionNonce).toEqual(original.sessionNonce);47-  });48-49-  it("writes magic bytes PEAC at offset 0", () => {50-    const bytes = encodeChirpPayload(fixedPayload());51-    expect(bytes.subarray(0, 4)).toEqual(CHIRP_MAGIC_BYTES);52-  });53-54-  it("zero-fills the reserved padding region", () => {55-    const bytes = encodeChirpPayload(fixedPayload());56-    for (let i = 58; i < 64; i++) expect(bytes[i]).toBe(0);57-  });58-59-  it("rejects wrong-length input", () => {60-    expect(() => decodeChirpPayload(new Uint8Array(63))).toThrow(ChirpDecodeError);61-    expect(() => decodeChirpPayload(new Uint8Array(65))).toThrow(ChirpDecodeError);62-  });63-64-  it("rejects bad magic", () => {65-    const bytes = encodeChirpPayload(fixedPayload());66-    bytes[0] = 0;67-    expect(() => decodeChirpPayload(bytes)).toThrow(/magic/);68-  });69-70-  it("rejects unsupported version", () => {71-    const bytes = encodeChirpPayload(fixedPayload());72-    bytes[4] = 0xff;73-    // CRC will also fail, but version is checked first74-    expect(() => decodeChirpPayload(bytes)).toThrow(/version/);75-  });76-77-  it("rejects unknown device type", () => {78-    const bytes = encodeChirpPayload(fixedPayload());79-    bytes[5] = 0x09;80-    expect(() => decodeChirpPayload(bytes)).toThrow(/device type/);81-  });82-83-  it("rejects checksum tamper", () => {84-    const bytes = encodeChirpPayload(fixedPayload());85-    bytes[10] ^= 1; // flip a pubkey bit86-    expect(() => decodeChirpPayload(bytes)).toThrow(/CRC32/);87-  });88-89-  it("rejects non-zero padding", () => {90-    const bytes = encodeChirpPayload(fixedPayload());91-    // Mutate padding then re-patch CRC over the first 54 bytes so that the92-    // padding check is reached rather than failing at CRC first.93-    bytes[63] = 0xab;94-    const fresh = crc32(bytes.subarray(0, 54));95-    bytes[54] = (fresh >>> 24) & 0xff;96-    bytes[55] = (fresh >>> 16) & 0xff;97-    bytes[56] = (fresh >>> 8) & 0xff;98-    bytes[57] = fresh & 0xff;99-    expect(() => decodeChirpPayload(bytes)).toThrow(/padding/);100-  });101-102-  it("encodes android device type byte", () => {103-    const bytes = encodeChirpPayload({ ...fixedPayload(), deviceType: DEVICE_TYPE_ANDROID });104-    expect(bytes[5]).toBe(0x02);105-  });106-});

view file at the parent

src/shared/chirp-payload.ts+0 −139
@@ -1,139 +0,0 @@1-// Audio pairing payload — 64 bytes on the wire.2-//3-//   magic            4   ASCII "PEAC"4-//   version          1   currently 0x015-//   deviceType       1   0x01=ext, 0x02=android, 0x03=ios, 0x04=desktop6-//   ephemeralPubkey 32   X25519 public key for the pairing Noise NK session7-//   sessionNonce    16   random per pairing attempt; replay-detection on retry8-//   checksum         4   CRC32 (IEEE 802.3) of the preceding 54 bytes9-//   padding          6   zero; reserved10-//11-// No IP, no name, no version-string — those are all carried inside the12-// Noise NK encrypted channel once the chirp has done its job. The on-air13-// payload is therefore the smallest stable shape that two devices can14-// negotiate trust from.15-16-import {17-  CHIRP_MAGIC_BYTES,18-  CHIRP_PAYLOAD_LENGTH,19-  CHIRP_PROTOCOL_VERSION,20-  type ChirpPayload,21-  type DeviceTypeByte,22-} from "./sync-types";23-24-const OFFSET_MAGIC = 0;25-const OFFSET_VERSION = 4;26-const OFFSET_DEVICE_TYPE = 5;27-const OFFSET_PUBKEY = 6;28-const OFFSET_NONCE = 38;29-const OFFSET_CHECKSUM = 54;30-const OFFSET_PADDING = 58;31-const CHECKSUM_COVERAGE = 54;32-33-export class ChirpDecodeError extends Error {34-  readonly kind:35-    | "length"36-    | "magic"37-    | "version"38-    | "device_type"39-    | "checksum"40-    | "padding";41-  constructor(kind: ChirpDecodeError["kind"], message: string) {42-    super(message);43-    this.kind = kind;44-  }45-}46-47-export function encodeChirpPayload(payload: ChirpPayload): Uint8Array {48-  if (payload.ephemeralPublicKey.length !== 32) {49-    throw new Error("ephemeralPublicKey must be 32 bytes");50-  }51-  if (payload.sessionNonce.length !== 16) {52-    throw new Error("sessionNonce must be 16 bytes");53-  }54-  const buf = new Uint8Array(CHIRP_PAYLOAD_LENGTH);55-  buf.set(CHIRP_MAGIC_BYTES, OFFSET_MAGIC);56-  buf[OFFSET_VERSION] = payload.version & 0xff;57-  buf[OFFSET_DEVICE_TYPE] = payload.deviceType & 0xff;58-  buf.set(payload.ephemeralPublicKey, OFFSET_PUBKEY);59-  buf.set(payload.sessionNonce, OFFSET_NONCE);60-  const crc = crc32(buf.subarray(0, CHECKSUM_COVERAGE));61-  buf[OFFSET_CHECKSUM] = (crc >>> 24) & 0xff;62-  buf[OFFSET_CHECKSUM + 1] = (crc >>> 16) & 0xff;63-  buf[OFFSET_CHECKSUM + 2] = (crc >>> 8) & 0xff;64-  buf[OFFSET_CHECKSUM + 3] = crc & 0xff;65-  // padding bytes already 066-  return buf;67-}68-69-export function decodeChirpPayload(buf: Uint8Array): ChirpPayload {70-  if (buf.length !== CHIRP_PAYLOAD_LENGTH) {71-    throw new ChirpDecodeError(72-      "length",73-      `chirp payload must be ${CHIRP_PAYLOAD_LENGTH} bytes, got ${buf.length}`,74-    );75-  }76-  for (let i = 0; i < 4; i++) {77-    if (buf[OFFSET_MAGIC + i] !== CHIRP_MAGIC_BYTES[i]) {78-      throw new ChirpDecodeError("magic", "magic bytes are not PEAC");79-    }80-  }81-  const version = buf[OFFSET_VERSION];82-  if (version !== CHIRP_PROTOCOL_VERSION) {83-    throw new ChirpDecodeError(84-      "version",85-      `unsupported chirp protocol version ${version}`,86-    );87-  }88-  const deviceType = buf[OFFSET_DEVICE_TYPE];89-  if (deviceType < 0x01 || deviceType > 0x04) {90-    throw new ChirpDecodeError(91-      "device_type",92-      `unknown device type byte 0x${deviceType.toString(16)}`,93-    );94-  }95-  const declared =96-    ((buf[OFFSET_CHECKSUM] << 24) |97-      (buf[OFFSET_CHECKSUM + 1] << 16) |98-      (buf[OFFSET_CHECKSUM + 2] << 8) |99-      buf[OFFSET_CHECKSUM + 3]) >>>100-    0;101-  const computed = crc32(buf.subarray(0, CHECKSUM_COVERAGE));102-  if (declared !== computed) {103-    throw new ChirpDecodeError("checksum", "CRC32 mismatch");104-  }105-  for (let i = OFFSET_PADDING; i < CHIRP_PAYLOAD_LENGTH; i++) {106-    if (buf[i] !== 0) {107-      throw new ChirpDecodeError("padding", "reserved padding bytes must be zero");108-    }109-  }110-  return {111-    version,112-    deviceType: deviceType as DeviceTypeByte,113-    ephemeralPublicKey: buf.slice(OFFSET_PUBKEY, OFFSET_PUBKEY + 32),114-    sessionNonce: buf.slice(OFFSET_NONCE, OFFSET_NONCE + 16),115-  };116-}117-118-// CRC32 — IEEE 802.3 (poly 0xEDB88320, reflected). Table-driven; built119-// lazily on first use because the chirp codec is not on the hot path.120-let crcTable: Uint32Array | undefined;121-122-function buildCrcTable(): Uint32Array {123-  const table = new Uint32Array(256);124-  for (let i = 0; i < 256; i++) {125-    let c = i;126-    for (let k = 0; k < 8; k++) c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);127-    table[i] = c >>> 0;128-  }129-  return table;130-}131-132-export function crc32(bytes: Uint8Array): number {133-  if (!crcTable) crcTable = buildCrcTable();134-  let crc = 0xffffffff;135-  for (let i = 0; i < bytes.length; i++) {136-    crc = crcTable[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8);137-  }138-  return (crc ^ 0xffffffff) >>> 0;139-}

view file at the parent

src/shared/chirp-transport.probe.test.ts+0 −244
@@ -1,244 +0,0 @@1-// T-010: PCH-01-018 subnet-probe identity-leak integration2-//3-// Why this test exists: PCH-01-018 added a static-key pin check to the Noise4-// XX initiator that fires BEFORE msg3 is sent. msg3 contains the initiator's5-// long-term static public key (our sync identity). If the responder's static6-// doesn't match the expected paired-device key, the handshake must abort7-// WITHOUT ever transmitting msg3. The pin check is tested in isolation in8-// chirp-sync-transport.test.ts, but no integration test verifies the end-to-end9-// "wrong peer → no msg3" guarantee.10-//11-// Approach: wire noiseXXInitiator and noiseXXResponder together through12-// in-memory channels. The responder uses a DIFFERENT static keypair than13-// expected. We spy on the channel to confirm the initiator never sends a14-// third message (msg3 would be the initiator's encrypted static key).15-16-import { describe, expect, it } from "vitest";17-import { generateKeyPair } from "@stablelib/x25519";18-import {19-  noiseXXInitiator,20-  noiseXXResponder,21-} from "./noise-xx";22-23-// ---------------------------------------------------------------------------24-// In-memory bidirectional channel25-// ---------------------------------------------------------------------------26-27-interface Channel {28-  send: (msg: Uint8Array) => Promise<void>;29-  receive: () => Promise<Uint8Array>;30-}31-32-function createChannelPair(): { a: Channel; b: Channel } {33-  const aToB: Uint8Array[] = [];34-  const bToA: Uint8Array[] = [];35-  const aToBWaiters: Array<(v: Uint8Array) => void> = [];36-  const bToAWaiters: Array<(v: Uint8Array) => void> = [];37-38-  return {39-    a: {40-      send: async (msg) => {41-        const waiter = aToBWaiters.shift();42-        if (waiter) waiter(msg.slice());43-        else aToB.push(msg.slice());44-      },45-      receive: () => {46-        const msg = bToA.shift();47-        if (msg) return Promise.resolve(msg);48-        return new Promise((resolve) => bToAWaiters.push(resolve));49-      },50-    },51-    b: {52-      send: async (msg) => {53-        const waiter = bToAWaiters.shift();54-        if (waiter) waiter(msg.slice());55-        else bToA.push(msg.slice());56-      },57-      receive: () => {58-        const msg = aToB.shift();59-        if (msg) return Promise.resolve(msg);60-        return new Promise((resolve) => aToBWaiters.push(resolve));61-      },62-    },63-  };64-}65-66-function pubkeyToHex(key: Uint8Array): string {67-  return Array.from(key).map(b => b.toString(16).padStart(2, "0")).join("");68-}69-70-// ---------------------------------------------------------------------------71-// Tests72-// ---------------------------------------------------------------------------73-74-describe("T-010: PCH-01-018 subnet-probe identity-leak integration", () => {75-  // T-010a: the initiator aborts before msg3 when the responder's static76-  // doesn't match the expected paired-device key.77-  //78-  // Key assertion: the responder never receives a third message. msg1 and msg279-  // are exchanged (the Noise XX pre-msg3 handshake), but msg3 — which contains80-  // the initiator's long-term static — is never sent.81-  //82-  // Bug this catches: if verifyRemoteStatic is removed from noiseXXInitiator,83-  // the initiator would send msg3 (exposing our static key to an unknown peer)84-  // and the responder would complete the handshake.85-  it("T-010a: wrong-key responder → initiator aborts before msg3; initiator static not transmitted", async () => {86-    // Initiator (our extension's sync identity)87-    const initiatorKp = generateKeyPair();88-    // Responder (what we EXPECT to talk to)89-    const expectedResponderKp = generateKeyPair();90-    // Actual responder (a different device on the LAN — the attacker scenario)91-    const wrongResponderKp = generateKeyPair();92-93-    const expectedHex = pubkeyToHex(expectedResponderKp.publicKey);94-    const { a: initiatorChannel, b: responderChannel } = createChannelPair();95-96-    // Track how many messages the responder receives97-    let responderMsgCount = 0;98-    const wrappedResponderReceive = async (): Promise<Uint8Array> => {99-      const msg = await responderChannel.receive();100-      responderMsgCount++;101-      return msg;102-    };103-104-    // Run the responder in the background using the WRONG static key.105-    // It will send msg2 with its wrong static, then wait for msg3.106-    const responderPromise = noiseXXResponder(107-      wrongResponderKp.secretKey,108-      wrongResponderKp.publicKey,109-      responderChannel.send,110-      wrappedResponderReceive,111-    ).catch(() => { /* responder may error when msg3 never arrives */ });112-113-    // Run the initiator with a verifyRemoteStatic check against the EXPECTED key.114-    const initiatorPromise = noiseXXInitiator(115-      initiatorKp.secretKey,116-      initiatorKp.publicKey,117-      initiatorChannel.send,118-      initiatorChannel.receive,119-      (remoteStatic) => {120-        const hex = pubkeyToHex(remoteStatic);121-        if (hex !== expectedHex) {122-          throw new Error(123-            `PCH-01-018: unexpected peer static (got ${hex.slice(0, 8)}…, ` +124-            `expected ${expectedHex.slice(0, 8)}…)`,125-          );126-        }127-      },128-    );129-130-    // The initiator must throw (pin check failed), not succeed.131-    await expect(initiatorPromise).rejects.toThrow(/unexpected peer static|PCH-01-018/i);132-133-    // The responder received msg1 (the initiator's ephemeral) and sent msg2.134-    // After that the initiator aborts — msg3 is NEVER sent. The responder135-    // had its receive() called once (for msg1); it never receives msg3.136-    //137-    // responderMsgCount === 1: the responder only received msg1.138-    // If it were 2, the initiator sent msg3 — our static was leaked.139-    expect(responderMsgCount).toBe(1);140-141-    // Unblock the parked responder receive() — the responder is waiting142-    // for msg3 via b.receive() which reads from the a→b queue. To unblock143-    // it we must push into that queue via initiatorChannel.send (a.send).144-    initiatorChannel.send(new Uint8Array(0)).catch(() => {});145-    await responderPromise;146-  });147-148-  // T-010b: a correct-key responder allows the handshake to complete.149-  // This is the happy path — the pin check passes and msg3 is sent normally.150-  // Bug this catches: an overly-aggressive pin check that also rejects valid peers.151-  it("T-010b: correct-key responder → handshake proceeds to completion", async () => {152-    const initiatorKp = generateKeyPair();153-    const responderKp = generateKeyPair();154-    const expectedHex = pubkeyToHex(responderKp.publicKey);155-156-    const { a: initiatorChannel, b: responderChannel } = createChannelPair();157-158-    const responderPromise = noiseXXResponder(159-      responderKp.secretKey,160-      responderKp.publicKey,161-      responderChannel.send,162-      responderChannel.receive,163-    );164-165-    const initiatorPromise = noiseXXInitiator(166-      initiatorKp.secretKey,167-      initiatorKp.publicKey,168-      initiatorChannel.send,169-      initiatorChannel.receive,170-      (remoteStatic) => {171-        const hex = pubkeyToHex(remoteStatic);172-        if (hex !== expectedHex) {173-          throw new Error("unexpected peer");174-        }175-      },176-    );177-178-    const [initiatorSession, responderSession] = await Promise.all([179-      initiatorPromise,180-      responderPromise,181-    ]);182-183-    // Both sides get a session — handshake is complete.184-    expect(initiatorSession).toBeDefined();185-    expect(responderSession).toBeDefined();186-187-    // Verify the remote static visible to the initiator is the responder's key188-    expect(pubkeyToHex(initiatorSession.remoteStaticPublicKey)).toBe(expectedHex);189-  });190-191-  // T-010c: no response from the responder (simulate probe timeout).192-  // When the initiator's receive() never resolves, the handshake hangs.193-  // This test verifies the initiator's abort path works and no static is sent.194-  // In production, openChirpSyncSession wraps this in a connection timeout.195-  //196-  // Implementation note: we can't easily test a pure timeout here without197-  // fake timers and AbortController. This test instead verifies that the198-  // initiator does NOT emit msg3 before it hears msg2 — i.e., the handshake199-  // is sequential and the initiator never speculatively leaks its static.200-  it("T-010c: initiator sends only msg1 before receiving msg2 (no speculative msg3)", async () => {201-    const initiatorKp = generateKeyPair();202-    const expectedHex = pubkeyToHex(generateKeyPair().publicKey); // any valid hex203-204-    const sentMessages: Uint8Array[] = [];205-    let receiveCallCount = 0;206-207-    // A send spy that records messages208-    const mockSend = async (msg: Uint8Array) => {209-      sentMessages.push(msg.slice());210-    };211-212-    // A receive that hangs forever (simulates no peer response)213-    const mockReceive = (): Promise<Uint8Array> => {214-      receiveCallCount++;215-      return new Promise(() => { /* never resolves */ });216-    };217-218-    // Start the initiator — it will send msg1 and then wait for msg2.219-    const initiatorPromise = noiseXXInitiator(220-      initiatorKp.secretKey,221-      initiatorKp.publicKey,222-      mockSend,223-      mockReceive,224-      (remoteStatic) => {225-        const hex = pubkeyToHex(remoteStatic);226-        if (hex !== expectedHex) throw new Error("wrong peer");227-      },228-    );229-230-    // Give the event loop a tick to let the initiator send msg1.231-    await new Promise((r) => setTimeout(r, 10));232-233-    // At this point: msg1 has been sent (initiator's ephemeral), but msg2234-    // has not arrived. The initiator must NOT have sent msg3 (our static).235-    //236-    // msg1 = 32 bytes (ephemeral public key, no AEAD)237-    // msg3 = 48 + 16 bytes (encrypted static + encrypted empty payload)238-    expect(sentMessages).toHaveLength(1);239-    expect(sentMessages[0]).toHaveLength(32); // only msg1 (32-byte ephemeral)240-241-    // Clean up: the promise hangs; we don't await it.242-    initiatorPromise.catch(() => {});243-  });244-});

view file at the parent

src/shared/ggwave.test.ts+0 −131
@@ -1,131 +0,0 @@1-import { describe, expect, it, beforeEach, afterEach } from "vitest";2-import { CHIRP_PAYLOAD_LENGTH } from "./sync-types";3-import {4-  __setGgwaveModuleForTest,5-  createChirpCodec,6-  GgwaveProtocol,7-  loadGgwave,8-} from "./ggwave";9-10-function fakeGgwaveModule() {11-  const initParams: unknown[] = [];12-  const encodeCalls: Array<{ payload: Uint8Array; protocol: number; volume: number }> = [];13-  const decodeCalls: Float32Array[] = [];14-  let nextInstance = 1;15-  const freed: number[] = [];16-17-  return {18-    initParams,19-    encodeCalls,20-    decodeCalls,21-    freed,22-    mod: {23-      ProtocolId: { GGWAVE_PROTOCOL_ULTRASOUND_FASTEST: 5 },24-      GGWAVE_OPERATING_MODE_RX: 1,25-      GGWAVE_OPERATING_MODE_TX: 2,26-      GGWAVE_OPERATING_MODE_RX_AND_TX: 3,27-      getDefaultParameters() {28-        return {29-          sampleRateInp: 48000,30-          sampleRateOut: 48000,31-          samplesPerFrame: 1024,32-          soundMarkerThreshold: 3,33-          payloadLength: -1,34-          operatingMode: 0,35-        };36-      },37-      init(p: unknown) {38-        initParams.push(p);39-        return nextInstance++;40-      },41-      free(instance: number) {42-        freed.push(instance);43-      },44-      encode(_inst: number, data: Uint8Array | string, protocol: number, volume: number) {45-        const payload = typeof data === "string" ? new TextEncoder().encode(data) : data;46-        encodeCalls.push({ payload, protocol, volume });47-        return new Float32Array(payload.length * 100); // synthetic waveform48-      },49-      decode(_inst: number, samples: Float32Array | Int16Array) {50-        decodeCalls.push(samples as Float32Array);51-        // half the time, "succeed"; tests inject deterministic samples52-        if (samples.length === 999) return null;53-        return new Uint8Array(CHIRP_PAYLOAD_LENGTH); // empty-but-correct-length54-      },55-    },56-  };57-}58-59-describe("ggwave loader", () => {60-  beforeEach(() => __setGgwaveModuleForTest(null));61-  afterEach(() => __setGgwaveModuleForTest(null));62-63-  it("loadGgwave returns the test-injected module", async () => {64-    const fake = fakeGgwaveModule();65-    __setGgwaveModuleForTest(fake.mod);66-    const mod = await loadGgwave();67-    expect(mod).toBe(fake.mod);68-  });69-70-  it("createChirpCodec configures sample rate, payload length, RX+TX mode", async () => {71-    const fake = fakeGgwaveModule();72-    __setGgwaveModuleForTest(fake.mod);73-    await createChirpCodec({ sampleRate: 44100 });74-    expect(fake.initParams).toHaveLength(1);75-    const p = fake.initParams[0] as Record<string, number>;76-    expect(p.sampleRateInp).toBe(44100);77-    expect(p.sampleRateOut).toBe(44100);78-    expect(p.payloadLength).toBe(CHIRP_PAYLOAD_LENGTH);79-    expect(p.operatingMode).toBe(3); // RX_AND_TX80-  });81-82-  it("encode forwards the payload to ggwave with ultrasonic-fastest protocol and 10% volume by default", async () => {83-    const fake = fakeGgwaveModule();84-    __setGgwaveModuleForTest(fake.mod);85-    const codec = await createChirpCodec({ sampleRate: 48000 });86-    const payload = new Uint8Array(CHIRP_PAYLOAD_LENGTH);87-    payload[0] = 0x50;88-    const wave = codec.encode(payload);89-    expect(wave.length).toBeGreaterThan(0);90-    expect(fake.encodeCalls).toHaveLength(1);91-    expect(fake.encodeCalls[0].payload).toEqual(payload);92-    expect(fake.encodeCalls[0].protocol).toBe(GgwaveProtocol.ULTRASOUND_FASTEST);93-    expect(fake.encodeCalls[0].volume).toBe(10);94-  });95-96-  it("encode rejects wrong-length payload", async () => {97-    const fake = fakeGgwaveModule();98-    __setGgwaveModuleForTest(fake.mod);99-    const codec = await createChirpCodec({ sampleRate: 48000 });100-    expect(() => codec.encode(new Uint8Array(63))).toThrow();101-  });102-103-  it("decode forwards samples and returns the payload bytes", async () => {104-    const fake = fakeGgwaveModule();105-    __setGgwaveModuleForTest(fake.mod);106-    const codec = await createChirpCodec({ sampleRate: 48000 });107-    const samples = new Float32Array(2048);108-    const decoded = codec.decode(samples);109-    expect(decoded).toBeInstanceOf(Uint8Array);110-    expect(decoded!.length).toBe(CHIRP_PAYLOAD_LENGTH);111-  });112-113-  it("decode returns null when ggwave returns null (no chirp detected)", async () => {114-    const fake = fakeGgwaveModule();115-    __setGgwaveModuleForTest(fake.mod);116-    const codec = await createChirpCodec({ sampleRate: 48000 });117-    const samples = new Float32Array(999);118-    expect(codec.decode(samples)).toBeNull();119-  });120-121-  it("free is idempotent and prevents further encode/decode", async () => {122-    const fake = fakeGgwaveModule();123-    __setGgwaveModuleForTest(fake.mod);124-    const codec = await createChirpCodec({ sampleRate: 48000 });125-    codec.free();126-    codec.free();127-    expect(fake.freed).toEqual([1]);128-    expect(() => codec.encode(new Uint8Array(CHIRP_PAYLOAD_LENGTH))).toThrow();129-    expect(() => codec.decode(new Float32Array(0))).toThrow();130-  });131-});

view file at the parent

src/shared/ggwave.ts+0 −187
@@ -1,187 +0,0 @@1-// ggwave loader — thin async wrapper around the ggwave-wasm module.2-//3-// ggwave is loaded from a vendored copy at `/ggwave/ggwave.js` rather than4-// npm-imported. Two reasons:5-//6-//   1. The browser-extension service worker can't import arbitrary npm7-//      modules at runtime — every dependency has to be bundled. ggwave-wasm8-//      ships a JS+WASM pair where the JS glue is small and the WASM blob9-//      is ~150KB; vendoring keeps the bundle deterministic and dodges a10-//      transitive-dep audit on every release.11-//12-//   2. The same loader works in popup pages, content scripts, and on the13-//      Capacitor mobile build — anywhere we can fetch a static asset.14-//15-// The loader is idempotent: repeated `loadGgwave()` calls share one16-// instance. The instance owns a per-AudioContext sample rate and must be17-// rebuilt if the AudioContext rate changes between calls (which it never18-// should during the lifetime of a pairing screen).19-20-import { CHIRP_PAYLOAD_LENGTH } from "./sync-types";21-22-// ggwave-wasm ProtocolId enum subset. We only ever use the ultrasonic23-// fastest mode — see brief §3.1 — but the others are listed so the type24-// covers the upstream API surface we forward.25-export const enum GgwaveProtocol {26-  AUDIBLE_NORMAL = 0,27-  AUDIBLE_FAST = 1,28-  AUDIBLE_FASTEST = 2,29-  ULTRASOUND_NORMAL = 3,30-  ULTRASOUND_FAST = 4,31-  ULTRASOUND_FASTEST = 5, // GGWAVE_PROTOCOL_MT_FASTEST equivalent — chirp default32-  DT_NORMAL = 6,33-  DT_FAST = 7,34-  DT_FASTEST = 8,35-}36-37-interface GgwaveParameters {38-  sampleRateInp: number;39-  sampleRateOut: number;40-  samplesPerFrame: number;41-  soundMarkerThreshold: number;42-  payloadLength: number;43-  operatingMode: number;44-}45-46-interface GgwaveModule {47-  ProtocolId: Record<string, number>;48-  GGWAVE_OPERATING_MODE_RX: number;49-  GGWAVE_OPERATING_MODE_TX: number;50-  GGWAVE_OPERATING_MODE_RX_AND_TX: number;51-  getDefaultParameters(): GgwaveParameters;52-  init(p: GgwaveParameters): number; // instance handle53-  free(instance: number): void;54-  encode(instance: number, data: Uint8Array | string, protocol: number, volumePercent: number): Float32Array;55-  decode(instance: number, samples: Float32Array | Int16Array): Uint8Array | null;56-}57-58-type GgwaveFactory = () => Promise<GgwaveModule>;59-60-declare global {61-  interface Window {62-    ggwave_factory?: GgwaveFactory;63-  }64-  // eslint-disable-next-line no-var65-  var ggwave_factory: GgwaveFactory | undefined;66-}67-68-let modulePromise: Promise<GgwaveModule> | null = null;69-70-/**71- * Locate the ggwave JS glue. Picked relative to the extension URL when72- * running inside Chrome/Firefox; falls back to a relative path so the same73- * loader works in the Capacitor mobile build.74- */75-function ggwaveScriptUrl(): string {76-  if (typeof chrome !== "undefined" && chrome.runtime?.getURL) {77-    return chrome.runtime.getURL("ggwave/ggwave.js");78-  }79-  return "./ggwave/ggwave.js";80-}81-82-async function injectScript(url: string): Promise<void> {83-  // Service worker / module worker: importScripts is unavailable in MV384-  // workers, but `import(url)` always works for a same-origin ES module.85-  // The vendored ggwave.js is wrapped as a side-effecting ES module that86-  // assigns globalThis.ggwave_factory on evaluation.87-  await import(/* @vite-ignore */ url);88-}89-90-export async function loadGgwave(): Promise<GgwaveModule> {91-  if (modulePromise) return modulePromise;92-  modulePromise = (async () => {93-    if (typeof globalThis.ggwave_factory !== "function") {94-      try {95-        await injectScript(ggwaveScriptUrl());96-      } catch (err) {97-        throw new Error(98-          `ggwave glue could not be loaded — vendored asset missing? ${(err as Error).message}`,99-        );100-      }101-    }102-    const factory = globalThis.ggwave_factory;103-    if (typeof factory !== "function") {104-      throw new Error("ggwave_factory not exposed after script load");105-    }106-    return factory();107-  })();108-  return modulePromise;109-}110-111-/**112- * High-level chirp codec bound to a specific AudioContext sample rate.113- * One instance is held by the pairing coordinator for the entire pairing114- * session and discarded on completion.115- *116- * Volume is fixed at 10% — ggwave's published guidance for ultrasonic117- * pairing on internal speakers. Raising it makes pairing more reliable in118- * noisy rooms but pumps audible artefacts at the low edge of the FSK band119- * on some laptop speakers.120- */121-export interface ChirpCodec {122-  /** Encode a 64-byte chirp payload into a mono float32 waveform. */123-  encode(payload: Uint8Array): Float32Array;124-  /** Try to decode a single chirp out of a buffer of input samples. */125-  decode(samples: Float32Array): Uint8Array | null;126-  /** Free the underlying ggwave instance. Idempotent. */127-  free(): void;128-}129-130-export interface ChirpCodecOptions {131-  sampleRate: number;132-  protocol?: GgwaveProtocol;133-  volumePercent?: number;134-}135-136-export async function createChirpCodec(opts: ChirpCodecOptions): Promise<ChirpCodec> {137-  const mod = await loadGgwave();138-  const params = mod.getDefaultParameters();139-  params.sampleRateInp = opts.sampleRate;140-  params.sampleRateOut = opts.sampleRate;141-  params.payloadLength = CHIRP_PAYLOAD_LENGTH;142-  params.operatingMode = mod.GGWAVE_OPERATING_MODE_RX_AND_TX;143-  const protocol = opts.protocol ?? GgwaveProtocol.ULTRASOUND_FASTEST;144-  const volume = opts.volumePercent ?? 10;145-  let instance: number | null = mod.init(params);146-147-  return {148-    encode(payload: Uint8Array): Float32Array {149-      if (instance === null) throw new Error("chirp codec is freed");150-      if (payload.length !== CHIRP_PAYLOAD_LENGTH) {151-        throw new Error(`chirp payload must be ${CHIRP_PAYLOAD_LENGTH} bytes`);152-      }153-      const result = mod.encode(instance, payload, protocol, volume);154-      if (!result || result.length === 0) {155-        throw new Error("ggwave.encode returned an empty waveform");156-      }157-      return result;158-    },159-    decode(samples: Float32Array): Uint8Array | null {160-      if (instance === null) throw new Error("chirp codec is freed");161-      const decoded = mod.decode(instance, samples);162-      if (!decoded) return null;163-      // ggwave returns the exact payload bytes — caller validates the164-      // PEAC magic via decodeChirpPayload.165-      return new Uint8Array(decoded);166-    },167-    free(): void {168-      if (instance !== null) {169-        mod.free(instance);170-        instance = null;171-      }172-    },173-  };174-}175-176-/**177- * Test-only override for the ggwave module. The chirp pairing tests inject178- * a fake module so they can exercise the play/listen loop without the179- * vendored WASM blob.180- */181-export function __setGgwaveModuleForTest(mod: GgwaveModule | null): void {182-  if (mod === null) {183-    modulePromise = null;184-    return;185-  }186-  modulePromise = Promise.resolve(mod);187-}

view file at the parent

src/shared/sync-types.ts+0 −26
@@ -110,32 +110,6 @@110110 }111111 112112 // ---------------------------------------------------------------------------113-// v3 chirp pairing — phase-1 UI state114-// ---------------------------------------------------------------------------115-116-/**117- * Phase 1 pairing state, surfaced to the chirp pairing view. The view118- * binds its bird-animation acts directly to these states: `listening` and119- * `chirping` map to Act 1, `handshake` maps to Act 2, `paired` to Act 3.120- */121-export type PairingPhase =122-  | "idle"123-  | "listening"124-  | "chirping"125-  | "handshake"126-  | "paired"127-  | "timeout"128-  | "mic_denied"129-  | "version_mismatch"130-  | "handshake_failed";131-132-export interface PairingProgress {133-  phase: PairingPhase;134-  peerDeviceName?: string;135-  errorMessage?: string;136-}137-138-// ---------------------------------------------------------------------------139113 // v3 wire protocol — phase-2 WebSocket manifest sync140114 // ---------------------------------------------------------------------------141115 

view file