repoburg 1.3.255 → 1.3.257

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/daemon/dist/index.js +61 -3
  2. package/daemon/dist/staticServing.js +2 -2
  3. package/daemon/dist/tabtape/api/follow.js +174 -0
  4. package/daemon/dist/tabtape/api/http.js +221 -0
  5. package/daemon/dist/tabtape/api/record.js +149 -0
  6. package/daemon/dist/tabtape/api/router.js +519 -0
  7. package/daemon/dist/tabtape/api/router.spec.js +604 -0
  8. package/daemon/dist/tabtape/api/static.js +70 -0
  9. package/daemon/dist/tabtape/api/ws.js +166 -0
  10. package/daemon/dist/tabtape/buffer/buffer.js +347 -0
  11. package/daemon/dist/tabtape/buffer/buffer.spec.js +137 -0
  12. package/daemon/dist/tabtape/config.js +108 -0
  13. package/daemon/dist/tabtape/e2e-server.js +96 -0
  14. package/daemon/dist/tabtape/errors.js +77 -0
  15. package/daemon/dist/tabtape/export/export.js +331 -0
  16. package/daemon/dist/tabtape/export/export.spec.js +262 -0
  17. package/daemon/dist/tabtape/har/gostd.js +395 -0
  18. package/daemon/dist/tabtape/har/gostd.spec.js +139 -0
  19. package/daemon/dist/tabtape/har/har.js +582 -0
  20. package/daemon/dist/tabtape/har/har.spec.js +325 -0
  21. package/daemon/dist/tabtape/index.js +134 -0
  22. package/daemon/dist/tabtape/index.spec.js +104 -0
  23. package/daemon/dist/tabtape/lock.js +67 -0
  24. package/daemon/dist/tabtape/log.js +32 -0
  25. package/daemon/dist/tabtape/mcp/definitions.js +362 -0
  26. package/daemon/dist/tabtape/mcp/gofmt.js +227 -0
  27. package/daemon/dist/tabtape/mcp/params.js +345 -0
  28. package/daemon/dist/tabtape/mcp/params.spec.js +172 -0
  29. package/daemon/dist/tabtape/mcp/recordLink.js +122 -0
  30. package/daemon/dist/tabtape/mcp/server.js +411 -0
  31. package/daemon/dist/tabtape/mcp/server.spec.js +722 -0
  32. package/daemon/dist/tabtape/mcp/tools.js +1085 -0
  33. package/daemon/dist/tabtape/query/format.js +195 -0
  34. package/daemon/dist/tabtape/query/network.js +133 -0
  35. package/daemon/dist/tabtape/query/perf.js +93 -0
  36. package/daemon/dist/tabtape/query/query.spec.js +233 -0
  37. package/daemon/dist/tabtape/query/reader.js +602 -0
  38. package/daemon/dist/tabtape/query/types.js +274 -0
  39. package/daemon/dist/tabtape/redact.js +37 -0
  40. package/daemon/dist/tabtape/repro/lang.js +95 -0
  41. package/daemon/dist/tabtape/repro/repro.js +422 -0
  42. package/daemon/dist/tabtape/repro/repro.spec.js +210 -0
  43. package/daemon/dist/tabtape/sink.js +24 -0
  44. package/daemon/dist/tabtape/store/record.js +99 -0
  45. package/daemon/dist/tabtape/store/segment.js +267 -0
  46. package/daemon/dist/tabtape/store/store.js +846 -0
  47. package/daemon/dist/tabtape/store/store.spec.js +485 -0
  48. package/daemon/dist/tabtape/testing/querytest.js +119 -0
  49. package/daemon/dist/tabtape/testing/tmp.js +33 -0
  50. package/package.json +3 -2
  51. package/tabtape/extension/dist/background.js +8 -0
  52. package/tabtape/extension/dist/background.js.map +7 -0
  53. package/tabtape/extension/dist/bridge.js +3 -0
  54. package/tabtape/extension/dist/bridge.js.map +7 -0
  55. package/tabtape/extension/dist/icons/128.png +0 -0
  56. package/tabtape/extension/dist/icons/16.png +0 -0
  57. package/tabtape/extension/dist/icons/32.png +0 -0
  58. package/tabtape/extension/dist/icons/48.png +0 -0
  59. package/tabtape/extension/dist/manifest.json +28 -0
  60. package/tabtape/extension/dist/popup.css +62 -0
  61. package/tabtape/extension/dist/popup.html +34 -0
  62. package/tabtape/extension/dist/popup.js +4 -0
  63. package/tabtape/extension/dist/popup.js.map +7 -0
  64. package/tabtape/extension/dist/recorder.js +133 -0
  65. package/tabtape/extension/dist/recorder.js.map +1 -0
  66. package/tabtape/extension/dist/state.js +2 -0
  67. package/tabtape/extension/dist/state.js.map +1 -0
  68. package/tabtape/playwright/mcp.mjs +58 -0
  69. package/tabtape/playwright/tabtape.d.mts +37 -0
  70. package/tabtape/playwright/tabtape.mjs +146 -0
  71. package/tabtape/ui/dist/app/assets/index-Canv3Ljk.css +1 -0
  72. package/tabtape/ui/dist/app/assets/index-iWNSSrAE.js +145 -0
  73. package/tabtape/ui/dist/app/index.html +14 -0
  74. package/tabtape/ui/dist/export/viewer.css +2 -0
  75. package/tabtape/ui/dist/export/viewer.js +143 -0
@@ -0,0 +1,2 @@
1
+ "use strict";(()=>{var tt="tabtape.state";var gt=JSON.stringify;var Ct=Object.prototype.toString,W="********",Pt=/passw(or)?d|^pwd$|^pass$/i;var J=(e,t)=>e.length>t?e.slice(0,t-1)+"\u2026":e;function $(e){return Pt.test(e)}var et={depth:4,items:50,string:2e3};function nt(e){if(!e||typeof e!="object")return!1;if(e instanceof Error)return!0;let t=e;return typeof t.message=="string"&&typeof t.name=="string"&&"stack"in t}function Lt(e){if(e.nodeType===1){let t=e,n="<"+t.tagName.toLowerCase();t.id&&(n+="#"+t.id);let r=t.getAttribute("class")?.trim();return r&&(n+="."+r.split(/\s+/).slice(0,3).join(".")),n+">"}return e.nodeType===3?`#text "${J(e.textContent??"",60)}"`:e.nodeName}function wt(e,t=et){return F(e,0,new WeakSet,t)}function F(e,t,n,r){switch(typeof e){case"string":return J(e,r.string);case"number":return Number.isFinite(e)?e:String(e);case"boolean":return e;case"undefined":return"undefined";case"bigint":return`${e}n`;case"symbol":return e.toString();case"function":return`\u0192 ${e.name||"anonymous"}()`}if(e===null)return null;let o=e;if(n.has(o))return"[Circular]";try{if(nt(o))return{name:o.name,message:J(String(o.message),r.string),stack:J(String(o.stack??""),Math.min(4e3,r.string*2))};let i=o;if(typeof i.nodeType=="number"&&typeof i.nodeName=="string")return Lt(i);if(o.window===o)return"[Window]";let s=Ct.call(o).slice(8,-1);if(s==="Date"){let f=o;return Number.isNaN(f.getTime())?"Invalid Date":f.toISOString()}if(s==="RegExp")return String(o);if(s==="Promise")return"[Promise]";if(o instanceof ArrayBuffer||ArrayBuffer.isView(o))return`[${s} ${o.byteLength} bytes]`;if(t>=r.depth)return Array.isArray(o)?`[Array(${o.length})]`:`[${s}]`;if(n.add(o),Array.isArray(o)){let f=o.slice(0,r.items).map(d=>F(d,t+1,n,r));return o.length>r.items&&f.push(`\u2026 ${o.length-r.items} more`),f}if(s==="Map")return{"[[Map]]":F([...o].slice(0,r.items),t+1,n,r)};if(s==="Set")return{"[[Set]]":F([...o].slice(0,r.items),t+1,n,r)};let a={},u=Object.keys(o);for(let f of u.slice(0,r.items))try{a[f]=F(o[f],t+1,n,r)}catch{a[f]="[unreadable]"}return u.length>r.items&&(a["\u2026"]=`${u.length-r.items} more keys`),a}catch{return"[unserializable]"}}var yt=8*1024;var Dt=[et,{depth:4,items:50,string:500},{depth:4,items:20,string:200},{depth:3,items:10,string:100},{depth:2,items:10,string:50}],V="\u2026";function Bt(e){let t=0;for(let n=0;n<e.length;n++){let r=e.charCodeAt(n);r<128?t+=1:r<2048?t+=2:r>=55296&&r<=56319&&(e.charCodeAt(n+1)&64512)===56320?(t+=4,n++):t+=3}return t}function H(e,t=!1){if(Array.isArray(e)){for(let n=0;n<e.length;n++)e[n]=H(e[n],t);return e}if(e&&typeof e=="object"){let n=e;for(let r of Object.keys(n)){let o=n[r];r==="[[Map]]"&&Array.isArray(o)?n[r]=o.map(i=>Array.isArray(i)&&i.length===2&&typeof i[0]=="string"?[i[0],H(i[1],t||$(i[0]))]:H(i,t)):n[r]=H(o,t||$(r))}return n}return t&&(typeof e=="string"||typeof e=="number")?W:e}function mt(e){if(e==null)return{};let t={};for(let[n,r]of Dt.entries())if(t=Ft(H(wt(e,r))),kt(t)<=yt)return bt(t,n>0);return bt($t(t),!0)}function Ft(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)?e:{value:e}}function bt(e,t){let n={};return Object.keys(e).length&&(n.props=e),t&&(n.truncated=!0),n}function kt(e){return Bt(gt(e)??"")}function $t(e){let t=e[V],n=typeof t=="string"&&Number.parseInt(t,10)||0,r=Object.keys(e).filter(a=>a!==V),o={},i=0;for(let a of r){o[a]=e[a];let u=r.length-i-1+n;if(kt(u?{...o,[V]:`${u} more keys`}:o)>yt){delete o[a];break}i++}let s=r.length-i+n;return s&&(o[V]=`${s} more keys`),o}var I=JSON.stringify,rt=Object.keys,z=Array.isArray,Ht=Object.getPrototypeOf,Ut=Object.prototype,Kt=Object.prototype.toString,Xt=Map.prototype.forEach,Jt=Set.prototype.forEach,Wt=Date.prototype.getTime,Vt=Date.prototype.toISOString,zt={String:String.prototype.valueOf,Number:Number.prototype.valueOf,Boolean:Boolean.prototype.valueOf,BigInt:BigInt.prototype.valueOf},Yt=ArrayBuffer.isView,qt=Number.isFinite,Gt=1024*1024,G=100,ot=32,Zt=512,Y=32*1024*1024,Qt=1024,te=4096,U=I(W),E={overflow:!0};function vt(e){return e===E}function ee(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function ne(e){return`[\u2026 ${ee(e)} not recorded]`}function re(e){return e.includes("~")||e.includes("/")?e.replace(/~/g,"~0").replace(/\//g,"~1"):e}var q=new Map;function C(e){let t=q.get(e);return t===void 0&&(t={json:I(e)+":",ptr:re(e),pw:$(e)},e.length<=64&&(q.size>=1e4&&q.clear(),q.set(e,t))),t}function _(e){return e===void 0||typeof e=="function"||typeof e=="symbol"}function K(e){let t=Ht(e);return t===Ut||t===null}function St(e,t){try{return e[t]}catch{return"[unreadable]"}}function oe(e){return e.nodeType===1?`<${String(e.tagName).toLowerCase()}${e.id?"#"+String(e.id):""}>`:e.nodeName}var P=class e{out="";truncated=!1;limit;counting;anc;sizes;reserved=0;count=0;constructor(t,n=!1,r=new Set,o){this.limit=t,this.counting=n,this.anc=r,this.sizes=o}get length(){return this.counting?this.count:this.out.length}raw(t){if(this.counting){if((this.count+=t.length)>Y)throw E;return}if(this.out.length+t.length>this.limit-this.reserved)throw E;this.out+=t}value(t,n,r){switch(typeof t){case"string":if(n)return this.raw(U);if(this.counting){if(t.length>te){if((this.count+=t.length+2)>Y)throw E;return}}else if(this.out.length+t.length+2>this.limit-this.reserved)throw E;return this.raw(I(t));case"number":return this.raw(n?U:qt(t)?""+t:"null");case"boolean":return this.raw(t?"true":"false");case"bigint":return this.raw(n?U:'"'+t+'n"');case"object":return t===null?this.raw("null"):this.object(t,n,r);default:return this.raw("null")}}object(t,n,r){if(this.anc.has(t))return this.raw('"[Circular]"');if(r>=G)return this.truncated=!0,this.raw(z(t)?`"[Array(${t.length})]"`:'"[Object]"');if(this.counting&&this.sizes){let s=this.sizes.get(t);if(s!==void 0){if((this.count+=s)>Y)throw E;return}}let o=this.out,i=this.length;this.anc.add(t);try{z(t)?this.array(t,n,r):K(t)?this.props(t,n,r):this.special(t,n,r)}catch(s){if(s===E)throw s;this.counting?this.count=i:this.out=o,this.raw('"[unreadable]"')}finally{this.anc.delete(t)}this.counting&&this.sizes&&this.count-i>=Qt&&this.sizes.set(t,this.count-i)}array(t,n,r){this.raw("[");for(let o=0,i=t.length;o<i;o++)o>0&&this.raw(","),this.value(t[o],n,r+1);this.raw("]")}props(t,n,r){this.raw("{");let o=rt(t),i=!0;for(let s=0;s<o.length;s++){let a=St(t,o[s]);if(_(a))continue;let u=C(o[s]);i?i=!1:this.raw(","),this.raw(u.json),this.value(a,n||u.pw,r+1)}this.raw("}")}special(t,n,r){let o=Kt.call(t).slice(8,-1);switch(o){case"Date":{let a;try{a=Wt.call(t)}catch{break}return this.raw(n?U:a===a?'"'+Vt.call(t)+'"':'"Invalid Date"')}case"Map":return this.map(t,n,r);case"Set":return this.set(t,n,r);case"RegExp":return this.raw(n?U:I(String(t)));case"Promise":case"WeakMap":case"WeakSet":case"WeakRef":return this.raw(`"[${o}]"`);case"String":case"Number":case"Boolean":case"BigInt":{let a;try{a=zt[o].call(t)}catch{break}return this.value(a,n,r)}}if(nt(t))return this.raw('{"name":'),this.value(String(t.name),n,r+1),this.raw(',"message":'),this.value(String(t.message),n,r+1),this.raw("}");if(t instanceof ArrayBuffer||Yt(t))return this.raw(`"[${o} ${t.byteLength} bytes]"`);let i=t;if(typeof i.nodeType=="number"&&typeof i.nodeName=="string")return this.raw(I(oe(i)));if(i.window===t)return this.raw('"[Window]"');let s=t.toJSON;if(typeof s=="function"){let a=s.call(t,"");if(a!==t)return this.value(a,n,r)}this.props(t,n,r)}map(t,n,r){this.raw('{"[[Map]]":[');let o=!0;Xt.call(t,(i,s)=>{o?o=!1:this.raw(","),this.raw("["),this.value(s,!1,r+2),this.raw(","),this.value(i,n||typeof s=="string"&&C(s).pw,r+2),this.raw("]")}),this.raw("]}")}set(t,n,r){this.raw('{"[[Set]]":[');let o=!0;Jt.call(t,i=>{o?o=!1:this.raw(","),this.value(i,n,r+2)}),this.raw("]}")}careful(t,n,r,o){let i=-1;if(!o&&(i=this.measure(t,n,r),i<=this.limit-this.reserved-this.out.length)){let s=this.out;try{return this.value(t,n,r),!1}catch{this.out=s}}if(this.descendable(t,r)){let s=this.out;try{return this.descend(t,n,r),!1}catch{this.out=s}}return i<0&&(i=this.measure(t,n,r)),this.raw(I(ne(i))),this.truncated=!0,!0}descendable(t,n){if(t===null||typeof t!="object"||n>=G||this.anc.has(t))return!1;let r=n===0?this.limit/2/ot:Zt;try{return z(t)?t.length<=r:K(t)&&rt(t).length<=r}catch{return!1}}descend(t,n,r){let o=z(t),i=[];if(o){let u=t;for(let f=0;f<u.length;f++)i.push({prefix:f?",":"",value:u[f],masked:n})}else for(let u of rt(t)){let f=St(t,u);if(_(f))continue;let d=C(u);i.push({prefix:(i.length?",":"")+d.json,value:f,masked:n||d.pw})}let s=1;for(let u of i)s+=u.prefix.length+ot;if(this.out.length+1+s>this.limit-this.reserved)throw E;let a=this.reserved;this.anc.add(t);try{this.raw(o?"[":"{"),this.reserved+=s;for(let u of i){let f=u.prefix.length+ot,d=this.out.length;this.reserved-=f,this.raw(u.prefix),this.careful(u.value,u.masked,r+1,!1)&&(this.reserved+=f-(this.out.length-d))}this.reserved-=1,this.raw(o?"]":"}")}finally{this.reserved=a,this.anc.delete(t)}}measure(t,n,r){let o=new e(1/0,!0,this.anc,this.sizes);try{o.value(t,n,r)}catch(i){if(i!==E)throw i;return Y}return o.count}};function it(e,t=Gt){let n=new P(t);try{return n.value(e,!1,0),{json:n.out,truncated:n.truncated}}catch(o){if(o!==E)throw o}let r=new P(t,!1,new Set,new Map);return r.careful(e,!1,0,!0),{json:r.out,truncated:!0}}var ie=200,se=64*1024,ae=JSON.stringify,Et=Object.keys,Tt=Array.isArray,xt=Object.prototype.hasOwnProperty,Ot={tooMany:!0};function At(e,t){let n=[];try{st(e,t,"",!1,0,n,new Set)}catch(o){if(o===Ot)return;throw o}let r=new P(se);try{r.raw("[");for(let o=0;o<n.length;o++){let i=n[o];r.raw((o?',{"path":':'{"path":')+ae(i.path)),i.deleted?r.raw(',"deleted":true}'):(r.raw(',"value":'),r.value(i.value,i.masked,i.depth),r.raw("}"))}r.raw("]")}catch(o){if(vt(o))return;throw o}return r.out}function L(e,t){if(e.length>=ie)throw Ot;e.push(t)}function st(e,t,n,r,o,i,s){if(e!==t){if(typeof e=="object"&&typeof t=="object"&&e!==null&&t!==null&&o<G){if(s.has(t))return;let a=Tt(e),u=Tt(t);if(a&&u){s.add(t),ue(e,t,n,r,o,i,s),s.delete(t);return}if(!a&&!u&&K(e)&&K(t)){s.add(t),ce(e,t,n,r,o,i,s),s.delete(t);return}}else if(typeof e=="number"&&typeof t=="number"&&e!==e&&t!==t)return;L(i,{path:n,value:t,masked:r,depth:o})}}function ue(e,t,n,r,o,i,s){let a=e.length,u=t.length,f=a<u?a:u;for(let d=0;d<f;d++){let x=e[d],w=t[d];x!==w&&st(x,w,n+"/"+d,r,o+1,i,s)}for(let d=a;d<u;d++)L(i,{path:n+"/"+d,value:t[d],masked:r,depth:o+1});for(let d=a-1;d>=u;d--)L(i,{path:n+"/"+d,deleted:!0,masked:r,depth:o+1})}function ce(e,t,n,r,o,i,s){let a=e,u=t,f=Et(u),d=0;for(let w=0;w<f.length;w++){let m=f[w],O=xt.call(a,m);O&&d++;let j=u[m],A=O?a[m]:void 0;if(j===A)continue;let D=C(m),B=n+"/"+D.ptr,T=r||D.pw;_(j)?_(A)||L(i,{path:B,deleted:!0,masked:T,depth:o+1}):_(A)?L(i,{path:B,value:j,masked:T,depth:o+1}):st(A,j,B,T,o+1,i,s)}let x=Et(a);if(d!==x.length)for(let w=0;w<x.length;w++){let m=x[w];if(xt.call(u,m)||_(a[m]))continue;let O=C(m);L(i,{path:n+"/"+O.ptr,deleted:!0,masked:r||O.pw,depth:o+1})}}var at={actions:50,windowMs:1e3,resyncMs:1e3},fe=100,Rt=100,le=1e3,de=200,ut=JSON.stringify,pe=Object.keys;function _t(e){return typeof e=="number"&&Number.isFinite(e)?String(e):typeof e=="string"?e.trim().slice(0,Rt):""}function he(e){let t=e;if(e!==null&&typeof e=="object"){try{t=e.type}catch{t=void 0}(typeof t=="number"||typeof t=="symbol"||typeof t=="bigint"||typeof t=="boolean")&&(t=String(t))}return typeof t=="string"&&t!==""?t.slice(0,le):"anonymous"}function ge(e){if(e===null||typeof e!="object")return"";let t=Object.create(null),n=!1;for(let o of pe(e)){if(o==="type")continue;let i;try{i=e[o]}catch{i="[unreadable]"}i!==void 0&&(t[o]=i,n=!0)}if(!n)return"";let r=mt(t);return r.props?',"payload":'+ut(r.props)+(r.truncated?',"payloadTruncated":true':""):""}var Z=class{api;mode="idle";emit;stores=[];names=new Set;unnamed=0;manualStores=new Map;busy=!1;deps;constructor(t,n){this.deps=t;let r=n?"early":"late";this.api={start:o=>this.guard(()=>this.start(o)),stop:()=>this.guard(()=>this.stop()),checkout:()=>this.guard(()=>this.checkout()),disable:()=>this.guard(()=>this.disable()),manual:(o,i,s)=>this.guard(()=>this.manual(o,i,s)),info:()=>{try{return{hook:r,stores:this.stores.map(o=>o.name)}}catch{return{hook:r,stores:[]}}}}}get recording(){return this.mode==="recording"}addEnhancerStore(t,n){let r=this.add(t,n);return r&&this.full(r,"init"),r}addConnection(t){return this.add(t,void 0)}connInit(t,n){t.latest=n,t.hasLatest=!0,this.mode!=="disabled"&&this.full(t,"init",void 0,{v:n})}connSend(t,n,r){n!==null&&(t.latest=r,t.hasLatest=!0,this.mode!=="disabled"&&this.action(t,n,r))}action(t,n,r){if(this.mode!=="recording"||this.busy)return;this.busy=!0;let o=!1;try{o=this.writeAction(t,n,r)}catch{t.base=void 0,t.hasBase=!1}finally{this.busy=!1}o&&this.full(t,"snapshot","interval",{v:r})}add(t,n){if(this.stores.length>=de)return;let r={name:this.unique(t),getState:n,latest:void 0,hasLatest:!1,base:void 0,hasBase:!1,written:!1,sinceFull:0,winStart:0,winCount:0,burst:!1,timer:void 0};return this.stores.push(r),r}unique(t){let n;if(t)if(!this.names.has(t))n=t;else{let r=2;for(;this.names.has(`${t}-${r}`);)r++;n=`${t}-${r}`}else do n=`store-${++this.unnamed}`;while(this.names.has(n));return this.names.add(n),n}start(t){if(typeof t=="function"){this.emit=t,this.mode="recording";for(let n of this.stores)this.reset(n),this.full(n,n.written?"snapshot":"init","start")}}stop(){this.mode==="recording"&&(this.mode="idle"),this.emit=void 0;for(let t of this.stores)this.reset(t)}disable(){this.mode="disabled",this.emit=void 0;for(let t of this.stores)this.reset(t)}checkout(){if(this.mode==="recording")for(let t of this.stores)this.full(t,"snapshot","checkout")}manual(t,n,r){if(typeof t!="string"||this.mode==="disabled")return;let o=t.trim();if(!o)return;let i=this.manualStores.get(o);if(!i){if(i=this.add(o.slice(0,Rt),void 0),!i)return;this.manualStores.set(o,i)}i.latest=r,i.hasLatest=!0,this.action(i,n,r)}reset(t){if(t.timer!==void 0){try{this.deps.clearTimeout(t.timer)}catch{}t.timer=void 0}t.burst=!1,t.winStart=0,t.winCount=0,t.sinceFull=0,t.base=void 0,t.hasBase=!1}guard(t){try{t()}catch{}}head(t,n){return'{"store":'+ut(t.name)+',"kind":"'+n+'"'}write(t,n,r){t.written=!0,this.emit?.(n,r)}writeAction(t,n,r){let o=this.deps.now(),i=ut(he(n));if((o-t.winStart>=at.windowMs||o<t.winStart)&&(t.winStart=o,t.winCount=0),++t.winCount>at.actions||t.burst)return t.burst||(t.burst=!0,t.base=void 0,t.hasBase=!1,t.timer=this.deps.setTimeout(()=>this.resync(t),at.resyncMs)),t.sinceFull++,this.write(t,this.head(t,"action")+',"action":'+i+',"burst":true}',o),!1;let s=this.head(t,"action")+',"action":'+i+ge(n),a;if(t.hasBase)try{a=At(t.base,r)}catch{a=void 0}let u=!1;if(a!==void 0)s+=',"changes":'+a,u=++t.sinceFull>=fe;else{let f=it(r);s+=',"state":'+f.json+(f.truncated?',"truncated":true':""),t.sinceFull=0}return t.base=r,t.hasBase=!0,this.write(t,s+"}",o),u}full(t,n,r,o){if(!(this.mode!=="recording"||this.busy)){this.busy=!0;try{let i;if(o)i=o.v;else if(t.getState)i=t.getState();else if(t.hasLatest)i=t.latest;else return;let s=this.deps.now(),a=it(i),u=this.head(t,n)+',"state":'+a.json;a.truncated&&(u+=',"truncated":true'),n==="snapshot"&&r&&(u+=',"reason":"'+r+'"'),t.base=i,t.hasBase=!0,t.sinceFull=0,this.write(t,u+"}",s)}catch{t.base=void 0,t.hasBase=!1}finally{this.busy=!1}}}resync(t){t.timer=void 0,t.burst&&(t.burst=!1,this.full(t,"snapshot","burst"))}};var we="__REDUX_DEVTOOLS_EXTENSION__",be="__REDUX_DEVTOOLS_EXTENSION_COMPOSE__",ye=["connect","disconnect","send","listen","open","notifyErrors"],ct=Object.defineProperty,me=Object.getOwnPropertyDescriptor,ke=Object.keys,M=Reflect.apply,Se=()=>{};function Mt(e){try{return e!==null&&typeof e=="object"?_t(e.name):""}catch{return""}}function ve(e){return typeof e=="function"||e!==null&&typeof e=="object"}function Nt(e,t){let n=e,r=Symbol.for(tt);try{let c=n[r];if(c)return c}catch{}let o=!1;try{o=n.document?.readyState==="loading"}catch{}let i=new Z(t,o),s,a,u={},f=0;function d(c){f++;try{return c()}finally{f--}}function x(){return f===0&&typeof s=="function"&&s!==N?s:void 0}function w(){return f===0&&typeof a=="function"&&a!==lt?a:void 0}function m(c){if(f!==0)return;let l;try{s&&s!==N&&(l=s[c])}catch{l=void 0}return typeof l!="function"&&(l=u[c]),typeof l=="function"&&l!==ft[c]?l:void 0}let O=new WeakSet;function j(c,l){try{if(c===null||typeof c!="object"||O.has(c))return c;let p=c,g=p.dispatch,b=p.getState;if(typeof g!="function"||typeof b!="function")return c;let k=()=>M(b,p,[]),h=i.addEnhancerStore(l,k);if(!h)return c;let y=[],R=S=>{S.done=!0;let v;try{v=k()}catch{return}i.action(h,S.action,v)};try{p.subscribe?.(()=>{let S=y[y.length-1];S&&!S.done&&R(S)})}catch{}let dt=(S,v,It)=>{if(!i.recording)return M(S,p,v);let Q={action:It,done:!1};y.push(Q);let ht;try{ht=M(S,p,v)}finally{y.pop()}return Q.done||R(Q),ht},X={...p};X.dispatch=function(...v){return dt(g,v,v[0])};let pt=p.replaceReducer;return typeof pt=="function"&&(X.replaceReducer=function(...v){return dt(pt,v,{type:"@@redux/REPLACE"})}),O.add(X),X}catch{return c}}function A(c,l){let p,g=l?x():void 0;g&&(p=d(()=>g(c)));let b=Mt(c);return k=>{let h=typeof p=="function"?p(k):k;return(...y)=>j(h(...y),b)}}function D(c){return(...l)=>{let p=w();if(p){let g=d(()=>p(c));if(typeof g=="function")return g(...l,A(c,!1))}return g=>l.reduceRight((b,k)=>k(b),A(c,!0)(g))}}function B(c){let l=m("connect"),p=l?d(()=>M(l,s,[c])):void 0,g;try{g=i.addConnection(Mt(c))}catch{g=void 0}let b=(h,y)=>{let R=p?.[h];return typeof R=="function"?M(R,p,y):void 0},k={init(...h){return g&&i.connInit(g,h[0]),b("init",h)},send(...h){return g&&i.connSend(g,h[0],h[1]),b("send",h)},subscribe(...h){return typeof p?.subscribe=="function"?b("subscribe",h):Se},unsubscribe(...h){return b("unsubscribe",h)},error(...h){return b("error",h)}};if(p&&typeof p=="object")try{for(let h of ke(p)){if(h in k)continue;let y=p[h];k[h]=typeof y=="function"?(...R)=>M(y,p,R):y}}catch{}return k}let T=c=>function(...l){let p=m(c);return p?d(()=>M(p,s,l)):void 0},ft={connect:(...c)=>B(c[0]),disconnect:T("disconnect"),send:T("send"),listen:T("listen"),open:T("open"),notifyErrors:T("notifyErrors")},N=function(l){return A(l,!0)};for(let c of ye)ct(N,c,{configurable:!0,enumerable:!0,get:()=>ft[c],set:l=>{if(s&&s!==N)try{s[c]=l;return}catch{}u[c]=l}});let lt=function(...l){return l.length===0?N():l.length===1&&typeof l[0]=="object"?D(l[0]??{}):D({})(...l)};jt(n,we,N,c=>{s=ve(c)?c:void 0}),jt(n,be,lt,c=>{a=typeof c=="function"?c:void 0});try{ct(n,r,{value:i.api,configurable:!0,enumerable:!1,writable:!1})}catch{}return i.api}function jt(e,t,n,r){try{let o=me(e,t);if(o&&!o.configurable)return;if(o){let i=o.get?o.get.call(e):o.value;i!==n&&r(i)}ct(e,t,{configurable:!0,enumerable:!1,get:()=>n,set:i=>{i!==n&&r(i)}})}catch{}}var Ee=Date.now,Te=setTimeout,xe=clearTimeout;Nt(window,{now:()=>Ee(),setTimeout:(e,t)=>Te.call(window,e,t),clearTimeout:e=>xe.call(window,e)});})();
2
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/protocol.ts","../src/recorder/util.ts","../src/recorder/track.ts","../src/state/json.ts","../src/state/diff.ts","../src/state/engine.ts","../src/state/hook.ts","../src/state/index.ts"],"sourcesContent":["// Messages between the extension's parts.\n//\n// recorder.js (page MAIN world) --DOM CustomEvents--> bridge.js (isolated world)\n// bridge.js --chrome.runtime messages--> background.js --HTTP--> tabtape (Repoburg daemon, /tabtape)\n\n/** DOM event names used between the recorder (MAIN world) and the bridge. */\nexport const DOM_EVENT = {\n /** recorder -> bridge, detail: one NDJSON line */\n data: '__tabtape:data',\n /** recorder -> bridge: send what you have now (page is going away) */\n flush: '__tabtape:flush',\n /** recorder -> bridge: I am loaded, tell me what to do */\n hello: '__tabtape:hello',\n /** bridge -> recorder, detail: JSON ControlMessage */\n control: '__tabtape:control',\n /** bridge -> recorder: report PageInfo now (answered synchronously with infoReply) */\n info: '__tabtape:info',\n /** recorder -> bridge, detail: JSON PageInfo */\n infoReply: '__tabtape:info-reply',\n} as const;\n\n/** The state hook bundle (state.js) publishes its API on window under this symbol. */\nexport const STATE_HOOK_KEY = 'tabtape.state';\n\nexport interface RecorderOptions {\n captureBodies: boolean;\n maxBodyChars: number;\n /** Periodic full snapshots keep replays usable after ring buffer eviction. */\n checkoutEveryNms: number;\n /**\n * In-page network capture (fetch, XHR, beacons, Resource and Navigation\n * Timing). Off while deep mode records the tab's network through the\n * debugger, so no request is written twice.\n */\n inPageNetwork: boolean;\n /** Redux / Zustand state recording; needs the state hook (state.js) on the page. */\n recordState: boolean;\n}\n\n/** What the recorder knows about its page; the popup asks for it (ST-R13). */\nexport interface PageInfo {\n /**\n * early: the Redux DevTools hook was defined before the page's scripts ran;\n * late: injected into a page that was already open (stores created at load\n * missed it); off: state recording is off (no state.js on the page).\n */\n stateHook: 'early' | 'late' | 'off';\n /** Names of the stores connected to the hook. */\n stores: string[];\n}\n\n/** Where a tab's lines go: a recording, or the tab's buffer (buffer mode). */\nexport type Target = { kind: 'session'; id: string } | { kind: 'buffer'; key: string; windowMs: number };\n\nexport type ControlMessage =\n | { cmd: 'start'; sessionId: string; options: RecorderOptions }\n | { cmd: 'stop' }\n | { cmd: 'disable' };\n\n/** How often the bridge sends its lines; faster while someone watches the recording live. */\nexport const FLUSH_MS = { normal: 1000, live: 250 };\n\nexport type Decision =\n | {\n record: true;\n target: Target;\n options: RecorderOptions;\n /** Bridge flush interval, when not FLUSH_MS.normal. */\n flushMs?: number;\n }\n | { record: false };\n\nexport type RuntimeMessage =\n | { type: 'hello' }\n | { type: 'batch'; target: Target; lines: string }\n | { type: 'status'; tabId?: number }\n /** deep: record this recording's network through the debugger (deep mode, NET-R1) */\n | { type: 'start'; tabId: number; deep?: boolean }\n | { type: 'stop' }\n | { type: 'save-buffer'; tabId: number }\n | { type: 'settings-changed' };\n\n/** background -> bridge */\nexport type BridgeMessage =\n /** stop recording; the response carries the lines not sent yet */\n | { type: 'stop' }\n /** hand over the lines not sent yet without stopping */\n | { type: 'take' }\n /** live viewers came or went: send batches every `ms` */\n | { type: 'flush-interval'; ms: number }\n /** the recording's options changed (deep mode ended): ask for a new decision */\n | { type: 'redecide' }\n /** popup -> bridge: the response is the recorder's PageInfo, or null without a recorder */\n | { type: 'page-info' };\n\nexport interface Settings {\n /** tabtape's server with its path, no trailing slash: URLs are built as `${serverUrl}/api/…`. */\n serverUrl: string;\n captureBodies: boolean;\n checkoutMinutes: number;\n /** Buffer mode: keep the last minutes of allow-listed tabs on the server. */\n bufferEnabled: boolean;\n /** Chrome match patterns, e.g. http://localhost/* */\n bufferSites: string[];\n bufferMinutes: number;\n /** Recordings started on a buffered tab begin with the buffered minutes. */\n startWithBuffer: boolean;\n /** Record Redux / Zustand stores through the Redux DevTools hook (ST-R1). */\n recordState: boolean;\n}\n\nexport const DEFAULT_SETTINGS: Settings = {\n // tabtape runs inside the Repoburg daemon, mounted under /tabtape.\n serverUrl: 'http://127.0.0.1:9998/tabtape',\n captureBodies: true,\n checkoutMinutes: 5,\n bufferEnabled: false,\n bufferSites: ['http://localhost/*', 'http://127.0.0.1/*'],\n bufferMinutes: 2,\n startWithBuffer: true,\n recordState: true,\n};\n\n/** The default server URL of tabtape's former standalone server. */\nconst STANDALONE_SERVER_URL = 'http://127.0.0.1:7777';\n\n/**\n * Stored settings over the defaults. The server URL loses trailing slashes;\n * one saved with the standalone server's default moves to the daemon.\n */\nexport function withDefaults(stored: Partial<Settings> | undefined): Settings {\n const s: Settings = { ...DEFAULT_SETTINGS, ...stored };\n const url = typeof s.serverUrl === 'string' ? s.serverUrl.trim().replace(/\\/+$/, '') : '';\n s.serverUrl = !url || url === STANDALONE_SERVER_URL ? DEFAULT_SETTINGS.serverUrl : url;\n return s;\n}\n\nexport const BUFFER_MINUTES = { min: 1, max: 10 };\n\n/** A checkout every quarter window (15 s – 1 min) keeps every save replayable. */\nexport function bufferCheckoutMs(windowMs: number): number {\n return Math.min(Math.max(windowMs / 4, 15_000), 60_000);\n}\n\nexport const MAX_BODY_CHARS = 1_000_000;\n\nexport interface Recording {\n sessionId: string;\n tabId: number;\n startedAt: number;\n serverUrl: string;\n title?: string;\n url?: string;\n /** Deep mode (NET-R1): set when the recording was started with it. */\n deep?: DeepStatus;\n}\n\n/**\n * on: the debugger is attached and records the tab's network;\n * ended: it detached early (the user canceled Chrome's debugging bar, the tab\n * crashed, the extension restarted) and the page captures requests again.\n */\nexport interface DeepStatus {\n state: 'on' | 'ended';\n /** Why it ended, in words for the popup. */\n reason?: string;\n}\n\nexport interface BufferTab {\n key: string;\n /** The tab currently shows an allow-listed page and is being buffered. */\n active: boolean;\n url?: string;\n}\n\nexport interface Status {\n recording: Recording | null;\n lastSession: { sessionId: string; serverUrl: string } | null;\n server: { ok: boolean; url: string; version?: string; error?: string };\n stats: { lines: number; bytes: number; queuedBytes: number; dropped: number; lastError?: string };\n buffer: { enabled: boolean; minutes: number; tab: BufferTab | null; error?: string };\n}\n\nexport type Result<T = object> = ({ ok: true } & T) | { ok: false; error: string };\n","// Helpers for code running in the page's MAIN world. The recorder is injected\n// at document_start, before page scripts run, so the references captured here\n// are the browser's own even if the page later patches the globals.\n\nexport const stringify = JSON.stringify;\nexport const parse = JSON.parse;\nconst toStringTag = Object.prototype.toString;\n\nexport const MASK = '********';\nconst PASSWORD_KEY = /passw(or)?d|^pwd$|^pass$/i;\n\n/** Bodies with these content types are recorded as text (in the page and in deep mode). */\nexport const TEXT_MIME = /json|text\\/|xml|javascript|ecmascript|graphql|x-www-form-urlencoded|csv|yaml/i;\n\nexport const clip = (s: string, n: number): string => (s.length > n ? s.slice(0, n - 1) + '…' : s);\nexport const round = (n: number): number => Math.round(n * 10) / 10;\n\nexport function isPasswordKey(key: string): boolean {\n return PASSWORD_KEY.test(key);\n}\n\nexport function absoluteUrl(url: string): string {\n try {\n return new URL(url, location.href).href;\n } catch {\n return url;\n }\n}\n\n/**\n * The URL a resource element (img, script, link, media) failed to load,\n * redacted and at most 1000 characters (data: URLs): currentSrc names the\n * image picked from srcset or <picture>, the src and href properties are\n * resolved against <base>. Elements without a string property (SVG <image>)\n * fall back to the attribute. Empty when there is none.\n */\nexport function resourceUrl(el: Pick<Element, 'getAttribute'>): string {\n const props = el as unknown as Record<string, unknown>;\n let url = '';\n for (const key of ['currentSrc', 'src', 'href']) {\n const v = props[key];\n if (typeof v === 'string' && v) {\n url = v;\n break;\n }\n }\n if (!url) {\n const attr = el.getAttribute('src') || el.getAttribute('href');\n if (attr) url = absoluteUrl(attr);\n }\n return url ? clip(redactUrl(url), 1000) : '';\n}\n\n/** Masks password-like query parameters. */\nexport function redactParams(query: string): string {\n const params = new URLSearchParams(query);\n let changed = false;\n for (const key of new Set(params.keys())) {\n if (isPasswordKey(key)) {\n params.set(key, MASK);\n changed = true;\n }\n }\n return changed ? params.toString() : query;\n}\n\nexport function redactUrl(url: string): string {\n const q = url.indexOf('?');\n if (q < 0) return url;\n const hash = url.indexOf('#', q);\n const query = url.slice(q + 1, hash < 0 ? undefined : hash);\n const redacted = redactParams(query);\n return redacted === query ? url : url.slice(0, q + 1) + redacted + (hash < 0 ? '' : url.slice(hash));\n}\n\nfunction redactValue(v: unknown): boolean {\n let changed = false;\n if (Array.isArray(v)) {\n for (const item of v) changed = redactValue(item) || changed;\n } else if (v && typeof v === 'object') {\n const obj = v as Record<string, unknown>;\n for (const key of Object.keys(obj)) {\n const val = obj[key];\n if (isPasswordKey(key) && (typeof val === 'string' || typeof val === 'number')) {\n obj[key] = MASK;\n changed = true;\n } else {\n changed = redactValue(val) || changed;\n }\n }\n }\n return changed;\n}\n\nconst QUERY_LIKE = /^[\\w.%[\\]-]+=[^&]*(&[\\w.%[\\]-]+=[^&]*)*$/;\nconst MULTIPART = /multipart\\/form-data/i;\n/** A multipart body without its content type: \"--boundary\" and a form-data part. */\nconst MULTIPART_START = /^--[^\\r\\n]{1,200}\\r?\\ncontent-disposition:\\s*form-data/i;\n\n/** Masks password fields in JSON, form-encoded and multipart/form-data request bodies. */\nexport function redactBody(text: string, contentType = ''): string {\n const t = text.trimStart();\n if (t.startsWith('{') || t.startsWith('[')) {\n try {\n const value = parse(text);\n return redactValue(value) ? stringify(value) : text;\n } catch {\n return text;\n }\n }\n if (MULTIPART.test(contentType) || MULTIPART_START.test(t)) return redactMultipart(text, contentType);\n if (/x-www-form-urlencoded/i.test(contentType) || (t.length < 100_000 && QUERY_LIKE.test(t))) {\n return redactParams(text);\n }\n return text;\n}\n\n/**\n * Masks the values of password fields in a multipart/form-data body. The\n * boundary comes from the content type or the body's first line; file parts\n * are left alone.\n */\nexport function redactMultipart(text: string, contentType = ''): string {\n const m = /boundary=(?:\"([^\"]+)\"|([^;,\\s]+))/i.exec(contentType);\n const boundary = m?.[1] ?? m?.[2] ?? /^--([^\\r\\n]+)/.exec(text.trimStart())?.[1];\n if (!boundary) return text;\n const delimiter = `--${boundary}`;\n const parts = text.split(delimiter);\n let changed = false;\n for (let i = 1; i < parts.length; i++) {\n const part = parts[i];\n const sep = /\\r?\\n\\r?\\n/.exec(part);\n if (!sep) continue;\n const head = part.slice(0, sep.index);\n if (/;\\s*filename\\*?=/i.test(head)) continue;\n const name = /;\\s*name=\"([^\"]*)\"/i.exec(head)?.[1] ?? /;\\s*name=([^;\\s]+)/i.exec(head)?.[1];\n if (name === undefined || !isPasswordKey(name)) continue;\n const start = sep.index + sep[0].length;\n // The line break before the next delimiter belongs to the delimiter.\n const end = part.endsWith('\\r\\n') ? part.length - 2 : part.endsWith('\\n') ? part.length - 1 : part.length;\n parts[i] = part.slice(0, start) + MASK + part.slice(Math.max(start, end));\n changed = true;\n }\n return changed ? parts.join(delimiter) : text;\n}\n\n/** \"key\": value pairs (a string or a number) anywhere in a text. */\nconst KEY_VALUE = /(\"([^\"\\\\]*)\"\\s*:\\s*)(\"(?:[^\"\\\\]|\\\\.)*\"|-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)/g;\n\n/**\n * Masks password values in a text that may hold JSON, like a WebSocket\n * message: JSON is parsed and its password keys masked; other texts get the\n * same for every \"password\": \"…\" pair a regex finds (Socket.IO messages such\n * as 42[\"login\",{\"password\":\"…\"}] are not JSON) and for form-encoded text.\n */\nexport function maskPasswordsInText(text: string): string {\n const t = text.trimStart();\n if (t.startsWith('{') || t.startsWith('[')) {\n try {\n const value = parse(text);\n return redactValue(value) ? stringify(value) : text;\n } catch {\n // not JSON after all: look for pairs below\n }\n }\n if (!/pass|pwd/i.test(text)) return text;\n if (t.length < 100_000 && QUERY_LIKE.test(t)) return redactParams(text);\n return text.replace(KEY_VALUE, (pair: string, prefix: string, key: string) => (isPasswordKey(key) ? `${prefix}\"${MASK}\"` : pair));\n}\n\n// ---- serialization of arbitrary values (console arguments, tabtape.track props) ----\n\nexport interface SerializeLimits {\n /** Objects nested deeper than this become \"[Object]\" / \"[Array(n)]\". */\n depth: number;\n /** Array items and object keys kept per level. */\n items: number;\n /** Characters kept per string. */\n string: number;\n}\n\n/** The console argument limits. */\nexport const SERIALIZE_LIMITS: SerializeLimits = { depth: 4, items: 50, string: 2000 };\n\ninterface ErrorLike {\n name: string;\n message: string;\n stack?: string;\n}\n\nexport function isErrorLike(v: unknown): v is ErrorLike {\n if (!v || typeof v !== 'object') return false;\n if (v instanceof Error) return true;\n const e = v as Partial<ErrorLike>;\n return typeof e.message === 'string' && typeof e.name === 'string' && 'stack' in e;\n}\n\nfunction describeNode(node: Node): string {\n if (node.nodeType === 1) {\n const el = node as Element;\n let s = '<' + el.tagName.toLowerCase();\n if (el.id) s += '#' + el.id;\n const cls = el.getAttribute('class')?.trim();\n if (cls) s += '.' + cls.split(/\\s+/).slice(0, 3).join('.');\n return s + '>';\n }\n if (node.nodeType === 3) return `#text \"${clip(node.textContent ?? '', 60)}\"`;\n return node.nodeName;\n}\n\n/** Converts any value into a bounded, JSON-safe structure. */\nexport function serialize(value: unknown, limits: SerializeLimits = SERIALIZE_LIMITS): unknown {\n return walk(value, 0, new WeakSet(), limits);\n}\n\nfunction walk(value: unknown, depth: number, seen: WeakSet<object>, lim: SerializeLimits): unknown {\n switch (typeof value) {\n case 'string':\n return clip(value, lim.string);\n case 'number':\n return Number.isFinite(value) ? value : String(value);\n case 'boolean':\n return value;\n case 'undefined':\n return 'undefined';\n case 'bigint':\n return `${value}n`;\n case 'symbol':\n return value.toString();\n case 'function':\n return `ƒ ${value.name || 'anonymous'}()`;\n }\n if (value === null) return null;\n const obj = value as object;\n if (seen.has(obj)) return '[Circular]';\n try {\n if (isErrorLike(obj)) {\n return { name: obj.name, message: clip(String(obj.message), lim.string), stack: clip(String(obj.stack ?? ''), Math.min(4000, lim.string * 2)) };\n }\n const n = obj as Node;\n if (typeof n.nodeType === 'number' && typeof n.nodeName === 'string') return describeNode(n);\n if ((obj as Window).window === obj) return '[Window]';\n const tag = toStringTag.call(obj).slice(8, -1);\n if (tag === 'Date') {\n const d = obj as Date;\n return Number.isNaN(d.getTime()) ? 'Invalid Date' : d.toISOString();\n }\n if (tag === 'RegExp') return String(obj);\n if (tag === 'Promise') return '[Promise]';\n if (obj instanceof ArrayBuffer || ArrayBuffer.isView(obj)) return `[${tag} ${(obj as ArrayBuffer).byteLength} bytes]`;\n if (depth >= lim.depth) return Array.isArray(obj) ? `[Array(${obj.length})]` : `[${tag}]`;\n\n seen.add(obj);\n if (Array.isArray(obj)) {\n const out = obj.slice(0, lim.items).map((v) => walk(v, depth + 1, seen, lim));\n if (obj.length > lim.items) out.push(`… ${obj.length - lim.items} more`);\n return out;\n }\n if (tag === 'Map') return { '[[Map]]': walk([...(obj as Map<unknown, unknown>)].slice(0, lim.items), depth + 1, seen, lim) };\n if (tag === 'Set') return { '[[Set]]': walk([...(obj as Set<unknown>)].slice(0, lim.items), depth + 1, seen, lim) };\n\n const out: Record<string, unknown> = {};\n const keys = Object.keys(obj);\n for (const key of keys.slice(0, lim.items)) {\n try {\n out[key] = walk((obj as Record<string, unknown>)[key], depth + 1, seen, lim);\n } catch {\n out[key] = '[unreadable]';\n }\n }\n if (keys.length > lim.items) out['…'] = `${keys.length - lim.items} more keys`;\n return out;\n } catch {\n return '[unserializable]';\n }\n}\n\nfunction preview(original: unknown, serialized: unknown): string {\n if (typeof original === 'string') return original;\n if (isErrorLike(original)) return `${original.name}: ${original.message}`;\n if (typeof serialized === 'string') return serialized;\n try {\n return clip(stringify(serialized) ?? String(serialized), 1000);\n } catch {\n return String(serialized);\n }\n}\n\n/** Formats console arguments roughly like DevTools does, including %s/%d/%o/%c. */\nexport function formatArgs(args: unknown[], serialized: unknown[]): string {\n const parts: string[] = [];\n let i = 0;\n if (typeof args[0] === 'string' && args[0].includes('%')) {\n i = 1;\n parts.push(\n args[0].replace(/%([sdifoOc%])/g, (m, f: string) => {\n if (f === '%') return '%';\n if (i >= args.length) return m;\n const idx = i++;\n const a = args[idx];\n switch (f) {\n case 'c':\n return '';\n case 'd':\n case 'i':\n return String(Number.parseInt(String(a), 10));\n case 'f':\n return String(Number(a));\n default:\n return preview(a, serialized[idx]);\n }\n }),\n );\n }\n for (; i < args.length; i++) parts.push(preview(args[i], serialized[i]));\n return clip(parts.join(' '), 5000);\n}\n\n/** Drops the recorder's own frames from a stack trace. */\nexport function cleanStack(stack: string | undefined): string | undefined {\n if (!stack) return undefined;\n const lines = stack.split('\\n').filter((l, idx) => idx > 0 && !l.includes('chrome-extension://'));\n return lines.length ? clip(lines.join('\\n'), 4000) : undefined;\n}\n","// The recorded page's own events and user id:\n//\n// window.tabtape?.track('checkout_started', { plan: 'pro', items: 3 });\n// window.tabtape?.identify('u_123', { role: 'admin' });\n// window.tabtape?.state('cart', { type: 'cart/add' }, store.getState());\n// dispatchEvent(new CustomEvent('tabtape:track', { detail: { name: 'checkout_started', props: { plan: 'pro' } } }));\n// dispatchEvent(new CustomEvent('tabtape:identify', { detail: { userId: 'u_123', traits: { role: 'admin' } } }));\n// dispatchEvent(new CustomEvent('tabtape:state', { detail: { store: 'cart', action: 'cart/add', state } }));\n//\n// tabtape.state() records a store the Redux DevTools hook does not see (MobX, a\n// production build); the state hook (state.js) does the work, so it needs\n// state recording on in the extension's settings.\n//\n// window.tabtape is defined once the page is recorded (session or buffer), and\n// only if the page has no `tabtape` of its own; the CustomEvents work either way.\n// Outside a recording calls are ignored, and nothing here throws into the page.\n//\n// Validation, the 8KB cap and the rate limit live in createTracker() and the\n// helpers below, which need no browser (see track.test.ts).\n\nimport type { IdentifyEvent, TrackEvent } from '../../../shared/events';\nimport { getStateHook } from '../state/api';\nimport { isPasswordKey, MASK, redactUrl, serialize, SERIALIZE_LIMITS, stringify, type SerializeLimits } from './util';\n\nexport type TabtapeRecord = TrackEvent | IdentifyEvent;\ntype Emit = (data: TabtapeRecord, ts: number) => void;\n\nexport const TRACK_EVENT = 'tabtape:track';\nexport const IDENTIFY_EVENT = 'tabtape:identify';\nexport const STATE_EVENT = 'tabtape:state';\nexport const NAME_MAX = 100;\nexport const USER_ID_MAX = 200;\nexport const PROPS_MAX_BYTES = 8 * 1024;\nexport const RATE_LIMIT = { calls: 100, perMs: 1000 };\n\n/** The console argument limits first; the tighter ones only when props exceed 8KB. */\nexport const PROPS_LEVELS: readonly SerializeLimits[] = [\n SERIALIZE_LIMITS,\n { depth: 4, items: 50, string: 500 },\n { depth: 4, items: 20, string: 200 },\n { depth: 3, items: 10, string: 100 },\n { depth: 2, items: 10, string: 50 },\n];\n\n/** The key serialize() uses for \"N more keys\". */\nconst MORE = '…';\n\n// ---------- validation and limits ----------\n\n/** The trimmed event name if it has 1–100 characters, else undefined. */\nexport function eventName(name: unknown): string | undefined {\n if (typeof name !== 'string') return undefined;\n const s = name.trim();\n // Characters, not UTF-16 units: an emoji counts once.\n return s && s.length <= NAME_MAX * 2 && [...s].length <= NAME_MAX ? s : undefined;\n}\n\n/** A non-empty string or a number as a string, at most 200 characters; else undefined. */\nexport function userIdOf(id: unknown): string | undefined {\n let s: string;\n if (typeof id === 'string') s = id.trim();\n else if ((typeof id === 'number' && Number.isFinite(id)) || typeof id === 'bigint') s = String(id);\n else return undefined;\n if (!s) return undefined;\n if (s.length <= USER_ID_MAX) return s;\n // Don't cut a surrogate pair in half.\n const end = /[\\uD800-\\uDBFF]/.test(s[USER_ID_MAX - 1]) ? USER_ID_MAX - 1 : USER_ID_MAX;\n return s.slice(0, end);\n}\n\n/** UTF-8 length of a string. */\nexport function utf8Bytes(s: string): number {\n let n = 0;\n for (let i = 0; i < s.length; i++) {\n const c = s.charCodeAt(i);\n if (c < 0x80) n += 1;\n else if (c < 0x800) n += 2;\n else if (c >= 0xd800 && c <= 0xdbff && (s.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {\n n += 4;\n i++;\n } else n += 3;\n }\n return n;\n}\n\n/**\n * Replaces password-like values (same key rule as request bodies) with\n * \"********\", at any depth: strings and numbers under such a key, including\n * everything nested inside it. Works in place on serialize() output.\n */\nexport function maskPasswords(v: unknown, masked = false): unknown {\n if (Array.isArray(v)) {\n for (let i = 0; i < v.length; i++) v[i] = maskPasswords(v[i], masked);\n return v;\n }\n if (v && typeof v === 'object') {\n const obj = v as Record<string, unknown>;\n for (const key of Object.keys(obj)) {\n const val = obj[key];\n if (key === '[[Map]]' && Array.isArray(val)) {\n // serialize() writes Maps as {\"[[Map]]\": [[key, value], …]}\n obj[key] = val.map((pair: unknown) =>\n Array.isArray(pair) && pair.length === 2 && typeof pair[0] === 'string'\n ? [pair[0], maskPasswords(pair[1], masked || isPasswordKey(pair[0]))]\n : maskPasswords(pair, masked),\n );\n } else {\n obj[key] = maskPasswords(val, masked || isPasswordKey(key));\n }\n }\n return obj;\n }\n return masked && (typeof v === 'string' || typeof v === 'number') ? MASK : v;\n}\n\nexport interface CappedProps {\n props?: Record<string, unknown>;\n /** The props had to be shortened to fit 8KB. */\n truncated?: true;\n}\n\n/**\n * Serializes props or traits like console arguments, masks password keys and\n * keeps the JSON within 8KB: tighter limits first, then trailing keys are\n * dropped. Values that are not plain objects are kept as {value: …}.\n */\nexport function capProps(value: unknown): CappedProps {\n if (value === undefined || value === null) return {};\n let props: Record<string, unknown> = {};\n for (const [i, limits] of PROPS_LEVELS.entries()) {\n props = toRecord(maskPasswords(serialize(value, limits)));\n if (jsonBytes(props) <= PROPS_MAX_BYTES) return pack(props, i > 0);\n }\n // Only very long keys get here: serialize() does not shorten keys.\n return pack(dropTrailingKeys(props), true);\n}\n\nfunction toRecord(v: unknown): Record<string, unknown> {\n return v !== null && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : { value: v };\n}\n\nfunction pack(props: Record<string, unknown>, truncated: boolean): CappedProps {\n const out: CappedProps = {};\n if (Object.keys(props).length) out.props = props;\n if (truncated) out.truncated = true;\n return out;\n}\n\nfunction jsonBytes(v: unknown): number {\n return utf8Bytes(stringify(v) ?? '');\n}\n\nfunction dropTrailingKeys(props: Record<string, unknown>): Record<string, unknown> {\n const marker = props[MORE];\n const alreadyDropped = typeof marker === 'string' ? Number.parseInt(marker, 10) || 0 : 0;\n const keys = Object.keys(props).filter((k) => k !== MORE);\n const out: Record<string, unknown> = {};\n let kept = 0;\n for (const key of keys) {\n out[key] = props[key];\n const more = keys.length - kept - 1 + alreadyDropped;\n if (jsonBytes(more ? { ...out, [MORE]: `${more} more keys` } : out) > PROPS_MAX_BYTES) {\n delete out[key];\n break;\n }\n kept++;\n }\n const more = keys.length - kept + alreadyDropped;\n if (more) out[MORE] = `${more} more keys`;\n return out;\n}\n\n/** Sliding window: at most `calls` accepted in any `perMs` milliseconds. */\nexport class RateLimiter {\n private readonly calls: number;\n private readonly perMs: number;\n private readonly clock: () => number;\n /** Times of the last accepted calls; a ring once full, `oldest` is its start. */\n private readonly times: number[] = [];\n private oldest = 0;\n\n constructor(calls: number, perMs: number, clock: () => number) {\n this.calls = calls;\n this.perMs = perMs;\n this.clock = clock;\n }\n\n take(): boolean {\n const now = this.clock();\n if (this.times.length < this.calls) {\n this.times.push(now);\n return true;\n }\n if (now - this.times[this.oldest] < this.perMs) return false;\n this.times[this.oldest] = now;\n this.oldest = (this.oldest + 1) % this.calls;\n return true;\n }\n}\n\n// ---------- the tracker ----------\n\nexport interface TrackerOptions {\n emit: Emit;\n /** Record timestamps, epoch ms. */\n now?: () => number;\n /** Monotonic milliseconds for the rate limit. */\n clock?: () => number;\n /** The page URL written into each record. */\n url?: () => string;\n warn?: (message: string) => void;\n}\n\nexport interface Tracker {\n readonly active: boolean;\n setActive(on: boolean): void;\n track(name: unknown, props?: unknown): void;\n identify(userId: unknown, traits?: unknown): void;\n}\n\nexport function createTracker(o: TrackerOptions): Tracker {\n const now = o.now ?? Date.now;\n const url = o.url ?? (() => redactUrl(location.href));\n const warn = o.warn ?? ((message: string) => console.warn(message));\n const limiter = new RateLimiter(RATE_LIMIT.calls, RATE_LIMIT.perMs, o.clock ?? (() => performance.now()));\n const warned = new Set<'track' | 'identify'>();\n let active = false;\n let dropped = 0;\n let busy = false; // a props getter may call back into tabtape\n\n function run(fn: () => void): void {\n if (!active || busy) return;\n busy = true;\n try {\n fn();\n } catch {\n // never break the page\n } finally {\n busy = false;\n }\n }\n\n /** One warning per page and kind, so a bad call in a loop does not flood the console. */\n function warnOnce(kind: 'track' | 'identify', message: () => string): void {\n if (warned.has(kind)) return;\n warned.add(kind);\n try {\n warn(message());\n } catch {\n // ignore\n }\n }\n\n function admit(): boolean {\n if (limiter.take()) return true;\n dropped++;\n return false;\n }\n\n function write(data: TabtapeRecord): void {\n if (dropped) {\n data.dropped = dropped;\n dropped = 0;\n }\n o.emit(data, now());\n }\n\n return {\n get active() {\n return active;\n },\n setActive(on: boolean) {\n if (on && !active) dropped = 0; // a new recording starts without the old count\n active = on;\n },\n track(name: unknown, props?: unknown) {\n run(() => {\n const event = eventName(name);\n if (event === undefined) {\n warnOnce('track', () => `tabtape.track: the event name must be a string of 1–${NAME_MAX} characters, got ${describe(name)}. The event was not recorded.`);\n return;\n }\n if (!admit()) return;\n const capped = capProps(props);\n const data: TrackEvent = { name: 'custom', event, url: url() };\n if (capped.props) data.props = capped.props;\n if (capped.truncated) data.propsTruncated = true;\n write(data);\n });\n },\n identify(id: unknown, traits?: unknown) {\n run(() => {\n const userId = userIdOf(id);\n if (userId === undefined) {\n warnOnce('identify', () => `tabtape.identify: the user id must be a non-empty string or a number, got ${describe(id)}. The call was ignored.`);\n return;\n }\n if (!admit()) return;\n const capped = capProps(traits);\n const data: IdentifyEvent = { name: 'identify', userId, url: url() };\n if (capped.props) data.traits = capped.props;\n if (capped.truncated) data.propsTruncated = true;\n write(data);\n });\n },\n };\n}\n\nfunction describe(v: unknown): string {\n if (typeof v === 'string') return v.trim() ? `${[...v.trim()].length} characters` : 'an empty string';\n return v === null ? 'null' : typeof v;\n}\n\n// ---------- the page side ----------\n\ninterface TabtapeApi {\n track(name: string, props?: Record<string, unknown>): void;\n identify(userId: string | number, traits?: Record<string, unknown>): void;\n state(store: string, action: string | { type: string }, state: unknown): void;\n readonly isRecording: boolean;\n}\n\nlet emit: Emit = () => {};\nlet tracker: Tracker | undefined;\nlet api: TabtapeApi | undefined;\nlet listening = false;\nlet warnedState = false;\n\nexport function start(onEvent: Emit): void {\n emit = onEvent;\n tracker ??= createTracker({ emit: (d, ts) => emit(d, ts) });\n tracker.setActive(true);\n define(tracker);\n if (!listening) {\n listening = true;\n addEventListener(TRACK_EVENT, onTrackEvent);\n addEventListener(IDENTIFY_EVENT, onIdentifyEvent);\n addEventListener(STATE_EVENT, onStateEvent);\n }\n}\n\n/** window.tabtape stays (isRecording turns false) for code that kept a reference. */\nexport function stop(): void {\n tracker?.setActive(false);\n if (!listening) return;\n listening = false;\n removeEventListener(TRACK_EVENT, onTrackEvent);\n removeEventListener(IDENTIFY_EVENT, onIdentifyEvent);\n removeEventListener(STATE_EVENT, onStateEvent);\n}\n\n/** Defines window.tabtape unless the page has a `tabtape` already (its own object, a global, an element id). */\nfunction define(t: Tracker): void {\n try {\n if (api && (window as unknown as { tabtape?: unknown }).tabtape === api) return;\n if ('tabtape' in window) return;\n api ??= Object.freeze({\n track(name: string, props?: Record<string, unknown>) {\n t.track(name, props);\n },\n identify(userId: string | number, traits?: Record<string, unknown>) {\n t.identify(userId, traits);\n },\n state(store: string, action: string | { type: string }, state: unknown) {\n recordState(t, store, action, state);\n },\n get isRecording() {\n return t.active;\n },\n });\n // Writable and configurable: page code assigning its own window.tabtape later still can.\n Object.defineProperty(window, 'tabtape', { value: api, configurable: true, writable: true, enumerable: false });\n } catch {\n // the CustomEvent path still works\n }\n}\n\nfunction detailOf(e: Event): Record<string, unknown> {\n const d: unknown = (e as CustomEvent).detail;\n return d !== null && typeof d === 'object' ? (d as Record<string, unknown>) : {};\n}\n\nfunction onTrackEvent(e: Event): void {\n try {\n const d = detailOf(e);\n tracker?.track(d.name, d.props);\n } catch {\n // a hostile detail (throwing getter) must not surface\n }\n}\n\nfunction onIdentifyEvent(e: Event): void {\n try {\n const d = detailOf(e);\n tracker?.identify(d.userId, d.traits);\n } catch {\n // ignore\n }\n}\n\nfunction onStateEvent(e: Event): void {\n try {\n const d = detailOf(e);\n if (tracker) recordState(tracker, d.store, d.action, d.state);\n } catch {\n // ignore\n }\n}\n\n/** Hands a tabtape.state() call to the state hook; ignored outside a recording. */\nfunction recordState(t: Tracker, store: unknown, action: unknown, state: unknown): void {\n if (!t.active) return;\n try {\n const hook = getStateHook();\n if (hook) {\n hook.manual(store, action, state);\n } else if (!warnedState) {\n warnedState = true;\n console.warn('tabtape.state: state recording is off in the tabtape extension settings; the call was ignored.');\n }\n } catch {\n // never break the page\n }\n}\n","// JSON text for state records, written in one bounded pass (ST-R9, ST-R11).\n//\n// Values: Dates become ISO strings, Maps and Sets {\"[[Map]]\": [[k, v], …]}\n// and {\"[[Set]]\": […]} (like util.serialize), BigInts \"123n\", Errors\n// {name, message}; functions, undefined and symbols are dropped from objects\n// and null in arrays (like JSON); class instances keep their own enumerable\n// properties (or what their toJSON() returns); a reference back to an object\n// being written is \"[Circular]\". Strings and numbers under a password key\n// (util.isPasswordKey), at any depth, become \"********\".\n//\n// Sizes are JSON characters (UTF-16 code units; bytes for ASCII text).\n// snapshotJson() keeps a state within 1MB: the first pass writes the JSON\n// straight into a string and stops at the limit; only a state that did not\n// fit gets a second pass, which measures branches (once, sizes cached) and\n// replaces those that do not fit by \"[… 2.3 MB not recorded]\".\n\nimport { isErrorLike, isPasswordKey, MASK } from '../recorder/util';\n\n// The page's own built-ins, captured before its scripts run.\nconst stringify = JSON.stringify;\nconst keysOf = Object.keys;\nconst isArray = Array.isArray;\nconst protoOf = Object.getPrototypeOf;\nconst OBJECT_PROTO = Object.prototype;\nconst tagOf = Object.prototype.toString;\nconst mapForEach = Map.prototype.forEach;\nconst setForEach = Set.prototype.forEach;\nconst dateTime = Date.prototype.getTime;\nconst dateIso = Date.prototype.toISOString;\n/** Unboxing, like JSON does for new String('x'), new Number(1), new Boolean(true). */\nconst unbox: Record<string, (this: unknown) => unknown> = {\n String: String.prototype.valueOf,\n Number: Number.prototype.valueOf,\n Boolean: Boolean.prototype.valueOf,\n BigInt: BigInt.prototype.valueOf,\n};\nconst isView = ArrayBuffer.isView;\nconst finite = Number.isFinite;\n\n/** A snapshot's state is at most this many characters of JSON. */\nexport const SNAPSHOT_LIMIT = 1024 * 1024;\n/** Deeper objects are written as \"[Object]\" / \"[Array(n)]\". */\nexport const MAX_DEPTH = 100;\n/** Room held for one size note and its separator: \"[… 1023.9 KB not recorded]\" is 28 characters. */\nconst NOTE_MAX = 32;\n/**\n * Bigger containers that do not fit get one note instead of a note per item;\n * the state itself is cut item by item while the notes take at most half\n * the budget (a big collection at the root would otherwise leave nothing).\n */\nconst DESCEND_MAX = 512;\n/** Measuring stops here; the note then says this much. */\nconst MEASURE_CAP = 32 * 1024 * 1024;\n/** Measured containers at least this big keep their size for the rest of the snapshot. */\nconst CACHE_MIN = 1024;\n/** Long strings are measured by their length (plus quotes) instead of being escaped. */\nconst MEASURE_EXACT_MAX = 4096;\nconst MASKED = stringify(MASK);\n\nconst OVERFLOW = { overflow: true } as const;\n\n/** The budget ran out: JsonWriter.raw() threw this. */\nexport function isOverflow(e: unknown): boolean {\n return e === OVERFLOW;\n}\n\n/** \"812 B\", \"1.2 KB\", \"2.3 MB\". */\nexport function formatSize(n: number): string {\n if (n < 1024) return `${n} B`;\n if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;\n return `${(n / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n/** What replaces a branch that did not fit. */\nexport function sizeNote(n: number): string {\n return `[… ${formatSize(n)} not recorded]`;\n}\n\n/** A JSON Pointer reference token (RFC 6901): ~ is ~0, / is ~1. */\nexport function pointerToken(key: string): string {\n return key.includes('~') || key.includes('/') ? key.replace(/~/g, '~0').replace(/\\//g, '~1') : key;\n}\n\nexport interface KeyInfo {\n /** The key as JSON, with the colon: `\"name\":`. */\n readonly json: string;\n /** The key as a JSON Pointer token. */\n readonly ptr: string;\n /** A password key: everything under it is masked. */\n readonly pw: boolean;\n}\n\n// State objects repeat the same keys over and over.\nconst keyCache = new Map<string, KeyInfo>();\n\nexport function keyInfo(key: string): KeyInfo {\n let info = keyCache.get(key);\n if (info === undefined) {\n info = { json: stringify(key) + ':', ptr: pointerToken(key), pw: isPasswordKey(key) };\n if (key.length <= 64) {\n if (keyCache.size >= 10_000) keyCache.clear();\n keyCache.set(key, info);\n }\n }\n return info;\n}\n\n/** Values JSON leaves out of objects. */\nexport function isAbsent(v: unknown): boolean {\n return v === undefined || typeof v === 'function' || typeof v === 'symbol';\n}\n\n/** Plain objects (and prototype-less ones) are walked key by key; class instances are not. */\nexport function isPlainObject(v: object): boolean {\n const p = protoOf(v);\n return p === OBJECT_PROTO || p === null;\n}\n\nfunction readKey(o: object, key: string): unknown {\n try {\n return (o as Record<string, unknown>)[key];\n } catch {\n return '[unreadable]';\n }\n}\n\nfunction describeNode(n: { nodeType: number; nodeName: string; tagName?: unknown; id?: unknown }): string {\n if (n.nodeType === 1) return `<${String(n.tagName).toLowerCase()}${n.id ? '#' + String(n.id) : ''}>`;\n return n.nodeName;\n}\n\n/**\n * Appends JSON text to `out`, at most `limit` characters: raw() throws an\n * overflow (see isOverflow) instead of going over. In counting mode it only\n * counts (measuring a branch for its size note).\n */\nexport class JsonWriter {\n out = '';\n /** Something was cut: a size note or the depth limit. */\n truncated = false;\n private readonly limit: number;\n private readonly counting: boolean;\n /** The objects being written, for \"[Circular]\". */\n private readonly anc: Set<object>;\n /** Sizes of big containers already measured in this snapshot. */\n private readonly sizes: Map<object, number> | undefined;\n /** Second pass: room held back for the notes of siblings not written yet. */\n private reserved = 0;\n private count = 0;\n\n constructor(limit: number, counting = false, anc: Set<object> = new Set(), sizes?: Map<object, number>) {\n this.limit = limit;\n this.counting = counting;\n this.anc = anc;\n this.sizes = sizes;\n }\n\n get length(): number {\n return this.counting ? this.count : this.out.length;\n }\n\n raw(s: string): void {\n if (this.counting) {\n if ((this.count += s.length) > MEASURE_CAP) throw OVERFLOW;\n return;\n }\n if (this.out.length + s.length > this.limit - this.reserved) throw OVERFLOW;\n this.out += s;\n }\n\n value(v: unknown, masked: boolean, depth: number): void {\n switch (typeof v) {\n case 'string':\n if (masked) return this.raw(MASKED);\n if (this.counting) {\n if (v.length > MEASURE_EXACT_MAX) {\n if ((this.count += v.length + 2) > MEASURE_CAP) throw OVERFLOW;\n return;\n }\n } else if (this.out.length + v.length + 2 > this.limit - this.reserved) {\n throw OVERFLOW; // too long even unescaped: no need to copy it\n }\n return this.raw(stringify(v));\n case 'number':\n return this.raw(masked ? MASKED : finite(v) ? '' + v : 'null');\n case 'boolean':\n return this.raw(v ? 'true' : 'false');\n case 'bigint':\n return this.raw(masked ? MASKED : '\"' + v + 'n\"');\n case 'object':\n return v === null ? this.raw('null') : this.object(v, masked, depth);\n default:\n return this.raw('null'); // undefined, functions and symbols, where JSON writes null\n }\n }\n\n private object(o: object, masked: boolean, depth: number): void {\n if (this.anc.has(o)) return this.raw('\"[Circular]\"');\n if (depth >= MAX_DEPTH) {\n this.truncated = true;\n return this.raw(isArray(o) ? `\"[Array(${o.length})]\"` : '\"[Object]\"');\n }\n if (this.counting && this.sizes) {\n const known = this.sizes.get(o);\n if (known !== undefined) {\n if ((this.count += known) > MEASURE_CAP) throw OVERFLOW;\n return;\n }\n }\n const mark = this.out;\n const start = this.length;\n this.anc.add(o);\n try {\n if (isArray(o)) this.array(o, masked, depth);\n else if (isPlainObject(o)) this.props(o, masked, depth);\n else this.special(o, masked, depth);\n } catch (e) {\n if (e === OVERFLOW) throw e;\n // A hostile object (revoked Proxy, throwing trap): keep the rest.\n if (this.counting) this.count = start;\n else this.out = mark;\n this.raw('\"[unreadable]\"');\n } finally {\n this.anc.delete(o);\n }\n if (this.counting && this.sizes && this.count - start >= CACHE_MIN) this.sizes.set(o, this.count - start);\n }\n\n private array(a: readonly unknown[], masked: boolean, depth: number): void {\n this.raw('[');\n for (let i = 0, n = a.length; i < n; i++) {\n if (i > 0) this.raw(',');\n this.value(a[i], masked, depth + 1);\n }\n this.raw(']');\n }\n\n private props(o: object, masked: boolean, depth: number): void {\n this.raw('{');\n const keys = keysOf(o);\n let first = true;\n for (let i = 0; i < keys.length; i++) {\n const v = readKey(o, keys[i]);\n if (isAbsent(v)) continue;\n const info = keyInfo(keys[i]);\n if (first) first = false;\n else this.raw(',');\n this.raw(info.json);\n this.value(v, masked || info.pw, depth + 1);\n }\n this.raw('}');\n }\n\n private special(o: object, masked: boolean, depth: number): void {\n const tag = (tagOf.call(o) as string).slice(8, -1);\n switch (tag) {\n case 'Date': {\n let t: number;\n try {\n t = dateTime.call(o as Date);\n } catch {\n break; // a Symbol.toStringTag pretending to be a Date\n }\n return this.raw(masked ? MASKED : t === t ? '\"' + dateIso.call(o as Date) + '\"' : '\"Invalid Date\"');\n }\n case 'Map':\n return this.map(o as Map<unknown, unknown>, masked, depth);\n case 'Set':\n return this.set(o as Set<unknown>, masked, depth);\n case 'RegExp':\n return this.raw(masked ? MASKED : stringify(String(o)));\n case 'Promise':\n case 'WeakMap':\n case 'WeakSet':\n case 'WeakRef':\n return this.raw(`\"[${tag}]\"`);\n case 'String':\n case 'Number':\n case 'Boolean':\n case 'BigInt': {\n let p: unknown;\n try {\n p = unbox[tag].call(o);\n } catch {\n break;\n }\n return this.value(p, masked, depth);\n }\n }\n if (isErrorLike(o)) {\n this.raw('{\"name\":');\n this.value(String(o.name), masked, depth + 1);\n this.raw(',\"message\":');\n this.value(String(o.message), masked, depth + 1);\n return this.raw('}');\n }\n if (o instanceof ArrayBuffer || isView(o)) return this.raw(`\"[${tag} ${(o as ArrayBuffer).byteLength} bytes]\"`);\n const node = o as { nodeType?: unknown; nodeName?: unknown; window?: unknown };\n if (typeof node.nodeType === 'number' && typeof node.nodeName === 'string') {\n return this.raw(stringify(describeNode(node as { nodeType: number; nodeName: string })));\n }\n if (node.window === o) return this.raw('\"[Window]\"');\n const toJSON = (o as { toJSON?: unknown }).toJSON;\n if (typeof toJSON === 'function') {\n const r: unknown = toJSON.call(o, '');\n if (r !== o) return this.value(r, masked, depth);\n }\n this.props(o, masked, depth); // a class instance: its own enumerable properties\n }\n\n private map(m: Map<unknown, unknown>, masked: boolean, depth: number): void {\n this.raw('{\"[[Map]]\":[');\n let first = true;\n mapForEach.call(m, (v: unknown, k: unknown) => {\n if (first) first = false;\n else this.raw(',');\n this.raw('[');\n this.value(k, false, depth + 2); // keys stay readable, like object keys\n this.raw(',');\n this.value(v, masked || (typeof k === 'string' && keyInfo(k).pw), depth + 2);\n this.raw(']');\n });\n this.raw(']}');\n }\n\n private set(s: Set<unknown>, masked: boolean, depth: number): void {\n this.raw('{\"[[Set]]\":[');\n let first = true;\n setForEach.call(s, (v: unknown) => {\n if (first) first = false;\n else this.raw(',');\n this.value(v, masked, depth + 2);\n });\n this.raw(']}');\n }\n\n // ---------- second pass: what does not fit becomes a size note ----------\n\n /**\n * Writes v within the budget: whole if it fits, else its children one by\n * one (plain objects and arrays of at most 512 items), else a size note\n * (then returns true). Throws an overflow only when not even a note fits.\n */\n careful(v: unknown, masked: boolean, depth: number, tooBig: boolean): boolean {\n let size = -1;\n if (!tooBig) {\n size = this.measure(v, masked, depth);\n if (size <= this.limit - this.reserved - this.out.length) {\n const mark = this.out;\n try {\n this.value(v, masked, depth);\n return false;\n } catch {\n this.out = mark; // the measure was off (a getter, a cached size): fall through\n }\n }\n }\n if (this.descendable(v, depth)) {\n const mark = this.out;\n try {\n this.descend(v as object, masked, depth);\n return false;\n } catch {\n this.out = mark; // not even the notes fit, or a hostile container: one note for all of it\n }\n }\n if (size < 0) size = this.measure(v, masked, depth);\n this.raw(stringify(sizeNote(size)));\n this.truncated = true;\n return true;\n }\n\n private descendable(v: unknown, depth: number): boolean {\n if (v === null || typeof v !== 'object' || depth >= MAX_DEPTH || this.anc.has(v)) return false;\n const max = depth === 0 ? this.limit / 2 / NOTE_MAX : DESCEND_MAX;\n try {\n if (isArray(v)) return v.length <= max;\n return isPlainObject(v) && keysOf(v).length <= max;\n } catch {\n return false;\n }\n }\n\n /** Writes a container whose children may become notes; room for every child's note is held from the start. */\n private descend(o: object, masked: boolean, depth: number): void {\n const arr = isArray(o);\n const kids: { prefix: string; value: unknown; masked: boolean }[] = [];\n if (arr) {\n const a = o as readonly unknown[];\n for (let i = 0; i < a.length; i++) kids.push({ prefix: i ? ',' : '', value: a[i], masked });\n } else {\n for (const key of keysOf(o)) {\n const v = readKey(o, key);\n if (isAbsent(v)) continue;\n const info = keyInfo(key);\n kids.push({ prefix: (kids.length ? ',' : '') + info.json, value: v, masked: masked || info.pw });\n }\n }\n let hold = 1; // the closing bracket\n for (const kid of kids) hold += kid.prefix.length + NOTE_MAX;\n if (this.out.length + 1 + hold > this.limit - this.reserved) throw OVERFLOW;\n const reserved = this.reserved;\n this.anc.add(o);\n try {\n this.raw(arr ? '[' : '{');\n this.reserved += hold;\n for (const kid of kids) {\n const room = kid.prefix.length + NOTE_MAX;\n const before = this.out.length;\n this.reserved -= room;\n this.raw(kid.prefix);\n // What a note leaves of its room stays held: later siblings get no\n // budget back from notes, so the cut is one stretch, not scattered.\n if (this.careful(kid.value, kid.masked, depth + 1, false)) this.reserved += room - (this.out.length - before);\n }\n this.reserved -= 1;\n this.raw(arr ? ']' : '}');\n } finally {\n this.reserved = reserved;\n this.anc.delete(o);\n }\n }\n\n /** The JSON size of v, written the same way (the cap if it is larger). */\n private measure(v: unknown, masked: boolean, depth: number): number {\n const m = new JsonWriter(Infinity, true, this.anc, this.sizes);\n try {\n m.value(v, masked, depth);\n } catch (e) {\n if (e !== OVERFLOW) throw e;\n return MEASURE_CAP;\n }\n return m.count;\n }\n}\n\nexport interface JsonText {\n json: string;\n /** Some branches were replaced by size notes (or the depth limit). */\n truncated: boolean;\n}\n\n/** A whole state as JSON, at most `limit` characters (ST-R9). */\nexport function snapshotJson(state: unknown, limit = SNAPSHOT_LIMIT): JsonText {\n const w = new JsonWriter(limit);\n try {\n w.value(state, false, 0);\n return { json: w.out, truncated: w.truncated };\n } catch (e) {\n if (e !== OVERFLOW) throw e;\n }\n const c = new JsonWriter(limit, false, new Set(), new Map());\n c.careful(state, false, 0, true);\n return { json: c.out, truncated: true };\n}\n","// The changes between two states of a store (ST-R7, ST-R8), as the JSON of\n// a StateChange[] the UI applies in order to the previous state:\n// {path, value} set the value at the path (an array index equal\n// to the length appends)\n// {path, deleted: true} delete the key; array items are only deleted from\n// the end, highest index first\n// Paths are JSON Pointers (RFC 6901), \"\" is the whole state. Branches whose\n// reference did not change are skipped, so an immutable update costs only\n// the walk down to what changed. Plain objects and arrays are walked;\n// anything else that changed (or changed type) is replaced as a whole.\n// Object entries JSON leaves out (undefined, functions) count as absent.\n\nimport { isAbsent, isOverflow, isPlainObject, JsonWriter, keyInfo, MAX_DEPTH } from './json';\n\n/** More changed paths than this: the action carries the full state instead. */\nexport const MAX_PATHS = 200;\n/** Changes JSON longer than this (characters): the full state instead. */\nexport const CHANGES_LIMIT = 64 * 1024;\n\nconst stringify = JSON.stringify;\nconst keysOf = Object.keys;\nconst isArray = Array.isArray;\nconst hasOwn = Object.prototype.hasOwnProperty;\n\ninterface Change {\n path: string;\n value?: unknown;\n deleted?: true;\n /** Under a password key. */\n masked: boolean;\n /** Depth of the value in the state, for the depth limit. */\n depth: number;\n}\n\nconst TOO_MANY = { tooMany: true } as const;\n\n/** The JSON of the changes from prev to next, or undefined when they are over the limits. */\nexport function changesJson(prev: unknown, next: unknown): string | undefined {\n const changes: Change[] = [];\n try {\n walk(prev, next, '', false, 0, changes, new Set());\n } catch (e) {\n if (e === TOO_MANY) return undefined;\n throw e;\n }\n const w = new JsonWriter(CHANGES_LIMIT);\n try {\n w.raw('[');\n for (let i = 0; i < changes.length; i++) {\n const c = changes[i];\n w.raw((i ? ',{\"path\":' : '{\"path\":') + stringify(c.path));\n if (c.deleted) {\n w.raw(',\"deleted\":true}');\n } else {\n w.raw(',\"value\":');\n w.value(c.value, c.masked, c.depth);\n w.raw('}');\n }\n }\n w.raw(']');\n } catch (e) {\n if (isOverflow(e)) return undefined;\n throw e;\n }\n return w.out;\n}\n\nfunction add(changes: Change[], c: Change): void {\n if (changes.length >= MAX_PATHS) throw TOO_MANY;\n changes.push(c);\n}\n\nfunction walk(prev: unknown, next: unknown, path: string, masked: boolean, depth: number, changes: Change[], anc: Set<object>): void {\n if (prev === next) return;\n if (typeof prev === 'object' && typeof next === 'object' && prev !== null && next !== null && depth < MAX_DEPTH) {\n // A reference back up the path is \"[Circular]\" in snapshots, and stays so.\n if (anc.has(next)) return;\n const pa = isArray(prev);\n const na = isArray(next);\n if (pa && na) {\n anc.add(next);\n walkArray(prev, next as unknown[], path, masked, depth, changes, anc);\n anc.delete(next);\n return;\n }\n if (!pa && !na && isPlainObject(prev) && isPlainObject(next)) {\n anc.add(next);\n walkObject(prev, next, path, masked, depth, changes, anc);\n anc.delete(next);\n return;\n }\n } else if (typeof prev === 'number' && typeof next === 'number' && prev !== prev && next !== next) {\n return; // NaN stays NaN\n }\n add(changes, { path, value: next, masked, depth });\n}\n\nfunction walkArray(prev: readonly unknown[], next: readonly unknown[], path: string, masked: boolean, depth: number, changes: Change[], anc: Set<object>): void {\n const pl = prev.length;\n const nl = next.length;\n const n = pl < nl ? pl : nl;\n for (let i = 0; i < n; i++) {\n const pv = prev[i];\n const nv = next[i];\n if (pv !== nv) walk(pv, nv, path + '/' + i, masked, depth + 1, changes, anc);\n }\n for (let i = pl; i < nl; i++) add(changes, { path: path + '/' + i, value: next[i], masked, depth: depth + 1 });\n for (let i = pl - 1; i >= nl; i--) add(changes, { path: path + '/' + i, deleted: true, masked, depth: depth + 1 });\n}\n\nfunction walkObject(prev: object, next: object, path: string, masked: boolean, depth: number, changes: Change[], anc: Set<object>): void {\n const p = prev as Record<string, unknown>;\n const n = next as Record<string, unknown>;\n const nextKeys = keysOf(n);\n let common = 0;\n for (let i = 0; i < nextKeys.length; i++) {\n const key = nextKeys[i];\n const had = hasOwn.call(p, key);\n if (had) common++;\n const nv = n[key];\n const pv = had ? p[key] : undefined;\n if (nv === pv) continue;\n const info = keyInfo(key);\n const kp = path + '/' + info.ptr;\n const km = masked || info.pw;\n if (isAbsent(nv)) {\n if (!isAbsent(pv)) add(changes, { path: kp, deleted: true, masked: km, depth: depth + 1 });\n } else if (isAbsent(pv)) {\n add(changes, { path: kp, value: nv, masked: km, depth: depth + 1 });\n } else {\n walk(pv, nv, kp, km, depth + 1, changes, anc);\n }\n }\n const prevKeys = keysOf(p);\n if (common === prevKeys.length) return;\n for (let i = 0; i < prevKeys.length; i++) {\n const key = prevKeys[i];\n if (hasOwn.call(n, key) || isAbsent(p[key])) continue;\n const info = keyInfo(key);\n add(changes, { path: path + '/' + info.ptr, deleted: true, masked: masked || info.pw, depth: depth + 1 });\n }\n}\n","// The stores the hook sees and the \"state\" records written about them\n// (shared/events.ts StateData). Three kinds of store:\n// - enhancer stores (Redux, Redux Toolkit): getState() gives their state;\n// - connections (connect(), Zustand devtools): their state is what init()\n// and send() passed last;\n// - manual stores (tabtape.state()): the state of the last call.\n//\n// Lifecycle (api.ts): before start() stores are only tracked; start() writes\n// an init (or a \"start\" snapshot) per store and then a record per action;\n// stop() stops writing; disable() makes the hook a pass-through (it only\n// forwards to the real Redux DevTools, if any, and keeps each connection's\n// latest state reference) until a start() comes again.\n//\n// Nothing here may throw into the page or the recorder: every entry point\n// catches, and a failed record makes the store's next record a full state.\n\nimport type { StateData } from '../../../shared/events';\nimport { capProps } from '../recorder/track';\nimport type { StateHookApi } from './api';\nimport { changesJson } from './diff';\nimport { snapshotJson } from './json';\n\nexport interface HookDeps {\n /** Epoch ms (the page's own Date.now, captured at load). */\n now(): number;\n setTimeout(fn: () => void, ms: number): unknown;\n clearTimeout(id: unknown): void;\n}\n\n/** Over 50 actions in a 1-second window: types only, and a snapshot a second after the burst began (ST-R10). */\nexport const BURST = { actions: 50, windowMs: 1000, resyncMs: 1000 } as const;\n/** A snapshot after this many actions without a full state (ST-R8). */\nexport const INTERVAL_ACTIONS = 100;\nconst NAME_MAX = 100;\nconst TYPE_MAX = 1000;\n/** Stores tracked per page (HMR or a store per component would grow without end); later ones are not recorded. */\nexport const STORES_MAX = 200;\n\nconst stringify = JSON.stringify;\nconst keysOf = Object.keys;\n\ntype Reason = NonNullable<StateData['reason']>;\n\nexport interface TrackedStore {\n readonly name: string;\n readonly getState: (() => unknown) | undefined;\n /** Connections and manual stores: the last state they reported. */\n latest: unknown;\n hasLatest: boolean;\n /** The state as of the store's last record: what the next diff starts from. */\n base: unknown;\n hasBase: boolean;\n /** A record of this store was written on this page (the next start() writes a snapshot, not an init). */\n written: boolean;\n /** Actions since the last record with a full state. */\n sinceFull: number;\n winStart: number;\n winCount: number;\n /** In a burst: types only until the re-sync snapshot. */\n burst: boolean;\n timer: unknown;\n}\n\n/** The store name from a name option: a non-empty string (or a number), at most 100 characters. */\nexport function nameOf(v: unknown): string {\n if (typeof v === 'number' && Number.isFinite(v)) return String(v);\n return typeof v === 'string' ? v.trim().slice(0, NAME_MAX) : '';\n}\n\n/** A string action, an object's type, else \"anonymous\". */\nexport function actionType(action: unknown): string {\n let t: unknown = action;\n if (action !== null && typeof action === 'object') {\n try {\n t = (action as { type?: unknown }).type;\n } catch {\n t = undefined;\n }\n if (typeof t === 'number' || typeof t === 'symbol' || typeof t === 'bigint' || typeof t === 'boolean') t = String(t);\n }\n return typeof t === 'string' && t !== '' ? t.slice(0, TYPE_MAX) : 'anonymous';\n}\n\n/** The rest of the action object, capped like tabtape.track props (8KB, password keys masked). */\nfunction payloadJson(action: unknown): string {\n if (action === null || typeof action !== 'object') return '';\n const rest = Object.create(null) as Record<string, unknown>;\n let any = false;\n for (const key of keysOf(action)) {\n if (key === 'type') continue;\n let v: unknown;\n try {\n v = (action as Record<string, unknown>)[key];\n } catch {\n v = '[unreadable]';\n }\n if (v === undefined) continue; // an action creator without a payload: {type, payload: undefined}\n rest[key] = v;\n any = true;\n }\n if (!any) return '';\n const capped = capProps(rest);\n if (!capped.props) return '';\n return ',\"payload\":' + stringify(capped.props) + (capped.truncated ? ',\"payloadTruncated\":true' : '');\n}\n\nexport class StateEngine {\n readonly api: StateHookApi;\n private mode: 'idle' | 'recording' | 'disabled' = 'idle';\n private emit: ((dataJson: string, ts: number) => void) | undefined;\n private readonly stores: TrackedStore[] = [];\n private readonly names = new Set<string>();\n private unnamed = 0;\n private readonly manualStores = new Map<string, TrackedStore>();\n /** Writing a record: a getter or toJSON() that dispatches is not recorded. */\n private busy = false;\n private readonly deps: HookDeps;\n\n constructor(deps: HookDeps, early: boolean) {\n this.deps = deps;\n const hook = early ? 'early' : 'late';\n this.api = {\n start: (emit) => this.guard(() => this.start(emit)),\n stop: () => this.guard(() => this.stop()),\n checkout: () => this.guard(() => this.checkout()),\n disable: () => this.guard(() => this.disable()),\n manual: (store, action, state) => this.guard(() => this.manual(store, action, state)),\n info: () => {\n try {\n return { hook, stores: this.stores.map((s) => s.name) };\n } catch {\n return { hook, stores: [] };\n }\n },\n };\n }\n\n get recording(): boolean {\n return this.mode === 'recording';\n }\n\n // ---------- stores ----------\n\n /** A store made by the enhancer; writes its init while recording. Undefined past the store limit. */\n addEnhancerStore(name: string, getState: () => unknown): TrackedStore | undefined {\n const s = this.add(name, getState);\n if (s) this.full(s, 'init');\n return s;\n }\n\n /** A connect() store; it has a state once init() or send() is called. Undefined past the store limit. */\n addConnection(name: string): TrackedStore | undefined {\n return this.add(name, undefined);\n }\n\n /** connection.init(state): the store's (new) initial state. */\n connInit(s: TrackedStore, state: unknown): void {\n // The latest state is kept even while disabled (a reference the store\n // holds anyway): the recorder disables on every stop, and a recording\n // started again on this page snapshots it.\n s.latest = state;\n s.hasLatest = true;\n if (this.mode === 'disabled') return;\n this.full(s, 'init', undefined, { v: state });\n }\n\n /** connection.send(action, state). A null action is the Redux DevTools importing a lifted state: not an app action. */\n connSend(s: TrackedStore, action: unknown, state: unknown): void {\n if (action === null) return;\n s.latest = state;\n s.hasLatest = true;\n if (this.mode === 'disabled') return;\n this.action(s, action, state);\n }\n\n /** One action of a store, with the store's state after it. */\n action(s: TrackedStore, action: unknown, state: unknown): void {\n if (this.mode !== 'recording' || this.busy) return;\n this.busy = true;\n let interval = false;\n try {\n interval = this.writeAction(s, action, state);\n } catch {\n s.base = undefined;\n s.hasBase = false;\n } finally {\n this.busy = false;\n }\n if (interval) this.full(s, 'snapshot', 'interval', { v: state });\n }\n\n private add(name: string, getState: (() => unknown) | undefined): TrackedStore | undefined {\n if (this.stores.length >= STORES_MAX) return undefined;\n const s: TrackedStore = {\n name: this.unique(name),\n getState,\n latest: undefined,\n hasLatest: false,\n base: undefined,\n hasBase: false,\n written: false,\n sinceFull: 0,\n winStart: 0,\n winCount: 0,\n burst: false,\n timer: undefined,\n };\n this.stores.push(s);\n return s;\n }\n\n /** Unnamed stores are store-1, store-2, …; a taken name gets -2, -3, … (ST-R5). */\n private unique(name: string): string {\n let n: string;\n if (!name) {\n do n = `store-${++this.unnamed}`;\n while (this.names.has(n));\n } else if (!this.names.has(name)) {\n n = name;\n } else {\n let i = 2;\n while (this.names.has(`${name}-${i}`)) i++;\n n = `${name}-${i}`;\n }\n this.names.add(n);\n return n;\n }\n\n // ---------- lifecycle ----------\n\n private start(emit: (dataJson: string, ts: number) => void): void {\n if (typeof emit !== 'function') return;\n this.emit = emit;\n this.mode = 'recording';\n for (const s of this.stores) {\n this.reset(s);\n this.full(s, s.written ? 'snapshot' : 'init', 'start');\n }\n }\n\n private stop(): void {\n if (this.mode === 'recording') this.mode = 'idle';\n this.emit = undefined;\n for (const s of this.stores) this.reset(s);\n }\n\n private disable(): void {\n this.mode = 'disabled';\n this.emit = undefined;\n for (const s of this.stores) this.reset(s);\n }\n\n private checkout(): void {\n if (this.mode !== 'recording') return;\n for (const s of this.stores) this.full(s, 'snapshot', 'checkout');\n }\n\n private manual(store: unknown, action: unknown, state: unknown): void {\n if (typeof store !== 'string' || this.mode === 'disabled') return;\n const key = store.trim();\n if (!key) return;\n let s = this.manualStores.get(key);\n if (!s) {\n s = this.add(key.slice(0, NAME_MAX), undefined);\n if (!s) return;\n this.manualStores.set(key, s);\n }\n s.latest = state;\n s.hasLatest = true;\n // The first call has no state to diff against: it carries the full state (ST-R14).\n this.action(s, action, state);\n }\n\n private reset(s: TrackedStore): void {\n if (s.timer !== undefined) {\n try {\n this.deps.clearTimeout(s.timer);\n } catch {\n // ignore\n }\n s.timer = undefined;\n }\n s.burst = false;\n s.winStart = 0;\n s.winCount = 0;\n s.sinceFull = 0;\n s.base = undefined;\n s.hasBase = false;\n }\n\n private guard(fn: () => void): void {\n try {\n fn();\n } catch {\n // never break the page or the recorder\n }\n }\n\n // ---------- records ----------\n\n private head(s: TrackedStore, kind: StateData['kind']): string {\n return '{\"store\":' + stringify(s.name) + ',\"kind\":\"' + kind + '\"';\n }\n\n private write(s: TrackedStore, json: string, ts: number): void {\n s.written = true;\n this.emit?.(json, ts);\n }\n\n /** Writes the action; true when an interval snapshot is due. */\n private writeAction(s: TrackedStore, action: unknown, state: unknown): boolean {\n const ts = this.deps.now();\n const type = stringify(actionType(action));\n if (ts - s.winStart >= BURST.windowMs || ts < s.winStart) {\n s.winStart = ts;\n s.winCount = 0;\n }\n if (++s.winCount > BURST.actions || s.burst) {\n if (!s.burst) {\n s.burst = true;\n s.base = undefined;\n s.hasBase = false;\n s.timer = this.deps.setTimeout(() => this.resync(s), BURST.resyncMs);\n }\n s.sinceFull++;\n this.write(s, this.head(s, 'action') + ',\"action\":' + type + ',\"burst\":true}', ts);\n return false;\n }\n let json = this.head(s, 'action') + ',\"action\":' + type + payloadJson(action);\n let changes: string | undefined;\n if (s.hasBase) {\n try {\n changes = changesJson(s.base, state);\n } catch {\n changes = undefined; // a throwing getter: the snapshot writer copes with it\n }\n }\n let interval = false;\n if (changes !== undefined) {\n json += ',\"changes\":' + changes;\n interval = ++s.sinceFull >= INTERVAL_ACTIONS;\n } else {\n const snap = snapshotJson(state);\n json += ',\"state\":' + snap.json + (snap.truncated ? ',\"truncated\":true' : '');\n s.sinceFull = 0;\n }\n s.base = state;\n s.hasBase = true;\n this.write(s, json + '}', ts);\n return interval;\n }\n\n /** A record with the store's full state (given, or its current one); nothing if it is not known. */\n private full(s: TrackedStore, kind: 'init' | 'snapshot', reason?: Reason, given?: { v: unknown }): void {\n if (this.mode !== 'recording' || this.busy) return;\n this.busy = true;\n try {\n let state: unknown;\n if (given) state = given.v;\n else if (s.getState) state = s.getState();\n else if (s.hasLatest) state = s.latest;\n else return;\n const ts = this.deps.now();\n const snap = snapshotJson(state);\n let json = this.head(s, kind) + ',\"state\":' + snap.json;\n if (snap.truncated) json += ',\"truncated\":true';\n if (kind === 'snapshot' && reason) json += ',\"reason\":\"' + reason + '\"';\n s.base = state;\n s.hasBase = true;\n s.sinceFull = 0;\n this.write(s, json + '}', ts);\n } catch {\n s.base = undefined;\n s.hasBase = false;\n } finally {\n this.busy = false;\n }\n }\n\n /** A second after a burst began: a snapshot, then diffs again. */\n private resync(s: TrackedStore): void {\n s.timer = undefined;\n if (!s.burst) return;\n s.burst = false;\n this.full(s, 'snapshot', 'burst');\n }\n}\n","// The Redux DevTools hook (ST-R2, ST-R3, ST-R4): window.__REDUX_DEVTOOLS_EXTENSION__\n// and window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__, defined before the page's\n// scripts, so Redux Toolkit's configureStore (devTools defaults to true),\n// legacy createStore(reducer, __REDUX_DEVTOOLS_EXTENSION__()) and Zustand's\n// devtools() connect to tabtape without code changes.\n//\n// The real Redux DevTools keeps working: both globals are configurable\n// accessors whose getters return tabtape's functions and whose setters keep\n// what the extension assigns (it may load before or after state.js), and\n// tabtape forwards every call to it. Its page script assigns its function and\n// then its methods (window.__REDUX_DEVTOOLS_EXTENSION__.connect = …); those\n// land on tabtape's function, whose method properties hand them on to the real\n// one. With the real extension:\n// - compose: the real compose builds the chain, with tabtape's recording\n// enhancer added innermost, right around the real devtools store\n// (so the real enhancer and tabtape's both apply, tabtape sees the app's state);\n// - __REDUX_DEVTOOLS_EXTENSION__(config): the real enhancer inside tabtape's;\n// - connect(): the real connection gets every call; subscribe() returns\n// the real unsubscribe.\n// Without it, subscribe listeners are never called: tabtape sends nothing to the\n// app (no time travel), and its enhancer only observes.\n//\n// Recording never changes what the app sees: dispatch returns what the\n// store's dispatch returned, errors of the app or of the real extension\n// pass through unchanged, and tabtape's own errors are swallowed.\n\nimport { STATE_HOOK_KEY } from '../protocol';\nimport type { StateHookApi } from './api';\nimport { nameOf, StateEngine, type HookDeps, type TrackedStore } from './engine';\n\nexport type { HookDeps } from './engine';\n\ntype Fn = (...args: unknown[]) => unknown;\ntype StoreCreator = (...args: unknown[]) => unknown;\ntype Enhancer = (next: StoreCreator) => StoreCreator;\n\ninterface ReduxStore {\n dispatch: Fn;\n getState: () => unknown;\n subscribe?: (listener: () => void) => unknown;\n replaceReducer?: Fn;\n [key: string | symbol]: unknown;\n}\n\ninterface Connection {\n init(...args: unknown[]): unknown;\n send(...args: unknown[]): unknown;\n subscribe(listener: unknown): unknown;\n unsubscribe(): unknown;\n error(...args: unknown[]): unknown;\n [key: string]: unknown;\n}\n\nconst EXTENSION = '__REDUX_DEVTOOLS_EXTENSION__';\nconst COMPOSE = '__REDUX_DEVTOOLS_EXTENSION_COMPOSE__';\n/** The methods the real extension puts on its function. */\nconst MEMBERS = ['connect', 'disconnect', 'send', 'listen', 'open', 'notifyErrors'] as const;\ntype Member = (typeof MEMBERS)[number];\n\nconst defineProperty = Object.defineProperty;\nconst getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;\nconst keysOf = Object.keys;\nconst apply = Reflect.apply;\n\nconst noop = () => {};\n\nfunction configName(config: unknown): string {\n try {\n return config !== null && typeof config === 'object' ? nameOf((config as { name?: unknown }).name) : '';\n } catch {\n return '';\n }\n}\n\nfunction isHookValue(v: unknown): boolean {\n return typeof v === 'function' || (v !== null && typeof v === 'object');\n}\n\n/**\n * Installs the hook on `win` (the page's window; a plain object in tests)\n * and publishes its StateHookApi under Symbol.for('tabtape.state'). Running it\n * again on the same window does nothing and returns the API already there.\n */\nexport function installHook(win: object, deps: HookDeps): StateHookApi {\n const w = win as Record<PropertyKey, unknown>;\n const key = Symbol.for(STATE_HOOK_KEY);\n try {\n const existing = w[key] as StateHookApi | undefined;\n if (existing) return existing;\n } catch {\n // install a new one\n }\n let early = false;\n try {\n early = (w.document as { readyState?: unknown } | undefined)?.readyState === 'loading';\n } catch {\n // late\n }\n const engine = new StateEngine(deps, early);\n\n // ---------- the real Redux DevTools ----------\n\n let real: unknown; // its __REDUX_DEVTOOLS_EXTENSION__ (a function with methods)\n let realCompose: unknown;\n /** Methods assigned through our function while there was no real one to hand them to. */\n const assigned: Partial<Record<Member, unknown>> = {};\n /** Inside a call to the real extension: a wrapper of ours that calls back gets no forwarding (no loop). */\n let inReal = 0;\n\n function callReal<T>(f: () => T): T {\n inReal++;\n try {\n return f();\n } finally {\n inReal--;\n }\n }\n\n function realHook(): Fn | undefined {\n return inReal === 0 && typeof real === 'function' && real !== hookFn ? (real as Fn) : undefined;\n }\n\n function realComposeFn(): Fn | undefined {\n return inReal === 0 && typeof realCompose === 'function' && realCompose !== composeFn ? (realCompose as Fn) : undefined;\n }\n\n function realMember(name: Member): Fn | undefined {\n if (inReal !== 0) return undefined;\n let m: unknown;\n try {\n if (real && real !== hookFn) m = (real as Record<string, unknown>)[name];\n } catch {\n m = undefined;\n }\n if (typeof m !== 'function') m = assigned[name];\n return typeof m === 'function' && m !== ours[name] ? (m as Fn) : undefined;\n }\n\n // ---------- enhancer stores ----------\n\n const instrumented = new WeakSet<object>();\n\n /** Records a store's actions; returns the store the app gets (the given one if anything goes wrong). */\n function instrument(store: unknown, name: string): unknown {\n try {\n if (store === null || typeof store !== 'object' || instrumented.has(store)) return store;\n const inner = store as ReduxStore;\n const origDispatch = inner.dispatch;\n const origGetState = inner.getState;\n if (typeof origDispatch !== 'function' || typeof origGetState !== 'function') return store;\n const getState = () => apply(origGetState, inner, []);\n const s = engine.addEnhancerStore(name, getState);\n if (!s) return store; // past the store limit: the app gets its store untouched\n\n // The actions being dispatched, innermost last. The state is read in\n // our store listener, the first one, right after the reducer ran: an\n // action a listener dispatches meanwhile is recorded on its own. If\n // no listener call comes (a batching store), it is read after dispatch.\n const pending: { action: unknown; done: boolean }[] = [];\n const record = (t: { action: unknown; done: boolean }) => {\n t.done = true;\n let state: unknown;\n try {\n state = getState();\n } catch {\n return;\n }\n engine.action(s, t.action, state);\n };\n try {\n inner.subscribe?.(() => {\n const t = pending[pending.length - 1];\n if (t && !t.done) record(t);\n });\n } catch {\n // recorded after dispatch instead\n }\n const run = (fn: Fn, args: unknown[], action: unknown): unknown => {\n if (!engine.recording) return apply(fn, inner, args);\n const t = { action, done: false };\n pending.push(t);\n let result: unknown;\n try {\n result = apply(fn, inner, args);\n } finally {\n pending.pop();\n }\n if (!t.done) record(t);\n return result;\n };\n\n const out: ReduxStore = { ...inner };\n out.dispatch = function dispatch(...args: unknown[]) {\n return run(origDispatch, args, args[0]);\n };\n const origReplace = inner.replaceReducer;\n if (typeof origReplace === 'function') {\n // Redux dispatches its own @@redux/REPLACE: new slices show up as its changes.\n out.replaceReducer = function replaceReducer(...args: unknown[]) {\n return run(origReplace, args, { type: '@@redux/REPLACE' });\n };\n }\n instrumented.add(out);\n return out;\n } catch {\n return store;\n }\n }\n\n /** tabtape's enhancer, around the real extension's when there is one (withReal). */\n function enhancer(config: unknown, withReal: boolean): Enhancer {\n let realEnhancer: unknown;\n const r = withReal ? realHook() : undefined;\n // The real extension's errors reach the app as they would without tabtape.\n if (r) realEnhancer = callReal(() => r(config));\n const name = configName(config);\n return (next: StoreCreator) => {\n const create = typeof realEnhancer === 'function' ? ((realEnhancer as Enhancer)(next) as StoreCreator) : next;\n return (...args: unknown[]) => instrument(create(...args), name);\n };\n }\n\n /** The real extension's compose shape: tabtape's (or the real) devtools enhancer innermost. */\n function extensionCompose(config: unknown) {\n return (...funcs: unknown[]) => {\n const rc = realComposeFn();\n if (rc) {\n const composed = callReal(() => rc(config));\n if (typeof composed === 'function') return (composed as Fn)(...funcs, enhancer(config, false));\n }\n return (next: StoreCreator) => (funcs as Enhancer[]).reduceRight((composed, f) => f(composed), enhancer(config, true)(next));\n };\n }\n\n // ---------- connect() ----------\n\n function connect(options: unknown): Connection {\n const rc = realMember('connect');\n const realConn = rc ? (callReal(() => apply(rc, real, [options])) as Partial<Connection> | undefined) : undefined;\n let s: TrackedStore | undefined;\n try {\n s = engine.addConnection(configName(options));\n } catch {\n s = undefined;\n }\n const call = (name: keyof Connection, args: unknown[]): unknown => {\n const m = realConn?.[name];\n return typeof m === 'function' ? apply(m as Fn, realConn, args) : undefined;\n };\n const conn: Connection = {\n init(...args: unknown[]) {\n if (s) engine.connInit(s, args[0]);\n return call('init', args);\n },\n send(...args: unknown[]) {\n if (s) engine.connSend(s, args[0], args[1]);\n return call('send', args);\n },\n subscribe(...args: unknown[]) {\n return typeof realConn?.subscribe === 'function' ? call('subscribe', args) : noop;\n },\n unsubscribe(...args: unknown[]) {\n return call('unsubscribe', args);\n },\n error(...args: unknown[]) {\n return call('error', args);\n },\n };\n if (realConn && typeof realConn === 'object') {\n try {\n for (const k of keysOf(realConn)) {\n if (k in conn) continue;\n const v = realConn[k];\n conn[k] = typeof v === 'function' ? (...args: unknown[]) => apply(v as Fn, realConn, args) : v;\n }\n } catch {\n // the five methods are enough\n }\n }\n return conn;\n }\n\n // ---------- the globals ----------\n\n const forward = (name: Member) =>\n function (...args: unknown[]) {\n const m = realMember(name);\n return m ? callReal(() => apply(m, real, args)) : undefined;\n };\n const ours: Record<Member, Fn> = {\n connect: (...args: unknown[]) => connect(args[0]),\n disconnect: forward('disconnect'),\n send: forward('send'),\n listen: forward('listen'),\n open: forward('open'),\n notifyErrors: forward('notifyErrors'),\n };\n\n const hookFn = function __REDUX_DEVTOOLS_EXTENSION__(config?: unknown) {\n return enhancer(config, true);\n };\n for (const name of MEMBERS) {\n defineProperty(hookFn, name, {\n configurable: true,\n enumerable: true,\n get: () => ours[name],\n set: (v: unknown) => {\n // The real extension's page script: window.__REDUX_DEVTOOLS_EXTENSION__.connect = connect.\n if (real && real !== hookFn) {\n try {\n (real as Record<string, unknown>)[name] = v;\n return;\n } catch {\n // keep it here\n }\n }\n assigned[name] = v;\n },\n });\n }\n\n const composeFn = function __REDUX_DEVTOOLS_EXTENSION_COMPOSE__(...funcs: unknown[]) {\n if (funcs.length === 0) return hookFn();\n if (funcs.length === 1 && typeof funcs[0] === 'object') return extensionCompose(funcs[0] ?? {});\n return extensionCompose({})(...funcs);\n };\n\n defineGlobal(w, EXTENSION, hookFn, (v) => {\n real = isHookValue(v) ? v : undefined;\n });\n defineGlobal(w, COMPOSE, composeFn, (v) => {\n realCompose = typeof v === 'function' ? v : undefined;\n });\n\n try {\n defineProperty(w, key, { value: engine.api, configurable: true, enumerable: false, writable: false });\n } catch {\n // the recorder will not find the hook: nothing is written\n }\n return engine.api;\n}\n\n/**\n * Makes w[name] an accessor returning `ours`; a value already there and\n * every later assignment (other than ours) go to setReal. Fails quietly on a\n * non-configurable property: the page keeps what it has.\n */\nfunction defineGlobal(w: Record<PropertyKey, unknown>, name: string, ours: unknown, setReal: (v: unknown) => void): void {\n try {\n const d = getOwnPropertyDescriptor(w, name);\n if (d && !d.configurable) return;\n if (d) {\n const current = d.get ? d.get.call(w) : d.value;\n if (current !== ours) setReal(current);\n }\n defineProperty(w, name, {\n configurable: true,\n enumerable: false,\n get: () => ours,\n set: (v: unknown) => {\n if (v !== ours) setReal(v);\n },\n });\n } catch {\n // leave the page's property alone\n }\n}\n","// The Redux DevTools hook (state.js, Feature 7). Runs in the page's MAIN\n// world at document_start, before the page's scripts and before recorder.js,\n// while a tab is recorded (and on buffer-mode sites) with \"Record Redux /\n// Zustand state\" on; see hook.ts. It may be injected again into the same\n// page: installHook() then leaves the first one in place.\n\nimport { installHook } from './hook';\n\n// The page's own clock and timers, captured before its scripts can patch them.\nconst now = Date.now;\nconst setTimer = setTimeout;\nconst clearTimer = clearTimeout;\n\ninstallHook(window, {\n now: () => now(),\n setTimeout: (fn, ms) => setTimer.call(window, fn, ms),\n clearTimeout: (id) => clearTimer.call(window, id as number),\n});\n"],"mappings":"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","names":["STATE_HOOK_KEY","stringify","toStringTag","MASK","PASSWORD_KEY","clip","s","n","isPasswordKey","key","PASSWORD_KEY","SERIALIZE_LIMITS","isErrorLike","v","e","describeNode","node","el","s","cls","clip","serialize","value","limits","walk","depth","seen","lim","obj","n","tag","toStringTag","d","out","keys","key","PROPS_MAX_BYTES","PROPS_LEVELS","SERIALIZE_LIMITS","MORE","utf8Bytes","s","n","i","c","maskPasswords","v","masked","obj","key","val","pair","isPasswordKey","MASK","capProps","value","props","limits","PROPS_LEVELS","toRecord","serialize","jsonBytes","PROPS_MAX_BYTES","pack","dropTrailingKeys","truncated","out","stringify","marker","MORE","alreadyDropped","keys","k","kept","more","stringify","keysOf","isArray","protoOf","OBJECT_PROTO","tagOf","mapForEach","setForEach","dateTime","dateIso","unbox","isView","finite","SNAPSHOT_LIMIT","MAX_DEPTH","NOTE_MAX","DESCEND_MAX","MEASURE_CAP","CACHE_MIN","MEASURE_EXACT_MAX","MASKED","MASK","OVERFLOW","isOverflow","formatSize","n","sizeNote","pointerToken","key","keyCache","keyInfo","info","isPasswordKey","isAbsent","v","isPlainObject","p","readKey","o","describeNode","JsonWriter","_JsonWriter","limit","counting","anc","sizes","s","masked","depth","known","mark","start","e","a","i","keys","first","tag","t","isErrorLike","node","toJSON","r","m","k","tooBig","size","max","arr","kids","hold","kid","reserved","room","before","snapshotJson","state","w","c","MAX_PATHS","CHANGES_LIMIT","stringify","keysOf","isArray","hasOwn","TOO_MANY","changesJson","prev","next","changes","walk","e","w","JsonWriter","i","c","isOverflow","add","path","masked","depth","anc","MAX_DEPTH","pa","na","walkArray","isPlainObject","walkObject","pl","nl","n","pv","nv","p","nextKeys","common","key","had","info","keyInfo","kp","km","isAbsent","prevKeys","BURST","INTERVAL_ACTIONS","NAME_MAX","TYPE_MAX","STORES_MAX","stringify","keysOf","nameOf","v","actionType","action","payloadJson","rest","any","key","capped","capProps","StateEngine","deps","early","hook","emit","store","state","s","name","getState","interval","i","fn","kind","json","ts","type","changes","changesJson","snap","snapshotJson","reason","given","EXTENSION","COMPOSE","MEMBERS","defineProperty","getOwnPropertyDescriptor","keysOf","apply","noop","configName","config","nameOf","isHookValue","v","installHook","win","deps","w","key","STATE_HOOK_KEY","existing","early","engine","StateEngine","real","realCompose","assigned","inReal","callReal","f","realHook","hookFn","realComposeFn","composeFn","realMember","name","m","ours","instrumented","instrument","store","inner","origDispatch","origGetState","getState","s","pending","record","t","state","run","fn","args","action","result","out","origReplace","enhancer","withReal","realEnhancer","r","next","create","extensionCompose","funcs","rc","composed","connect","options","realConn","call","conn","k","forward","defineGlobal","setReal","d","current","now","setTimer","clearTimer","installHook","fn","ms","id"],"x_google_ignoreList":[0,1,2,3,4,5,6,7]}
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ // Playwright MCP with the tabtape extension, so record links (the tabtape
3
+ // MCP tool get_record_link) record what an agent does in the browser. The
4
+ // extension records to tabtape in the Repoburg daemon (repoburg-daemon).
5
+ // Use it in place of `npx @playwright/mcp@latest`, with the same options:
6
+ //
7
+ // claude mcp add playwright -- node <Repoburg repo>/tabtape/playwright/mcp.mjs --headless
8
+ //
9
+ // Every start gets a fresh profile, as with --isolated, which cannot load
10
+ // extensions (it uses a browser.newContext() context) and is ignored here.
11
+ // The browser is Playwright's own Chromium: Google Chrome ignores
12
+ // --load-extension since Chrome 137.
13
+
14
+ import { spawn } from 'node:child_process';
15
+ import { existsSync } from 'node:fs';
16
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
17
+ import os from 'node:os';
18
+ import path from 'node:path';
19
+ import { extensionPath } from './tabtape.mjs';
20
+
21
+ // stdout is the MCP channel: only stderr may be written to.
22
+ if (!existsSync(path.join(extensionPath, 'manifest.json'))) {
23
+ console.error(`tabtape: the extension is not built (${extensionPath}); run "npm install && npm run build" in the tabtape directory of the Repoburg repository`);
24
+ process.exit(1);
25
+ }
26
+ const dir = await mkdtemp(path.join(os.tmpdir(), 'tabtape-playwright-mcp-'));
27
+ const config = path.join(dir, 'config.json');
28
+ await writeFile(
29
+ config,
30
+ JSON.stringify({
31
+ browser: {
32
+ browserName: 'chromium',
33
+ launchOptions: { channel: 'chromium', args: [`--disable-extensions-except=${extensionPath}`, `--load-extension=${extensionPath}`] },
34
+ },
35
+ }),
36
+ );
37
+ // Each Playwright MCP release expects its own Chromium build; this is quick
38
+ // when it is there, and downloads it (~100 MB) after an update. Its output
39
+ // goes to stderr.
40
+ await new Promise((resolve) => {
41
+ const install = spawn('npx', ['-y', '@playwright/mcp@latest', 'install-browser', 'chromium'], { stdio: ['ignore', 2, 2] });
42
+ install.on('exit', resolve);
43
+ install.on('error', resolve);
44
+ });
45
+ const args = process.argv.slice(2).filter((a) => a !== '--isolated');
46
+ const child = spawn('npx', ['-y', '@playwright/mcp@latest', '--config', config, '--user-data-dir', path.join(dir, 'profile'), ...args], { stdio: 'inherit' });
47
+
48
+ const cleanup = () => rm(dir, { recursive: true, force: true }).catch(() => {});
49
+ for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) process.on(signal, () => child.kill(signal));
50
+ child.on('error', async (err) => {
51
+ console.error(`tabtape: cannot start Playwright MCP: ${err.message}`);
52
+ await cleanup();
53
+ process.exit(1);
54
+ });
55
+ child.on('exit', async (code, signal) => {
56
+ await cleanup();
57
+ process.exit(code ?? (signal ? 1 : 0));
58
+ });
@@ -0,0 +1,37 @@
1
+ import type { BrowserContext, BrowserType, Page } from 'playwright';
2
+
3
+ /** The unpacked extension, built by `npm run build` in tabtape/. */
4
+ export declare const extensionPath: string;
5
+
6
+ type PersistentContextOptions = NonNullable<Parameters<BrowserType['launchPersistentContext']>[1]>;
7
+
8
+ export interface LaunchOptions extends PersistentContextOptions {
9
+ /** Profile directory; a temporary one by default. */
10
+ userDataDir?: string;
11
+ /**
12
+ * Where tabtape is served, with its path. The extension's setting by
13
+ * default: http://127.0.0.1:9998/tabtape, tabtape in the Repoburg daemon.
14
+ */
15
+ serverUrl?: string;
16
+ }
17
+
18
+ export interface Recording {
19
+ /** Session id, for the tabtape MCP tools. */
20
+ id: string;
21
+ /** The replay, e.g. http://127.0.0.1:9998/tabtape/#/s/<id>. */
22
+ url: string;
23
+ /** Set when deep mode could not start. */
24
+ warning?: string;
25
+ }
26
+
27
+ /** Launches Playwright's Chromium (headless by default) in a persistent context with the tabtape extension. */
28
+ export declare function launch(chromium: BrowserType, options?: LaunchOptions): Promise<BrowserContext>;
29
+
30
+ /** Starts recording the page's tab; before the first goto, the first load is recorded too. Throws when tabtape is not reachable. */
31
+ export declare function start(page: Page, options?: { name?: string; deep?: boolean }): Promise<Recording>;
32
+
33
+ /** Stops the recording; null when nothing was recorded. */
34
+ export declare function stop(target: Page | BrowserContext): Promise<Recording | null>;
35
+
36
+ /** Writes a test step ("test: <name>") into the recording; first waits (up to 5 s) for the recorder to start on the page. */
37
+ export declare function step(page: Page, name: string, props?: Record<string, unknown>): Promise<void>;
@@ -0,0 +1,146 @@
1
+ // Records Playwright runs with tabtape (README: "Playwright scripts"). The
2
+ // recordings go to tabtape in the Repoburg daemon (repoburg-daemon), at
3
+ // http://127.0.0.1:9998/tabtape unless launch() is given another serverUrl.
4
+ //
5
+ // import { chromium } from 'playwright'; // or '@playwright/test'
6
+ // import * as tabtape from '<Repoburg repo>/tabtape/playwright/tabtape.mjs';
7
+ //
8
+ // const context = await tabtape.launch(chromium);
9
+ // const page = context.pages()[0];
10
+ // await tabtape.start(page, { name: 'checkout: coupon' }); // before goto: the first load is recorded too
11
+ // await page.goto('http://localhost:3000/cart');
12
+ // await tabtape.step(page, 'apply the coupon');
13
+ // const recording = await tabtape.stop(context); // { id, url }
14
+ // await context.close();
15
+ //
16
+ // The extension only runs in Playwright's own Chromium, in a persistent
17
+ // context: browser.newContext() contexts have no extensions, and Google
18
+ // Chrome ignores --load-extension since Chrome 137. launch() sees to both.
19
+ // No dependencies: pass in the chromium of the Playwright you use.
20
+
21
+ import { existsSync } from 'node:fs';
22
+ import path from 'node:path';
23
+ import { fileURLToPath } from 'node:url';
24
+
25
+ /** The unpacked extension, built by `npm run build` in tabtape/. */
26
+ export const extensionPath = fileURLToPath(new URL('../extension/dist', import.meta.url));
27
+
28
+ /** Extension ids by context, to wake the extension's service worker when Chrome stopped it. */
29
+ const extensionIds = new WeakMap();
30
+
31
+ /**
32
+ * Launches Playwright's Chromium with the tabtape extension. Takes the
33
+ * options of launchPersistentContext (headless by default), plus
34
+ * `userDataDir` (a temporary profile by default) and `serverUrl` (where
35
+ * tabtape is served, with its path; the extension's setting, by default
36
+ * http://127.0.0.1:9998/tabtape: the Repoburg daemon).
37
+ */
38
+ export async function launch(chromium, options = {}) {
39
+ const { serverUrl, userDataDir = '', args = [], channel = 'chromium', ...rest } = options;
40
+ if (channel !== 'chromium') {
41
+ throw new Error(`tabtape: the "${channel}" browser does not load unpacked extensions; leave channel unset to use Playwright's Chromium`);
42
+ }
43
+ if (!existsSync(path.join(extensionPath, 'manifest.json'))) {
44
+ throw new Error(`tabtape: the extension is not built (${extensionPath}); run "npm install && npm run build" in the tabtape directory of the Repoburg repository`);
45
+ }
46
+ const context = await chromium.launchPersistentContext(userDataDir, {
47
+ headless: true,
48
+ ...rest,
49
+ channel,
50
+ args: [`--disable-extensions-except=${extensionPath}`, `--load-extension=${extensionPath}`, ...args],
51
+ });
52
+ try {
53
+ const sw = await worker(context);
54
+ if (serverUrl) {
55
+ // URLs are built as `${serverUrl}/api/…`: no trailing slash.
56
+ await sw.evaluate(async (url) => {
57
+ const { settings } = await chrome.storage.local.get('settings');
58
+ await chrome.storage.local.set({ settings: { ...settings, serverUrl: url } });
59
+ }, String(serverUrl).trim().replace(/\/+$/, ''));
60
+ }
61
+ } catch (err) {
62
+ await context.close().catch(() => {});
63
+ throw err;
64
+ }
65
+ return context;
66
+ }
67
+
68
+ /**
69
+ * Starts recording the page's tab, titled `name` in the recordings list.
70
+ * Called before the first goto, the first page load is recorded from its
71
+ * first request. `deep` records the network through the debugger (deep
72
+ * mode). Resolves to { id, url } (url: the replay), plus `warning` when deep
73
+ * mode could not start. Throws when tabtape is not reachable (the Repoburg
74
+ * daemon is not running).
75
+ */
76
+ export async function start(page, { name, deep } = {}) {
77
+ const context = page.context();
78
+ const tabId = await tabIdOf(page);
79
+ const sw = await worker(context);
80
+ const rec = await sw.evaluate(([id, opts]) => globalThis.tabtape.start(id, opts), [tabId, { name, deep: deep === true }]);
81
+ const out = { id: rec.sessionId, url: replayUrl(rec.serverUrl, rec.sessionId) };
82
+ if (rec.warning) out.warning = rec.warning;
83
+ return out;
84
+ }
85
+
86
+ /** Stops the recording of the browser of `target` (a page or a context); resolves to { id, url }, or null when nothing was recorded. */
87
+ export async function stop(target) {
88
+ const context = typeof target.pages === 'function' ? target : target.context();
89
+ const sw = await worker(context);
90
+ const res = await sw.evaluate(() => globalThis.tabtape.stop());
91
+ return res ? { id: res.sessionId, url: replayUrl(res.serverUrl, res.sessionId) } : null;
92
+ }
93
+
94
+ /**
95
+ * Writes a test step into the recording, next to what the page did: a
96
+ * custom event named "test: <name>", with optional properties.
97
+ */
98
+ export async function step(page, name, props) {
99
+ // The recorder starts once the extension answers the page, which can be
100
+ // after the load event: a step right after goto() would be lost.
101
+ await page
102
+ .waitForFunction(() => window[Symbol.for('tabtape.recorder')]?.active === true, undefined, { timeout: 5000, polling: 20 })
103
+ .catch(() => {}); // not recorded: the event goes nowhere, as before
104
+ await page.evaluate(([n, p]) => dispatchEvent(new CustomEvent('tabtape:track', { detail: { name: n, props: p } })), [`test: ${name}`, props]);
105
+ }
106
+
107
+ function replayUrl(serverUrl, id) {
108
+ return `${String(serverUrl).replace(/\/+$/, '')}/#/s/${id}`;
109
+ }
110
+
111
+ /** The browser tab of a page: its DevTools target, as the extension's debugger API lists it. */
112
+ async function tabIdOf(page) {
113
+ const cdp = await page.context().newCDPSession(page);
114
+ let targetId;
115
+ try {
116
+ ({ targetInfo: { targetId } } = await cdp.send('Target.getTargetInfo'));
117
+ } finally {
118
+ await cdp.detach().catch(() => {});
119
+ }
120
+ const sw = await worker(page.context());
121
+ const tabId = await sw.evaluate(async (id) => (await chrome.debugger.getTargets()).find((t) => t.id === id)?.tabId, targetId);
122
+ if (tabId === undefined) throw new Error('tabtape: this page is not a browser tab');
123
+ return tabId;
124
+ }
125
+
126
+ const isOurs = (w) => /^chrome-extension:\/\/[a-p]{32}\/background\.js$/.test(w.url());
127
+
128
+ /** The extension's service worker; Chrome stops it when idle, so an extension page wakes it. */
129
+ async function worker(context) {
130
+ let sw = context.serviceWorkers().find(isOurs);
131
+ if (!sw) {
132
+ const id = extensionIds.get(context);
133
+ if (id) {
134
+ const waiting = context.waitForEvent('serviceworker', { predicate: isOurs, timeout: 10_000 });
135
+ const page = await context.newPage();
136
+ await page.goto(`chrome-extension://${id}/popup.html`).catch(() => {});
137
+ sw = await waiting.catch(() => undefined);
138
+ await page.close().catch(() => {});
139
+ } else {
140
+ sw = await context.waitForEvent('serviceworker', { predicate: isOurs, timeout: 10_000 }).catch(() => undefined);
141
+ }
142
+ }
143
+ if (!sw) throw new Error('tabtape: the extension is not running in this browser; launch it with tabtape.launch()');
144
+ extensionIds.set(context, new URL(sw.url()).host);
145
+ return sw;
146
+ }