browser-lens-mcp 1.1.0 → 1.2.1

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.
@@ -1,26 +1,30 @@
1
1
  export function getConnectorScript(httpPort, wsPort) {
2
2
  return `(function(){
3
- if(window.__MCP_BROWSER_LENS__){console.log('[Browser Lens] Already connected.');return}
3
+ if(window.__MCP_BROWSER_LENS__){console.log('[BrowserLens] Already active.');return}
4
4
  window.__MCP_BROWSER_LENS__=true;
5
5
  var WS_URL='ws://localhost:'+${wsPort};
6
6
  var HTTP_URL='http://localhost:'+${httpPort}+'/ingest';
7
- var ws=null,mutQueue=[],timer=null;
8
- var SKIP_TAGS={SCRIPT:1,STYLE:1,META:1,LINK:1,NOSCRIPT:1,BR:1};
9
- var CSS_PROPS=['color','backgroundColor','fontSize','fontFamily','fontWeight','fontStyle','lineHeight','letterSpacing','textAlign','textDecoration','textTransform','display','position','top','right','bottom','left','width','height','minWidth','minHeight','maxWidth','maxHeight','margin','marginTop','marginRight','marginBottom','marginLeft','padding','paddingTop','paddingRight','paddingBottom','paddingLeft','border','borderWidth','borderStyle','borderColor','borderRadius','borderTopLeftRadius','borderTopRightRadius','borderBottomLeftRadius','borderBottomRightRadius','overflow','overflowX','overflowY','opacity','visibility','zIndex','transform','transition','animation','boxShadow','cursor','flexDirection','flexWrap','justifyContent','alignItems','alignSelf','flexGrow','flexShrink','flexBasis','gap','gridTemplateColumns','gridTemplateRows','gridColumn','gridRow','whiteSpace','wordBreak','textOverflow','outline','backgroundImage','backgroundSize','backgroundPosition','backgroundRepeat','float','clear','verticalAlign','listStyleType','boxSizing'];
7
+ var ws=null,mutQueue=[],sendQueue=[];
8
+ var SKIP_TAGS={SCRIPT:1,STYLE:1,META:1,LINK:1,NOSCRIPT:1,BR:1,TEMPLATE:1};
9
+ var CSS_PROPS=['color','backgroundColor','fontSize','fontFamily','fontWeight','fontStyle','lineHeight','letterSpacing','textAlign','textDecoration','textTransform','display','position','top','right','bottom','left','width','height','minWidth','minHeight','maxWidth','maxHeight','margin','marginTop','marginRight','marginBottom','marginLeft','padding','paddingTop','paddingRight','paddingBottom','paddingLeft','borderWidth','borderStyle','borderColor','borderRadius','borderTopLeftRadius','borderTopRightRadius','borderBottomLeftRadius','borderBottomRightRadius','overflow','overflowX','overflowY','opacity','visibility','zIndex','transform','transition','animation','boxShadow','cursor','flexDirection','flexWrap','justifyContent','alignItems','alignSelf','flexGrow','flexShrink','flexBasis','gap','gridTemplateColumns','gridTemplateRows','gridColumn','gridRow','whiteSpace','wordBreak','textOverflow','outline','backgroundImage','backgroundSize','backgroundPosition','backgroundRepeat','float','clear','verticalAlign','listStyleType','boxSizing'];
10
+
11
+ function log(msg){console.log('[BrowserLens] '+msg);}
12
+ function err(msg,e){console.error('[BrowserLens] '+msg,e||'');}
10
13
 
11
14
  function buildSelector(el){
15
+ if(!el||!el.tagName)return'unknown';
12
16
  if(el.id)return'#'+el.id;
13
17
  var s=el.tagName.toLowerCase();
14
18
  if(el.className&&typeof el.className==='string'){
15
- var cls=el.className.trim().split(/\\s+/).slice(0,3).join('.');
19
+ var cls=el.className.trim().split(/\\s+/).filter(Boolean).slice(0,3).join('.');
16
20
  if(cls)s+='.'+cls;
17
21
  }
18
22
  var p=el.parentElement;
19
23
  if(p&&p!==document.documentElement&&p!==document.body){
20
- var children=Array.from(p.children).filter(function(c){return c.tagName===el.tagName});
21
- if(children.length>1){
22
- var idx=children.indexOf(el);
23
- s+=':nth-child('+(idx+1)+')';
24
+ var siblings=Array.from(p.children).filter(function(c){return c.tagName===el.tagName});
25
+ if(siblings.length>1){
26
+ var idx=siblings.indexOf(el);
27
+ if(idx>=0)s+=':nth-child('+(idx+1)+')';
24
28
  }
25
29
  }
26
30
  return s;
@@ -31,149 +35,123 @@ function captureDomNode(el,depth,maxDepth){
31
35
  var tag=el.tagName;
32
36
  if(SKIP_TAGS[tag])return null;
33
37
  var attrs={};
34
- for(var i=0;i<el.attributes.length;i++){
35
- var a=el.attributes[i];
36
- attrs[a.name]=a.value.slice(0,200);
37
- }
38
+ try{for(var i=0;i<Math.min(el.attributes.length,20);i++){var a=el.attributes[i];attrs[a.name]=a.value.slice(0,200);}}catch(e){}
38
39
  var children=[];
39
40
  if(depth<maxDepth){
40
41
  var ch=el.children;
41
- for(var j=0;j<Math.min(ch.length,50);j++){
42
+ for(var j=0;j<Math.min(ch.length,40);j++){
42
43
  var c=captureDomNode(ch[j],depth+1,maxDepth);
43
44
  if(c)children.push(c);
44
45
  }
45
46
  }
46
- var text=el.textContent||'';
47
- if(text.length>200)text=text.slice(0,200)+'...';
48
- if(children.length>0)text='';
47
+ var text='';
48
+ try{text=el.textContent||'';if(text.length>200)text=text.slice(0,200)+'...';if(children.length>0)text='';}catch(e){}
49
49
  return{
50
- selector:buildSelector(el),
51
- tagName:tag.toLowerCase(),
52
- id:el.id||'',
50
+ selector:buildSelector(el),tagName:tag.toLowerCase(),id:el.id||'',
53
51
  classNames:el.className&&typeof el.className==='string'?el.className.trim().split(/\\s+/).filter(Boolean):[],
54
- attributes:attrs,
55
- textContent:text,
56
- innerHTML:'',
57
- outerHTML:'',
58
- childCount:el.children.length,
59
- children:children,
60
- depth:depth
52
+ attributes:attrs,textContent:text,innerHTML:'',outerHTML:'',
53
+ childCount:el.children.length,children:children,depth:depth
61
54
  };
62
55
  }
63
56
 
64
57
  function captureDom(){
65
- var root=captureDomNode(document.documentElement,0,10);
66
- if(!root)return null;
67
- var total=document.querySelectorAll('*').length;
68
- var semantic=[];
69
- var landmarks=['header','nav','main','aside','footer','section','article','form'];
70
- landmarks.forEach(function(tag){
71
- document.querySelectorAll(tag).forEach(function(el){
72
- semantic.push({tag:tag,role:el.getAttribute('role')||'',label:el.getAttribute('aria-label')||'',selector:buildSelector(el),children:[]});
58
+ try{
59
+ var root=captureDomNode(document.documentElement,0,8);
60
+ if(!root)return null;
61
+ var total=document.querySelectorAll('*').length;
62
+ var semantic=[];
63
+ ['header','nav','main','aside','footer','section','article','form'].forEach(function(tag){
64
+ document.querySelectorAll(tag).forEach(function(el){
65
+ semantic.push({tag:tag,role:el.getAttribute('role')||'',label:el.getAttribute('aria-label')||'',selector:buildSelector(el),children:[]});
66
+ });
73
67
  });
74
- });
75
- document.querySelectorAll('h1,h2,h3,h4,h5,h6').forEach(function(el){
76
- semantic.push({tag:el.tagName.toLowerCase(),level:parseInt(el.tagName[1]),label:el.textContent.slice(0,100),selector:buildSelector(el),children:[]});
77
- });
78
- return{
79
- timestamp:Date.now(),
80
- url:location.href,
81
- title:document.title,
82
- doctype:document.doctype?document.doctype.name:'html',
83
- charset:document.characterSet,
84
- viewport:{width:window.innerWidth,height:window.innerHeight,scrollX:window.scrollX,scrollY:window.scrollY,devicePixelRatio:window.devicePixelRatio,scrollWidth:document.documentElement.scrollWidth,scrollHeight:document.documentElement.scrollHeight},
85
- rootElement:root,
86
- totalElements:total,
87
- semanticStructure:semantic
88
- };
68
+ document.querySelectorAll('h1,h2,h3,h4,h5,h6').forEach(function(el){
69
+ semantic.push({tag:el.tagName.toLowerCase(),level:parseInt(el.tagName[1]),label:(el.textContent||'').slice(0,100),selector:buildSelector(el),children:[]});
70
+ });
71
+ log('DOM captured: '+total+' elements');
72
+ return{timestamp:Date.now(),url:location.href,title:document.title,doctype:'html',charset:document.characterSet,
73
+ viewport:{width:window.innerWidth,height:window.innerHeight,scrollX:window.scrollX,scrollY:window.scrollY,devicePixelRatio:window.devicePixelRatio,scrollWidth:document.documentElement.scrollWidth,scrollHeight:document.documentElement.scrollHeight},
74
+ rootElement:root,totalElements:total,semanticStructure:semantic};
75
+ }catch(e){err('DOM capture failed',e);return null;}
89
76
  }
90
77
 
91
78
  function captureElementDetail(el){
92
- var sel=buildSelector(el);
93
- var cs=getComputedStyle(el);
94
- var styles={};
95
- CSS_PROPS.forEach(function(p){styles[p]=cs.getPropertyValue(p.replace(/[A-Z]/g,function(m){return'-'+m.toLowerCase()}))||cs[p]||'';});
96
- var rect=el.getBoundingClientRect();
97
- var layout={
98
- selector:sel,tagName:el.tagName.toLowerCase(),
99
- box:{width:rect.width,height:rect.height,padding:{top:parseFloat(cs.paddingTop)||0,right:parseFloat(cs.paddingRight)||0,bottom:parseFloat(cs.paddingBottom)||0,left:parseFloat(cs.paddingLeft)||0},margin:{top:parseFloat(cs.marginTop)||0,right:parseFloat(cs.marginRight)||0,bottom:parseFloat(cs.marginBottom)||0,left:parseFloat(cs.marginLeft)||0},border:{top:parseFloat(cs.borderTopWidth)||0,right:parseFloat(cs.borderRightWidth)||0,bottom:parseFloat(cs.borderBottomWidth)||0,left:parseFloat(cs.borderLeftWidth)||0},contentWidth:rect.width-parseFloat(cs.paddingLeft||'0')-parseFloat(cs.paddingRight||'0')-parseFloat(cs.borderLeftWidth||'0')-parseFloat(cs.borderRightWidth||'0'),contentHeight:rect.height-parseFloat(cs.paddingTop||'0')-parseFloat(cs.paddingBottom||'0')-parseFloat(cs.borderTopWidth||'0')-parseFloat(cs.borderBottomWidth||'0')},
100
- position:{type:cs.position,top:rect.top,left:rect.left,right:rect.right,bottom:rect.bottom,offsetParent:el.offsetParent?buildSelector(el.offsetParent):'',boundingRect:{x:rect.x,y:rect.y,width:rect.width,height:rect.height}},
101
- display:cs.display,overflow:{x:cs.overflowX,y:cs.overflowY},zIndex:cs.zIndex,transform:cs.transform,opacity:cs.opacity,visibility:cs.visibility
102
- };
103
- if(cs.display==='flex'||cs.display==='inline-flex'){
104
- layout.flexInfo={direction:cs.flexDirection,wrap:cs.flexWrap,justifyContent:cs.justifyContent,alignItems:cs.alignItems,gap:cs.gap,children:[]};
105
- }
106
- if(cs.display==='grid'||cs.display==='inline-grid'){
107
- layout.gridInfo={templateColumns:cs.gridTemplateColumns,templateRows:cs.gridTemplateRows,gap:cs.gap,areas:cs.gridTemplateAreas,children:[]};
108
- }
109
- var snap=captureDomNode(el,0,2);
110
- var acc=null;
111
- if(el.getAttribute('role')||el.getAttribute('aria-label')||el.tabIndex>=0){
112
- acc={selector:sel,tagName:el.tagName.toLowerCase(),role:el.getAttribute('role')||undefined,ariaLabel:el.getAttribute('aria-label')||undefined,ariaDescribedBy:el.getAttribute('aria-describedby')||undefined,ariaHidden:el.getAttribute('aria-hidden')==='true',tabIndex:el.tabIndex,altText:el.getAttribute('alt')||undefined,hasLabel:!!(el.getAttribute('aria-label')||el.getAttribute('title')),issues:[]};
113
- }
114
- return{snapshot:snap,computedStyle:{selector:sel,tagName:el.tagName.toLowerCase(),styles:styles,appliedClasses:el.className&&typeof el.className==='string'?el.className.trim().split(/\\s+/).filter(Boolean):[],matchedRules:[]},layout:layout,accessibility:acc};
79
+ try{
80
+ var sel=buildSelector(el);
81
+ var cs=getComputedStyle(el);
82
+ var styles={};
83
+ CSS_PROPS.forEach(function(p){try{styles[p]=cs.getPropertyValue(p.replace(/[A-Z]/g,function(m){return'-'+m.toLowerCase()}))||cs[p]||'';}catch(e){styles[p]='';}});
84
+ var rect=el.getBoundingClientRect();
85
+ var layout={
86
+ selector:sel,tagName:el.tagName.toLowerCase(),
87
+ box:{width:rect.width,height:rect.height,padding:{top:parseFloat(cs.paddingTop)||0,right:parseFloat(cs.paddingRight)||0,bottom:parseFloat(cs.paddingBottom)||0,left:parseFloat(cs.paddingLeft)||0},margin:{top:parseFloat(cs.marginTop)||0,right:parseFloat(cs.marginRight)||0,bottom:parseFloat(cs.marginBottom)||0,left:parseFloat(cs.marginLeft)||0},border:{top:parseFloat(cs.borderTopWidth)||0,right:parseFloat(cs.borderRightWidth)||0,bottom:parseFloat(cs.borderBottomWidth)||0,left:parseFloat(cs.borderLeftWidth)||0},contentWidth:Math.max(0,rect.width-(parseFloat(cs.paddingLeft)||0)-(parseFloat(cs.paddingRight)||0)-(parseFloat(cs.borderLeftWidth)||0)-(parseFloat(cs.borderRightWidth)||0)),contentHeight:Math.max(0,rect.height-(parseFloat(cs.paddingTop)||0)-(parseFloat(cs.paddingBottom)||0)-(parseFloat(cs.borderTopWidth)||0)-(parseFloat(cs.borderBottomWidth)||0))},
88
+ position:{type:cs.position,top:rect.top,left:rect.left,right:rect.right,bottom:rect.bottom,offsetParent:el.offsetParent?buildSelector(el.offsetParent):'',boundingRect:{x:rect.x,y:rect.y,width:rect.width,height:rect.height}},
89
+ display:cs.display,overflow:{x:cs.overflowX,y:cs.overflowY},zIndex:cs.zIndex,transform:cs.transform,opacity:cs.opacity,visibility:cs.visibility
90
+ };
91
+ if(cs.display==='flex'||cs.display==='inline-flex')layout.flexInfo={direction:cs.flexDirection,wrap:cs.flexWrap,justifyContent:cs.justifyContent,alignItems:cs.alignItems,gap:cs.gap,children:[]};
92
+ if(cs.display==='grid'||cs.display==='inline-grid')layout.gridInfo={templateColumns:cs.gridTemplateColumns,templateRows:cs.gridTemplateRows,gap:cs.gap,areas:cs.gridTemplateAreas,children:[]};
93
+ var snap=captureDomNode(el,0,2);
94
+ var acc=null;
95
+ if(el.getAttribute('role')||el.getAttribute('aria-label')||el.tabIndex>=0){
96
+ acc={selector:sel,tagName:el.tagName.toLowerCase(),role:el.getAttribute('role')||undefined,ariaLabel:el.getAttribute('aria-label')||undefined,ariaDescribedBy:el.getAttribute('aria-describedby')||undefined,ariaHidden:el.getAttribute('aria-hidden')==='true',tabIndex:el.tabIndex,altText:el.getAttribute('alt')||undefined,hasLabel:!!(el.getAttribute('aria-label')||el.getAttribute('title')),issues:[]};
97
+ }
98
+ return{snapshot:snap,computedStyle:{selector:sel,tagName:el.tagName.toLowerCase(),styles:styles,appliedClasses:el.className&&typeof el.className==='string'?el.className.trim().split(/\\s+/).filter(Boolean):[],matchedRules:[]},layout:layout,accessibility:acc};
99
+ }catch(e){err('Element detail failed for '+buildSelector(el),e);return null;}
115
100
  }
116
101
 
117
102
  function captureTopElements(){
118
103
  var elements={};
119
- var els=document.querySelectorAll('body > *');
120
- var count=0;
121
- function addEl(el){
122
- if(count>=30)return;
123
- if(SKIP_TAGS[el.tagName])return;
124
- var sel=buildSelector(el);
125
- elements[sel]=captureElementDetail(el);
126
- count++;
127
- var ch=el.children;
128
- for(var i=0;i<Math.min(ch.length,5);i++){
129
- if(count>=30)break;
130
- if(!SKIP_TAGS[ch[i].tagName]){
131
- var csel=buildSelector(ch[i]);
132
- elements[csel]=captureElementDetail(ch[i]);
133
- count++;
104
+ try{
105
+ var els=document.querySelectorAll('body > *');
106
+ var count=0;
107
+ function addEl(el){
108
+ if(count>=30||SKIP_TAGS[el.tagName])return;
109
+ var d=captureElementDetail(el);
110
+ if(d){elements[buildSelector(el)]=d;count++;}
111
+ var ch=el.children;
112
+ for(var i=0;i<Math.min(ch.length,5)&&count<30;i++){
113
+ if(!SKIP_TAGS[ch[i].tagName]){var cd=captureElementDetail(ch[i]);if(cd){elements[buildSelector(ch[i])]=cd;count++;}}
134
114
  }
135
115
  }
136
- }
137
- for(var i=0;i<els.length;i++)addEl(els[i]);
116
+ for(var i=0;i<els.length;i++)addEl(els[i]);
117
+ log('Elements captured: '+count);
118
+ }catch(e){err('Top elements capture failed',e);}
138
119
  return elements;
139
120
  }
140
121
 
141
122
  function captureCssVars(){
142
123
  var vars={},count=0;
143
- var cs=getComputedStyle(document.documentElement);
144
- for(var i=0;i<cs.length;i++){
145
- if(cs[i].startsWith('--')){vars[cs[i]]=cs.getPropertyValue(cs[i]).trim();count++;}
146
- }
147
- var sheets=document.styleSheets;
148
124
  try{
149
- for(var s=0;s<sheets.length;s++){
150
- try{
151
- var rules=sheets[s].cssRules;
152
- for(var r=0;r<rules.length;r++){
153
- if(rules[r].style){
154
- for(var p=0;p<rules[r].style.length;p++){
155
- var prop=rules[r].style[p];
156
- if(prop.startsWith('--')&&!vars[prop]){vars[prop]=rules[r].style.getPropertyValue(prop).trim();count++;}
157
- }
158
- }
159
- }
160
- }catch(e){}
125
+ var cs=getComputedStyle(document.documentElement);
126
+ for(var i=0;i<cs.length;i++){
127
+ if(cs[i].startsWith('--')){vars[cs[i]]=cs.getPropertyValue(cs[i]).trim();count++;}
161
128
  }
162
- }catch(e){}
129
+ try{
130
+ var sheets=document.styleSheets;
131
+ for(var s=0;s<sheets.length;s++){
132
+ try{var rules=sheets[s].cssRules;if(!rules)continue;
133
+ for(var r=0;r<rules.length;r++){if(rules[r].style){for(var p=0;p<rules[r].style.length;p++){var prop=rules[r].style[p];if(prop.startsWith('--')&&!vars[prop]){vars[prop]=rules[r].style.getPropertyValue(prop).trim();count++;}}}}
134
+ }catch(e){}
135
+ }
136
+ }catch(e){}
137
+ log('CSS vars captured: '+count);
138
+ }catch(e){err('CSS vars failed',e);}
163
139
  return{timestamp:Date.now(),variables:vars,totalCount:count};
164
140
  }
165
141
 
166
142
  function captureTypography(){
167
- var fonts={},fontMap={};
168
- var textEls=document.querySelectorAll('p,h1,h2,h3,h4,h5,h6,span,a,li,td,th,label,button,input,textarea,div');
169
- for(var i=0;i<Math.min(textEls.length,200);i++){
170
- var el=textEls[i];
171
- if(!el.textContent||!el.textContent.trim())continue;
172
- var cs=getComputedStyle(el);
173
- var key=cs.fontFamily+'|'+cs.fontSize+'|'+cs.fontWeight+'|'+cs.lineHeight;
174
- if(!fontMap[key]){fontMap[key]={family:cs.fontFamily,size:cs.fontSize,weight:cs.fontWeight,lineHeight:cs.lineHeight,color:cs.color,selector:buildSelector(el),count:0};}
175
- fontMap[key].count++;
176
- }
143
+ var fontMap={};
144
+ try{
145
+ var textEls=document.querySelectorAll('p,h1,h2,h3,h4,h5,h6,span,a,li,td,th,label,button,input,textarea,div');
146
+ for(var i=0;i<Math.min(textEls.length,200);i++){
147
+ var el=textEls[i];
148
+ if(!el.textContent||!el.textContent.trim())continue;
149
+ var cs=getComputedStyle(el);
150
+ var key=cs.fontFamily+'|'+cs.fontSize+'|'+cs.fontWeight+'|'+cs.lineHeight;
151
+ if(!fontMap[key])fontMap[key]={family:cs.fontFamily,size:cs.fontSize,weight:cs.fontWeight,lineHeight:cs.lineHeight,color:cs.color,selector:buildSelector(el),count:0};
152
+ fontMap[key].count++;
153
+ }
154
+ }catch(e){err('Typography failed',e);}
177
155
  return{timestamp:Date.now(),fonts:Object.values(fontMap).sort(function(a,b){return b.count-a.count}),fontFaces:[]};
178
156
  }
179
157
 
@@ -187,21 +165,15 @@ function rgbToHex(rgb){
187
165
 
188
166
  function captureColors(){
189
167
  var colorMap={},bgMap={},borderMap={};
190
- var els=document.querySelectorAll('*');
191
- for(var i=0;i<Math.min(els.length,300);i++){
192
- var cs=getComputedStyle(els[i]);
193
- var sel=buildSelector(els[i]);
194
- function addColor(map,val){
195
- if(!val||val==='transparent'||val==='rgba(0, 0, 0, 0)')return;
196
- var hex=rgbToHex(val);
197
- if(!map[hex])map[hex]={value:val,hex:hex,count:0,elements:[]};
198
- map[hex].count++;
199
- if(map[hex].elements.length<5)map[hex].elements.push(sel);
168
+ try{
169
+ var els=document.querySelectorAll('*');
170
+ for(var i=0;i<Math.min(els.length,300);i++){
171
+ var cs=getComputedStyle(els[i]);
172
+ var sel=buildSelector(els[i]);
173
+ function addC(map,val){if(!val||val==='transparent'||val==='rgba(0, 0, 0, 0)')return;var hex=rgbToHex(val);if(!map[hex])map[hex]={value:val,hex:hex,count:0,elements:[]};map[hex].count++;if(map[hex].elements.length<5)map[hex].elements.push(sel);}
174
+ addC(colorMap,cs.color);addC(bgMap,cs.backgroundColor);addC(borderMap,cs.borderColor);
200
175
  }
201
- addColor(colorMap,cs.color);
202
- addColor(bgMap,cs.backgroundColor);
203
- addColor(borderMap,cs.borderColor);
204
- }
176
+ }catch(e){err('Colors failed',e);}
205
177
  var all=Object.assign({},colorMap,bgMap,borderMap);
206
178
  return{timestamp:Date.now(),colors:Object.values(colorMap).sort(function(a,b){return b.count-a.count}),backgroundColors:Object.values(bgMap).sort(function(a,b){return b.count-a.count}),borderColors:Object.values(borderMap).sort(function(a,b){return b.count-a.count}),totalUniqueColors:Object.keys(all).length};
207
179
  }
@@ -209,99 +181,127 @@ function captureColors(){
209
181
  function captureAccessibility(){
210
182
  var elements=[];
211
183
  var summary={totalInteractive:0,withLabels:0,withoutLabels:0,imagesWithAlt:0,imagesWithoutAlt:0,headingLevels:{},landmarks:[],issues:[]};
212
- var interactive=document.querySelectorAll('a,button,input,select,textarea,[tabindex],[role]');
213
- summary.totalInteractive=interactive.length;
214
- for(var i=0;i<interactive.length;i++){
215
- var el=interactive[i];
216
- var hasLabel=!!(el.getAttribute('aria-label')||el.getAttribute('title')||el.textContent.trim());
217
- if(hasLabel)summary.withLabels++;else{summary.withoutLabels++;summary.issues.push('Missing label: '+buildSelector(el));}
218
- elements.push({selector:buildSelector(el),tagName:el.tagName.toLowerCase(),role:el.getAttribute('role')||undefined,ariaLabel:el.getAttribute('aria-label')||undefined,tabIndex:el.tabIndex,hasLabel:hasLabel,issues:hasLabel?[]:['Missing accessible label']});
219
- }
220
- document.querySelectorAll('img').forEach(function(img){
221
- if(img.alt)summary.imagesWithAlt++;else{summary.imagesWithoutAlt++;summary.issues.push('Image without alt: '+buildSelector(img));}
222
- });
223
- document.querySelectorAll('h1,h2,h3,h4,h5,h6').forEach(function(h){
224
- var lv=h.tagName;
225
- summary.headingLevels[lv]=(summary.headingLevels[lv]||0)+1;
226
- });
227
- ['banner','navigation','main','complementary','contentinfo'].forEach(function(r){
228
- var el=document.querySelector('[role="'+r+'"]');
229
- if(el)summary.landmarks.push(r);
230
- });
184
+ try{
185
+ var interactive=document.querySelectorAll('a,button,input,select,textarea,[tabindex],[role]');
186
+ summary.totalInteractive=interactive.length;
187
+ for(var i=0;i<Math.min(interactive.length,100);i++){
188
+ var el=interactive[i];
189
+ var hasLabel=!!(el.getAttribute('aria-label')||el.getAttribute('title')||(el.textContent||'').trim());
190
+ if(hasLabel)summary.withLabels++;else{summary.withoutLabels++;summary.issues.push('Missing label: '+buildSelector(el));}
191
+ elements.push({selector:buildSelector(el),tagName:el.tagName.toLowerCase(),role:el.getAttribute('role')||undefined,ariaLabel:el.getAttribute('aria-label')||undefined,tabIndex:el.tabIndex,hasLabel:hasLabel,issues:hasLabel?[]:['Missing accessible label']});
192
+ }
193
+ document.querySelectorAll('img').forEach(function(img){if(img.alt)summary.imagesWithAlt++;else{summary.imagesWithoutAlt++;summary.issues.push('Image without alt: '+buildSelector(img));}});
194
+ document.querySelectorAll('h1,h2,h3,h4,h5,h6').forEach(function(h){var lv=h.tagName;summary.headingLevels[lv]=(summary.headingLevels[lv]||0)+1;});
195
+ ['banner','navigation','main','complementary','contentinfo'].forEach(function(r){if(document.querySelector('[role="'+r+'"]'))summary.landmarks.push(r);});
196
+ }catch(e){err('Accessibility failed',e);}
231
197
  return{timestamp:Date.now(),elements:elements.slice(0,100),summary:summary};
232
198
  }
233
199
 
234
200
  function captureResponsive(){
235
201
  var bps=[{q:'(max-width: 319px)',matches:false},{q:'(min-width: 320px) and (max-width: 374px)',matches:false},{q:'(min-width: 375px) and (max-width: 767px)',matches:false},{q:'(min-width: 768px) and (max-width: 1023px)',matches:false},{q:'(min-width: 1024px) and (max-width: 1279px)',matches:false},{q:'(min-width: 1280px) and (max-width: 1439px)',matches:false},{q:'(min-width: 1440px)',matches:false}];
236
202
  var active=[];
237
- bps.forEach(function(bp){
238
- bp.matches=window.matchMedia(bp.q).matches;
239
- if(bp.matches)active.push(bp.q);
240
- });
203
+ bps.forEach(function(bp){bp.matches=window.matchMedia(bp.q).matches;if(bp.matches)active.push(bp.q);});
241
204
  return{viewport:{width:window.innerWidth,height:window.innerHeight,scrollX:window.scrollX,scrollY:window.scrollY,devicePixelRatio:window.devicePixelRatio,scrollWidth:document.documentElement.scrollWidth,scrollHeight:document.documentElement.scrollHeight},activeMediaQueries:active,breakpoints:bps};
242
205
  }
243
206
 
244
207
  function captureSpacing(){
245
- var entries=[];
246
- var vals={margin:{},padding:{}};
247
- var els=document.querySelectorAll('body *');
248
- for(var i=0;i<Math.min(els.length,50);i++){
249
- var el=els[i];
250
- if(SKIP_TAGS[el.tagName])continue;
251
- var cs=getComputedStyle(el);
252
- var m={top:parseFloat(cs.marginTop)||0,right:parseFloat(cs.marginRight)||0,bottom:parseFloat(cs.marginBottom)||0,left:parseFloat(cs.marginLeft)||0};
253
- var p={top:parseFloat(cs.paddingTop)||0,right:parseFloat(cs.paddingRight)||0,bottom:parseFloat(cs.paddingBottom)||0,left:parseFloat(cs.paddingLeft)||0};
254
- entries.push({selector:buildSelector(el),margin:m,padding:p,gap:cs.gap||undefined});
255
- [m.top,m.right,m.bottom,m.left].forEach(function(v){if(v>0)vals.margin[v+'px']=(vals.margin[v+'px']||0)+1;});
256
- [p.top,p.right,p.bottom,p.left].forEach(function(v){if(v>0)vals.padding[v+'px']=(vals.padding[v+'px']||0)+1;});
257
- }
258
- var scale=Object.keys(Object.assign({},vals.margin,vals.padding)).sort(function(a,b){return parseFloat(a)-parseFloat(b)});
259
- return{timestamp:Date.now(),elements:entries,inconsistencies:[],spacingScale:scale};
208
+ var entries=[],vals={margin:{},padding:{}};
209
+ try{
210
+ var els=document.querySelectorAll('body *');
211
+ for(var i=0;i<Math.min(els.length,50);i++){
212
+ var el=els[i];if(SKIP_TAGS[el.tagName])continue;
213
+ var cs=getComputedStyle(el);
214
+ var m={top:parseFloat(cs.marginTop)||0,right:parseFloat(cs.marginRight)||0,bottom:parseFloat(cs.marginBottom)||0,left:parseFloat(cs.marginLeft)||0};
215
+ var p={top:parseFloat(cs.paddingTop)||0,right:parseFloat(cs.paddingRight)||0,bottom:parseFloat(cs.paddingBottom)||0,left:parseFloat(cs.paddingLeft)||0};
216
+ entries.push({selector:buildSelector(el),margin:m,padding:p,gap:cs.gap||undefined});
217
+ [m.top,m.right,m.bottom,m.left].forEach(function(v){if(v>0)vals.margin[v+'px']=(vals.margin[v+'px']||0)+1;});
218
+ [p.top,p.right,p.bottom,p.left].forEach(function(v){if(v>0)vals.padding[v+'px']=(vals.padding[v+'px']||0)+1;});
219
+ }
220
+ }catch(e){err('Spacing failed',e);}
221
+ return{timestamp:Date.now(),elements:entries,inconsistencies:[],spacingScale:Object.keys(Object.assign({},vals.margin,vals.padding)).sort(function(a,b){return parseFloat(a)-parseFloat(b)})};
260
222
  }
261
223
 
224
+ var _ssLastOk=0,_ssFailing=false;
225
+ function _doCapture(opts){
226
+ var w=Math.min(window.innerWidth,1440),h=Math.min(window.innerHeight,900);
227
+ return html2canvas(document.body,Object.assign({scale:1,width:w,height:h,logging:false,removeContainer:true,imageTimeout:5000},opts)).then(function(canvas){
228
+ var dataUrl=canvas.toDataURL('image/png');
229
+ _ssLastOk=Date.now();_ssFailing=false;
230
+ log('Screenshot OK: '+canvas.width+'x'+canvas.height);
231
+ return{timestamp:Date.now(),type:'viewport',width:canvas.width,height:canvas.height,dataUrl:dataUrl,format:'png'};
232
+ });
233
+ }
262
234
  function captureScreenshot(){
263
- try{
264
- var w=Math.min(window.innerWidth,1200);
265
- var h=Math.min(window.innerHeight,900);
266
- var canvas=document.createElement('canvas');
267
- canvas.width=w;canvas.height=h;
268
- var ctx=canvas.getContext('2d');
269
- var data='<svg xmlns="http://www.w3.org/2000/svg" width="'+w+'" height="'+h+'"><foreignObject width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="font-size:12px">'+new XMLSerializer().serializeToString(document.documentElement)+'</div></foreignObject></svg>';
270
- var img=new Image();
271
- var blob=new Blob([data],{type:'image/svg+xml;charset=utf-8'});
272
- var url=URL.createObjectURL(blob);
273
- return new Promise(function(resolve){
274
- img.onload=function(){
275
- ctx.drawImage(img,0,0,w,h);
276
- URL.revokeObjectURL(url);
277
- resolve({timestamp:Date.now(),type:'viewport',width:w,height:h,dataUrl:canvas.toDataURL('image/png'),format:'png'});
278
- };
279
- img.onerror=function(){URL.revokeObjectURL(url);resolve(null);};
280
- img.src=url;
281
- });
282
- }catch(e){return Promise.resolve(null);}
235
+ if(_ssFailing&&Date.now()-_ssLastOk<120000)return Promise.resolve(null);
236
+ return new Promise(function(resolve){
237
+ function run(){
238
+ _doCapture({useCORS:true,allowTaint:false,foreignObjectRendering:false}).then(resolve)
239
+ .catch(function(){
240
+ log('Retrying screenshot without cross-origin images...');
241
+ _doCapture({useCORS:false,allowTaint:false,foreignObjectRendering:false,ignoreElements:function(el){
242
+ if(el.tagName==='IMG'){var s=el.src||'';if(s&&!s.startsWith(location.origin)&&!s.startsWith('data:'))return true;}
243
+ return false;
244
+ }}).then(resolve)
245
+ .catch(function(e){
246
+ _ssFailing=true;
247
+ err('Screenshot failed (will retry in 2min)',e);
248
+ resolve(null);
249
+ });
250
+ });
251
+ }
252
+ try{
253
+ if(typeof html2canvas==='function'){run();}
254
+ else{
255
+ log('Loading html2canvas...');
256
+ var s=document.createElement('script');
257
+ s.src='https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js';
258
+ s.onload=function(){log('html2canvas ready');run();};
259
+ s.onerror=function(){err('CDN load failed');resolve(null);};
260
+ document.head.appendChild(s);
261
+ }
262
+ }catch(e){err('Screenshot error',e);resolve(null);}
263
+ });
283
264
  }
284
265
 
285
- function fullSync(){
286
- var dom=captureDom();
287
- var elements=captureTopElements();
288
- var cssVars=captureCssVars();
289
- var typo=captureTypography();
290
- var colors=captureColors();
291
- var acc=captureAccessibility();
292
- var resp=captureResponsive();
293
- var spacing=captureSpacing();
294
- var muts=mutQueue.splice(0);
295
- var payload={timestamp:Date.now(),url:location.href,userAgent:navigator.userAgent,dom:dom,elements:elements,cssVariables:cssVars,typography:typo,colors:colors,accessibility:acc,responsive:resp,spacing:spacing,mutations:muts.length?muts:undefined};
296
- send(JSON.stringify(payload));
297
- captureScreenshot().then(function(shot){
298
- if(shot)send(JSON.stringify({timestamp:Date.now(),screenshots:[shot]}));
299
- });
266
+ function send(data){
267
+ var payload=typeof data==='string'?data:JSON.stringify(data);
268
+ var size=payload.length;
269
+ if(ws&&ws.readyState===1){
270
+ try{ws.send(payload);return true;}catch(e){err('WS send failed ('+size+' bytes)',e);}
271
+ }
272
+ try{
273
+ return fetch(HTTP_URL,{method:'POST',headers:{'Content-Type':'application/json'},body:payload,keepalive:size<60000}).then(function(r){
274
+ if(!r.ok)err('HTTP send failed: '+r.status);
275
+ return r.ok;
276
+ }).catch(function(e){err('HTTP send error',e);return false;});
277
+ }catch(e){err('Send failed completely',e);return false;}
300
278
  }
301
279
 
302
- function send(payload){
303
- if(ws&&ws.readyState===1){ws.send(payload);return;}
304
- try{navigator.sendBeacon?navigator.sendBeacon(HTTP_URL,payload):fetch(HTTP_URL,{method:'POST',headers:{'Content-Type':'application/json'},body:payload}).catch(function(){});}catch(e){}
280
+ function fullSync(){
281
+ try{
282
+ log('Syncing...');
283
+ var dom=captureDom();
284
+ var elements=captureTopElements();
285
+ var cssVars=captureCssVars();
286
+ var typo=captureTypography();
287
+ var colors=captureColors();
288
+ var acc=captureAccessibility();
289
+ var resp=captureResponsive();
290
+ var spacing=captureSpacing();
291
+ var muts=mutQueue.splice(0);
292
+ var payload={timestamp:Date.now(),url:location.href,userAgent:navigator.userAgent,dom:dom,elements:elements,cssVariables:cssVars,typography:typo,colors:colors,accessibility:acc,responsive:resp,spacing:spacing};
293
+ if(muts.length)payload.mutations=muts;
294
+ send(payload);
295
+ captureScreenshot().then(function(shot){
296
+ if(shot){
297
+ log('Sending screenshot ('+shot.width+'x'+shot.height+')...');
298
+ send({timestamp:Date.now(),screenshots:[shot]});
299
+ }else{
300
+ err('Screenshot was null - check browser console for html2canvas errors');
301
+ }
302
+ });
303
+ log('Sync complete');
304
+ }catch(e){err('fullSync failed',e);}
305
305
  }
306
306
 
307
307
  try{
@@ -317,20 +317,20 @@ try{
317
317
  });
318
318
  });
319
319
  observer.observe(document.documentElement,{childList:true,attributes:true,subtree:true,attributeOldValue:true});
320
- }catch(e){}
320
+ }catch(e){err('MutationObserver failed',e);}
321
321
 
322
322
  function connectWs(){
323
323
  try{
324
324
  ws=new WebSocket(WS_URL);
325
- ws.onopen=function(){console.log('[Browser Lens] WebSocket connected');fullSync();};
326
- ws.onclose=function(){ws=null;setTimeout(connectWs,5000);};
327
- ws.onerror=function(){try{ws.close();}catch(e){}};
328
- }catch(e){setTimeout(connectWs,5000);}
325
+ ws.onopen=function(){log('WebSocket connected to '+WS_URL);fullSync();};
326
+ ws.onclose=function(){ws=null;log('WebSocket closed, reconnecting in 5s...');setTimeout(connectWs,5000);};
327
+ ws.onerror=function(e){err('WebSocket error',e);try{ws.close();}catch(ex){}};
328
+ }catch(e){err('WebSocket connect failed',e);setTimeout(connectWs,5000);}
329
329
  }
330
330
  connectWs();
331
331
  setInterval(fullSync,15000);
332
- window.addEventListener('beforeunload',function(){fullSync();});
333
- console.log('[Browser Lens] Connected! DOM, CSS, layout, and visual data streaming to IDE.');
332
+ window.addEventListener('beforeunload',function(){try{var payload=JSON.stringify({timestamp:Date.now(),url:location.href});if(navigator.sendBeacon)navigator.sendBeacon(HTTP_URL,payload);}catch(e){}});
333
+ log('Initialized! Connecting to '+WS_URL+'...');
334
334
  })()`;
335
335
  }
336
336
  //# sourceMappingURL=connector-script.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-lens-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "MCP server that connects to your browser for real-time DOM, CSS, layout inspection, screenshot capture, and Figma design comparison — your IDE's AI agent sees exactly what users see",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",