@ejazullah/browser-mcp 0.0.57 → 0.0.58
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.
- package/README.md +2 -0
- package/lib/auth.js +82 -1
- package/lib/browserContextFactory.js +205 -1
- package/lib/browserServerBackend.js +125 -1
- package/lib/config.js +266 -1
- package/lib/context.js +232 -1
- package/lib/databaseLogger.js +264 -1
- package/lib/extension/cdpRelay.js +346 -1
- package/lib/extension/extensionContextFactory.js +56 -1
- package/lib/extension/main.js +26 -1
- package/lib/fileUtils.js +32 -1
- package/lib/httpServer.js +39 -1
- package/lib/index.js +39 -1
- package/lib/javascript.js +49 -1
- package/lib/log.js +21 -1
- package/lib/loop/loop.js +69 -1
- package/lib/loop/loopClaude.js +152 -1
- package/lib/loop/loopOpenAI.js +143 -1
- package/lib/loop/main.js +60 -1
- package/lib/loopTools/context.js +66 -1
- package/lib/loopTools/main.js +49 -1
- package/lib/loopTools/perform.js +32 -1
- package/lib/loopTools/snapshot.js +29 -1
- package/lib/loopTools/tool.js +18 -1
- package/lib/manualPromise.js +111 -1
- package/lib/mcp/inProcessTransport.js +72 -1
- package/lib/mcp/server.js +93 -1
- package/lib/mcp/transport.js +223 -1
- package/lib/mongoDBLogger.js +252 -1
- package/lib/package.js +20 -1
- package/lib/program.js +113 -1
- package/lib/response.js +172 -1
- package/lib/sessionLog.js +156 -1
- package/lib/tab.js +266 -1
- package/lib/tools/cdp.js +169 -1
- package/lib/tools/common.js +55 -1
- package/lib/tools/console.js +33 -1
- package/lib/tools/dialogs.js +47 -1
- package/lib/tools/evaluate.js +53 -1
- package/lib/tools/extraction.js +217 -1
- package/lib/tools/files.js +44 -1
- package/lib/tools/forms.js +180 -1
- package/lib/tools/getext.js +99 -1
- package/lib/tools/install.js +53 -1
- package/lib/tools/interactions.js +191 -1
- package/lib/tools/keyboard.js +86 -1
- package/lib/tools/mouse.js +99 -1
- package/lib/tools/navigate.js +70 -1
- package/lib/tools/network.js +41 -1
- package/lib/tools/pdf.js +40 -1
- package/lib/tools/screenshot.js +75 -1
- package/lib/tools/selectors.js +233 -1
- package/lib/tools/snapshot.js +169 -1
- package/lib/tools/states.js +147 -1
- package/lib/tools/tabs.js +87 -1
- package/lib/tools/tool.js +33 -1
- package/lib/tools/utils.js +74 -1
- package/lib/tools/wait.js +56 -1
- package/lib/tools.js +64 -1
- package/lib/utils.js +26 -1
- package/package.json +2 -2
|
@@ -1 +1,346 @@
|
|
|
1
|
-
function _0x5fc2(_0xea796d,_0x14e7f6){_0xea796d=_0xea796d-0xd5;const _0xdfbfe3=_0x596d();let _0x10e76a=_0xdfbfe3[_0xea796d];if(_0x5fc2['nVTtDa']===undefined){var _0x39cd3f=function(_0x3c5a63){const _0x39a4e1='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x11510b='',_0x4e6218='',_0x1e36b7=_0x11510b+_0x39cd3f;for(let _0x14e5c8=0x0,_0x5dccdd,_0x104c1b,_0x1bd0e6=0x0;_0x104c1b=_0x3c5a63['charAt'](_0x1bd0e6++);~_0x104c1b&&(_0x5dccdd=_0x14e5c8%0x4?_0x5dccdd*0x40+_0x104c1b:_0x104c1b,_0x14e5c8++%0x4)?_0x11510b+=_0x1e36b7['charCodeAt'](_0x1bd0e6+0xa)-0xa!==0x0?String['fromCharCode'](0xff&_0x5dccdd>>(-0x2*_0x14e5c8&0x6)):_0x14e5c8:0x0){_0x104c1b=_0x39a4e1['indexOf'](_0x104c1b);}for(let _0xd5ce4f=0x0,_0x51689c=_0x11510b['length'];_0xd5ce4f<_0x51689c;_0xd5ce4f++){_0x4e6218+='%'+('00'+_0x11510b['charCodeAt'](_0xd5ce4f)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x4e6218);};_0x5fc2['CiSiuc']=_0x39cd3f,_0x5fc2['vlXSCL']={},_0x5fc2['nVTtDa']=!![];}const _0x596de7=_0xdfbfe3[0x0],_0x5fc2ee=_0xea796d+_0x596de7,_0x1cb306=_0x5fc2['vlXSCL'][_0x5fc2ee];if(!_0x1cb306){const _0x20e6ab=function(_0x2f546c){this['wLLSAj']=_0x2f546c,this['NHtMGd']=[0x1,0x0,0x0],this['uUUXUs']=function(){return'newState';},this['gcxuQT']='\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*',this['WctUhI']='[\x27|\x22].+[\x27|\x22];?\x20*}';};_0x20e6ab['prototype']['zNKToY']=function(){const _0x12a4d9=new RegExp(this['gcxuQT']+this['WctUhI']),_0x4084e5=_0x12a4d9['test'](this['uUUXUs']['toString']())?--this['NHtMGd'][0x1]:--this['NHtMGd'][0x0];return this['aalQHS'](_0x4084e5);},_0x20e6ab['prototype']['aalQHS']=function(_0x2b6fa8){if(!Boolean(~_0x2b6fa8))return _0x2b6fa8;return this['BFLRrp'](this['wLLSAj']);},_0x20e6ab['prototype']['BFLRrp']=function(_0x55f187){for(let _0x547013=0x0,_0x2a719f=this['NHtMGd']['length'];_0x547013<_0x2a719f;_0x547013++){this['NHtMGd']['push'](Math['round'](Math['random']())),_0x2a719f=this['NHtMGd']['length'];}return _0x55f187(this['NHtMGd'][0x0]);},new _0x20e6ab(_0x5fc2)['zNKToY'](),_0x10e76a=_0x5fc2['CiSiuc'](_0x10e76a),_0x5fc2['vlXSCL'][_0x5fc2ee]=_0x10e76a;}else _0x10e76a=_0x1cb306;return _0x10e76a;}const _0x2b5da1=_0x5fc2;(function(_0x3ae9f2,_0x4be5d0){const _0x322427=_0x5fc2,_0x5c2ff2=_0x3ae9f2();while(!![]){try{const _0x3371b3=parseInt(_0x322427(0x155))/0x1*(-parseInt(_0x322427(0x10c))/0x2)+parseInt(_0x322427(0x193))/0x3+-parseInt(_0x322427(0xd5))/0x4+parseInt(_0x322427(0x16d))/0x5*(parseInt(_0x322427(0x16f))/0x6)+parseInt(_0x322427(0xed))/0x7*(parseInt(_0x322427(0x15b))/0x8)+parseInt(_0x322427(0xd8))/0x9+parseInt(_0x322427(0x129))/0xa*(-parseInt(_0x322427(0x153))/0xb);if(_0x3371b3===_0x4be5d0)break;else _0x5c2ff2['push'](_0x5c2ff2['shift']());}catch(_0x54db56){_0x5c2ff2['push'](_0x5c2ff2['shift']());}}}(_0x596d,0x9c0e2));const _0x39cd3f=(function(){let _0x27bb1f=!![];return function(_0x2ca952,_0x3c2250){const _0x4044c0=_0x27bb1f?function(){const _0x248cd4=_0x5fc2;if(_0x3c2250){const _0x3159a7=_0x3c2250[_0x248cd4(0x103)](_0x2ca952,arguments);return _0x3c2250=null,_0x3159a7;}}:function(){};return _0x27bb1f=![],_0x4044c0;};}()),_0x10e76a=_0x39cd3f(this,function(){const _0x4ef561=_0x5fc2,_0x2f6ffe={};_0x2f6ffe[_0x4ef561(0xff)]=_0x4ef561(0x112);const _0x1c1eb2=_0x2f6ffe;return _0x10e76a['toString']()[_0x4ef561(0x177)]('(((.+)+)+)+$')[_0x4ef561(0x12f)]()[_0x4ef561(0x186)](_0x10e76a)[_0x4ef561(0x177)](_0x1c1eb2[_0x4ef561(0xff)]);});_0x10e76a();import{spawn}from'child_process';import _0x4dc223 from'debug';import{WebSocket,WebSocketServer}from'ws';import{httpAddressToString}from'../httpServer.js';import{logUnhandledError}from'../log.js';import{ManualPromise}from'../manualPromise.js';function _0x596d(){const _0x514f5b=['B25JBg9Zzq','BxbIEhu','CMvZCg9UC2uOAwq9','yxr0ywnOvg9uywi','qw5VDgHLCIbLEhrLBNnPB24Gy29UBMvJDgLVBIbHBhjLywr5igvZDgfIBgLZAgvK','t3fSBMS','whrWEfq','swr2Egi','ChjVzhvJDa','sw52ywXPzcbWyxrO','ywjVCNq','DhLWzq','x29UrxjYB3i','y29Kzq','mJm2ndC1nxfSru5stG','ugXHExDYAwDODcbnq1aGy29UBMvJDgvK','mtjmB2vNAvm','CMvQzwn0','Eu5rtfO','Ahr0CdOVl2XVy2fSAg9ZDa','CMvHC29U','x3DZ','x2HHBMrSzvbHCNnLze1LC3nHz2u','ihrHCMDLDd0','C2vHCMnO','AwDUB3jL','Cgf0Ag5HBwu','x2nSB3nLugXHExDYAwDODenVBM5Ly3rPB24','s0HerLC','tMv3ignVBM5Ly3rPB24GDg8G','AgfZ','BhrbzeC','CMvWBgfJzq','CgfYC2u','q0rqlujYAwrNzs1tzxj2zxiVms4WlJa','ChvZAa','x2nSB3nLrxH0zw5ZAw9Uq29UBMvJDgLVBG','DxnLCKfNzw50','x2HHBMrSzvbSyxL3CMLNAhrdB25Uzwn0Aw9U','y29UC3rYDwn0B3i','x3DZCW','x29Uq2XVC2u','x3DZsg9ZDa','EM1gyxm','uhjVDg9JB2WGzxjYB3i6ia','v2vIu29JA2v0ignSB3nLza','EvHnww8','z0LuEM8','z0XtEvG','CMvHzhLtDgf0zq','rgLru1G','y2HYB21Llwv4DgvUC2LVBJOVl2PHA2zHBgjUyMHNA3bTB2fHA2zMBgHMBgjMCgTHAwXMl2nVBM5Ly3qUAhrTBa','mZq5nZa1nuTnA1bLza','x2HHBMrSzuv4DgvUC2LVBKnVBM5Ly3rPB24','BhbMsve','zM9YD2fYzeneuev2zw50','zxHLy3v0ywjSzvbHDgG','wMvAsLq','icHPzd0','ls11C2vYlwrHDgeTzgLYpq','vw5LEhbLy3rLzcbxzwjtB2nRzxqGC3rHDgu6ia','rxH0zw5ZAw9Uig5VDcbJB25Uzwn0zwq','4OAqiev4DgvUC2LVBJOGDw5LEhbLy3rLzcbYzxnWB25Zzq','B2PYEfi','x2HHBMrSzuneuenVBw1HBMq','zxH0zw5ZAw9Urw5KCg9PBNq','EfL3A0i','BMvRt1e','x2v4DgvUC2LVBLbHDgG','rMPwCgS','y2rWrw5KCg9PBNq','ndG0nJK4mfP1zLDPAW','z0X3sgu','y2XVC2vdB25Uzwn0Aw9UCW','nZy2mJG3mffOA3rora','suvMrw0','DgfYz2v0','vgP5y1y','y2XLyxi','x2HHBMrSzvbSyxL3CMLNAhrnzxnZywDL','zw5ZDxjLrxH0zw5ZAw9Uq29UBMvJDgLVBKzVCK1duenVBNrLEhq','v3nwD0m','x2nKCfbHDgG','B25TzxnZywDL','pgnSB3nPBMCGD3m+ienSB3nPBMCGD2vIC29JA2v0igr1zsb0BYbMywLSzwqGB25TzxnZywDLignHBgXIywnRlIbLDMvUDerHDge9','vgfYz2v0lNnLDef1Dg9bDhrHy2G','v2PPs0W','rvfrEgW','qw5VDgHLCIbdrfaGy2XPzw50igfSCMvHzhKGy29UBMvJDgvK','ugXHExDYAwDODcbxzwjtB2nRzxqGy2XVC2vK','u2vYDMvYihn0B3bWzwq','x2v4DgvUC2LVBKnVBM5Ly3rPB24','x2rPC3bVC2u','ChjVDg9JB2XwzxjZAw9U','x2nVBM5Ly3rcCM93C2vY','oteYmZu5Aezyq0D1','t1nOqxu','ChC6BwnWoNjLBgf5','ugXHExDYAwDODcbxzwjtB2nRzxqGzxjYB3i6','y2f0y2G','vKLnzNq','uMvQzwn0Aw5NihnLy29UzcbqBgf5D3jPz2H0ignVBM5Ly3rPB24','vw5ZDxbWB3j0zwqGy2HHBM5LBdOGiG','x25LEhrtzxnZAw9Uswq','x2nHBgXIywnRCW','veHyvNK','C3rVCa','CgfYyw1Z','DNrPv2i','zMLUzev4zwn1DgfIBgu','x2nVBM5Ly3rLzfrHyKLUzM8','C2v0','EKHLtMK','C2ncAhe','nxW0Fdf8m3WWFdi','rxH0zw5ZAw9UignVBM5Ly3rPB24Gzxn0ywjSAxnOzwq','phDZignSB3nLzd4Gy29Kzt0','yxbWBhK','s3DQC3e','rxbSCvC','zM9YD2fYzeneuenVBw1HBMq','ChCTDgfIlq','x3bSyxL3CMLNAhrdB25Uzwn0Aw9U','x2XHC3rjza','l2v4DgvUC2LVBI8','x29UtwvZC2fNzq','mKzltMzPtG','AMPfuxi','vgfYz2v0lMDLDfrHCMDLDeLUzM8','4OAsifbSyxL3CMLNAhq6','zgv0ywnOzwrgCM9TvgfI','CMvZB2X2zq','kcGOlISPkYKRksSK','C3rYAw5NAwz5','igu9','CMLLzwy','y29UBMvJDgLVBG','B0HNqLe','AezNCg8','zuDet1C','Cxv4EuC','Bwv0Ag9K','y2XVC2u','CgXHExDYAwDODc1JB3jLl2XPyI9Zzxj2zxiVCMvNAxn0CNKVAw5KzxG','l2nKCc8','v2fPDgLUzYbMB3iGAw5JB21PBMCGzxH0zw5ZAw9UignVBM5Ly3rPB24','qNjVD3nLCI5NzxrwzxjZAw9U','z2v0','CMfUzg9Tvvvjra','tKDhq1C','zfDdtMK','zgvSzxrL','D1nzve0','rxjYB3iGAw4GDgHLigv4DgvUC2LVBJO','ms4Z','mtK4odK1me1wvKPXsq','x2v4DgvUC2LVBKnVBM5Ly3rPB25qCM9TAxnL','sg9uzLq','DMfSDwvZ','BwvZC2fNzq','zxjYB3i','Dg9tDhjPBMC','C3bSAxq','u2LTDwXHDgLUzYbHDxrVlwf0DgfJAa','DwDzu3a','x3jLC2v0rxH0zw5ZAw9Uq29UBMvJDgLVBG','ywrKrxzLBNrmAxn0zw5LCG','tMrSwKS','txrly0K','x2HHBMrSzuv4DgvUC2LVBK1LC3nHz2u','ihjLyxnVBJ0','rw5ZDxjPBMCGzxH0zw5ZAw9UignVBM5Ly3rPB24GzM9Yie1ducbJB250zxH0','DxjS','x29Uq29UBMvJDgLVBG','4OAqifbSyxL3CMLNAhq6','vMnmteO','vgfYz2v0lMf0DgfJAgvKvg9uyxjNzxq','C2vUza','u1nwuKC','whvmrMK','rxH0zw5ZAw9UigrPC2nVBM5Ly3rLzdOG','C2vZC2LVBKLK','pgnSB3nPBMCGD3m+ienSB3nPBMCGD2vIC29JA2v0igr1zsb0BYbTywXMB3jTzwqGsLnptI4GzxzLBNreyxrHpq','iIbLEgvJDxrHyMXLig5VDcbMB3vUzc4GtwfRzsbZDxjLigL0igLZigLUC3rHBgXLzcbHDcbHihn0yw5KyxjKigXVy2f0Aw9UlG','ywrKCMvZCW','tMrrB3y','x2zVCNDHCMruB0v4DgvUC2LVBG','ue9jEg0','rxH0zw5ZAw9UifDLyLnVy2TLDcbJBg9Zzwq6','x3nLBMruB1bSyxL3CMLNAhq','C2vHCMnOugfYyw1Z','rxjYB3iGD2HPBguGAgfUzgXPBMCGugXHExDYAwDODcbTzxnZywDLcG','q2HYB21Ll0v4DgvUC2LVBI1cCMLKz2u','phDZigvYCM9YpIbTzxnZywDLpq','CMfJzq','DgfYz2v0sw5MBW','DwTMChy','mZnLBMHkEeu','x3vZzxjeyxrHrgLY','nZC2mdu5suLqs0jm','x2jYB3DZzxjdAgfUBMvS','yMLUza','y2XPzw50','ugXHExDYAwDODcbJBgLLBNqGzgLZy29UBMvJDgvK','AeHnuum','mtzTDwriswq','yNfvzuy','DLzsugy','BwnWuMvSyxLvCMW'];_0x596d=function(){return _0x514f5b;};return _0x596d();}const {registry}=await import(_0x2b5da1(0x11d)),debugLogger=_0x4dc223(_0x2b5da1(0xef));export class CDPRelayServer{[_0x2b5da1(0x189)];[_0x2b5da1(0x156)];[_0x2b5da1(0x154)];[_0x2b5da1(0xe0)];[_0x2b5da1(0x1a3)];[_0x2b5da1(0x187)];[_0x2b5da1(0x108)]=null;[_0x2b5da1(0xe9)]=null;[_0x2b5da1(0xfc)];[_0x2b5da1(0xf5)]=0x1;[_0x2b5da1(0x12a)];constructor(_0x2baa85,_0x542908,_0x186b34){const _0x19853f=_0x2b5da1,_0x445f57={};_0x445f57[_0x19853f(0x136)]=_0x19853f(0x116);const _0xe033c8=_0x445f57;this['_wsHost']=httpAddressToString(_0x2baa85[_0x19853f(0x146)]())[_0x19853f(0x17f)](/^http/,'ws'),this[_0x19853f(0x156)]=_0x542908,this[_0x19853f(0x154)]=_0x186b34;const _0x19a11e=crypto[_0x19853f(0x122)]();this[_0x19853f(0xe0)]=_0x19853f(0x11e)+_0x19a11e,this[_0x19853f(0x1a3)]=_0x19853f(0x10a)+_0x19a11e,this['_resetExtensionConnection']();const _0x540a6e={};_0x540a6e['server']=_0x2baa85,this['_wss']=new WebSocketServer(_0x540a6e),this[_0x19853f(0x187)]['on'](_0xe033c8[_0x19853f(0x136)],this[_0x19853f(0x13b)]['bind'](this));}[_0x2b5da1(0x1a5)](){const _0x10e555=_0x2b5da1;return''+this[_0x10e555(0x189)]+this[_0x10e555(0xe0)];}[_0x2b5da1(0x1a0)](){const _0x374e23=_0x2b5da1;return''+this['_wsHost']+this[_0x374e23(0x1a3)];}async[_0x2b5da1(0xde)](_0x179fb7,_0x511a00){const _0xd2b8c8=_0x2b5da1,_0x4edf06={'xYwkB':_0xd2b8c8(0x100),'ugYSp':function(_0x375618,_0x12f0b7){return _0x375618(_0x12f0b7);},'FAakm':_0xd2b8c8(0x11f),'Kwjsq':function(_0x40f5f0,_0x5b6df5){return _0x40f5f0(_0x5b6df5);},'NGGCW':_0xd2b8c8(0x139)},_0x4b2d6d=_0x4edf06[_0xd2b8c8(0x1a1)][_0xd2b8c8(0x130)]('|');let _0x1cabf6=0x0;while(!![]){switch(_0x4b2d6d[_0x1cabf6++]){case'0':await Promise[_0xd2b8c8(0x150)]([this[_0xd2b8c8(0x12a)],new Promise((_0x54caa0,_0x36f9ef)=>_0x511a00[_0xd2b8c8(0x134)](_0xd2b8c8(0x169),_0x36f9ef))]);continue;case'1':this[_0xd2b8c8(0xec)](_0x179fb7);continue;case'2':_0x4edf06[_0xd2b8c8(0x132)](debugLogger,_0xd2b8c8(0x101));continue;case'3':debugLogger(_0x4edf06['FAakm']);continue;case'4':if(this[_0xd2b8c8(0xe9)])return;continue;case'5':_0x4edf06[_0xd2b8c8(0x104)](debugLogger,_0x4edf06[_0xd2b8c8(0x123)]);continue;}break;}}[_0x2b5da1(0xec)](_0x34e9f0){const _0x1646b3=_0x2b5da1,_0x2dd5a2={'ZHNPs':_0x1646b3(0x192),'zmFas':_0x1646b3(0x15e),'bqUeF':function(_0x467925,_0x2aead8,_0x3846bf,_0x33cf99){return _0x467925(_0x2aead8,_0x3846bf,_0x33cf99);},'FjVpk':_0x1646b3(0x178)},_0x393845=''+this[_0x1646b3(0x189)]+this[_0x1646b3(0x1a3)],_0x108f90=new URL(_0x2dd5a2['ZHNPs']);_0x108f90[_0x1646b3(0x14c)][_0x1646b3(0xfd)](_0x2dd5a2[_0x1646b3(0x18a)],_0x393845),_0x108f90[_0x1646b3(0x14c)][_0x1646b3(0xfd)](_0x1646b3(0x158),JSON['stringify'](_0x34e9f0));const _0x3c1111=_0x108f90[_0x1646b3(0x12f)](),_0x20da82=registry[_0x1646b3(0xfb)](this[_0x1646b3(0x156)]);if(!_0x20da82)throw new Error(_0x1646b3(0xf4)+this[_0x1646b3(0x156)]+'\x22');const _0x3e91c9=_0x20da82[_0x1646b3(0x197)]();if(!_0x3e91c9)throw new Error('\x22'+this[_0x1646b3(0x156)]+_0x1646b3(0x145));const _0x15392e=[];if(this[_0x1646b3(0x154)])_0x15392e[_0x1646b3(0x182)](_0x1646b3(0x19a)+this[_0x1646b3(0x154)]);_0x15392e[_0x1646b3(0x182)](_0x3c1111),_0x2dd5a2[_0x1646b3(0x15c)](spawn,_0x3e91c9,_0x15392e,{'windowsHide':!![],'detached':!![],'shell':![],'stdio':_0x2dd5a2[_0x1646b3(0x1a4)]});}[_0x2b5da1(0xf8)](){const _0x26a490=_0x2b5da1,_0x161af0={};_0x161af0[_0x26a490(0x117)]=_0x26a490(0xe8);const _0x1362d7=_0x161af0;this[_0x26a490(0xd7)](_0x1362d7[_0x26a490(0x117)]),this[_0x26a490(0x187)]['close']();}[_0x2b5da1(0xd7)](_0xf5cabd){const _0x3ea5e6=_0x2b5da1;this[_0x3ea5e6(0x17a)](_0xf5cabd),this[_0x3ea5e6(0x183)](_0xf5cabd);}[_0x2b5da1(0x13b)](_0x22c7c2,_0xe07849){const _0x3aff01=_0x2b5da1,_0x990628={'gLwHe':function(_0x1cc818,_0x47d8a8){return _0x1cc818(_0x47d8a8);},'NdlZK':function(_0x2e3b8f,_0x40a1ee){return _0x2e3b8f===_0x40a1ee;},'nekOQ':function(_0x159897,_0x572b3c){return _0x159897(_0x572b3c);},'Oqlnk':_0x3aff01(0x168)},_0x5655f3=new URL(_0x3aff01(0x172)+_0xe07849[_0x3aff01(0x13a)]);_0x990628[_0x3aff01(0xd6)](debugLogger,_0x3aff01(0x17c)+_0x5655f3['pathname']);if(_0x990628[_0x3aff01(0x135)](_0x5655f3[_0x3aff01(0x179)],this[_0x3aff01(0xe0)]))this[_0x3aff01(0x185)](_0x22c7c2);else _0x990628[_0x3aff01(0x135)](_0x5655f3[_0x3aff01(0x179)],this[_0x3aff01(0x1a3)])?this[_0x3aff01(0x194)](_0x22c7c2):(_0x990628[_0x3aff01(0x1a2)](debugLogger,'Invalid\x20path:\x20'+_0x5655f3[_0x3aff01(0x179)]),_0x22c7c2[_0x3aff01(0x11c)](0xfa4,_0x990628[_0x3aff01(0x164)]));}[_0x2b5da1(0x185)](_0x579db3){const _0x24c01f=_0x2b5da1,_0x3a7480={'ojrxR':function(_0x51041b,_0x1d9ff3){return _0x51041b(_0x1d9ff3);},'quxyG':_0x24c01f(0xe7),'hFgpo':_0x24c01f(0xf0),'TjycV':_0x24c01f(0xf3),'POIxm':_0x24c01f(0xe6),'wSYTM':_0x24c01f(0x12d),'XtpxT':_0x24c01f(0x11c),'KHDFW':_0x24c01f(0x12e),'WsVwC':_0x24c01f(0x16e)};if(this['_playwrightConnection']){_0x3a7480[_0x24c01f(0x19e)](debugLogger,_0x3a7480[_0x24c01f(0xdb)]),_0x579db3[_0x24c01f(0x11c)](0x3e8,_0x3a7480[_0x24c01f(0x149)]);return;}this[_0x24c01f(0x108)]=_0x579db3,_0x579db3['on'](_0x3a7480[_0x24c01f(0x126)],async _0x536553=>{const _0x17c820=_0x24c01f;try{const _0x39908e=JSON[_0x17c820(0x180)](_0x536553[_0x17c820(0x12f)]());await this[_0x17c820(0xdd)](_0x39908e);}catch(_0x453b4c){debugLogger(_0x17c820(0x14d)+_0x536553[_0x17c820(0x12f)]()+'\x0a',_0x453b4c);}}),_0x579db3['on'](_0x3a7480[_0x24c01f(0x165)],()=>{const _0x213ce6=_0x24c01f;if(this[_0x213ce6(0x108)]!==_0x579db3)return;this[_0x213ce6(0x108)]=null,this[_0x213ce6(0x183)](_0x213ce6(0x159)),_0x3a7480[_0x213ce6(0x19e)](debugLogger,_0x3a7480[_0x213ce6(0x11a)]);}),_0x579db3['on'](_0x3a7480[_0x24c01f(0x17b)],_0xa42af2=>{const _0x3c3ebe=_0x24c01f;debugLogger(_0x3a7480[_0x3c3ebe(0x118)],_0xa42af2);}),_0x3a7480['ojrxR'](debugLogger,_0x3a7480[_0x24c01f(0xdf)]);}[_0x2b5da1(0x183)](_0x329e20){const _0x19b2cf=_0x2b5da1;this[_0x19b2cf(0xe9)]?.[_0x19b2cf(0x11c)](_0x329e20),this[_0x19b2cf(0x12a)][_0x19b2cf(0x170)](new Error(_0x329e20)),this[_0x19b2cf(0x133)]();}[_0x2b5da1(0x133)](){const _0xea4124=_0x2b5da1;this[_0xea4124(0xfc)]=undefined,this[_0xea4124(0xe9)]=null,this[_0xea4124(0x12a)]=new ManualPromise(),void this[_0xea4124(0x12a)][_0xea4124(0xf1)](logUnhandledError);}[_0x2b5da1(0x17a)](_0x5408bf){const _0x13f5ae=_0x2b5da1;if(this['_playwrightConnection']?.['readyState']===WebSocket['OPEN'])this[_0x13f5ae(0x108)]['close'](0x3e8,_0x5408bf);this[_0x13f5ae(0x108)]=null;}[_0x2b5da1(0x194)](_0x13fb09){const _0xddc927=_0x2b5da1,_0x2d5f1d={'EplqW':function(_0x38cdf9,_0x19096a,_0x3cda6a,_0x2fbff1){return _0x38cdf9(_0x19096a,_0x3cda6a,_0x2fbff1);},'THXVy':_0xddc927(0x14a),'YJfNx':function(_0x5d443a,_0x282205){return _0x5d443a===_0x282205;},'eGDOW':function(_0x2e1581,_0x56f470){return _0x2e1581!==_0x56f470;},'zHeNi':_0xddc927(0x163)};if(this[_0xddc927(0xe9)]){_0x13fb09[_0xddc927(0x11c)](0x3e8,_0x2d5f1d[_0xddc927(0xfe)]);return;}this[_0xddc927(0xe9)]=new ExtensionConnection(_0x13fb09),this[_0xddc927(0xe9)][_0xddc927(0x15f)]=(_0x2f8a4f,_0x187e6f)=>{const _0x8d5667=_0xddc927;_0x2d5f1d[_0x8d5667(0x105)](debugLogger,_0x2d5f1d[_0x8d5667(0xf7)],_0x187e6f,_0x2d5f1d['YJfNx'](_0x2f8a4f,this[_0x8d5667(0xe9)]));if(_0x2d5f1d[_0x8d5667(0x119)](this[_0x8d5667(0xe9)],_0x2f8a4f))return;this[_0x8d5667(0x133)](),this[_0x8d5667(0x17a)](_0x8d5667(0x142)+_0x187e6f);},this[_0xddc927(0xe9)][_0xddc927(0xe1)]=this[_0xddc927(0x137)][_0xddc927(0x157)](this),this[_0xddc927(0x12a)][_0xddc927(0x111)]();}['_handleExtensionMessage'](_0x1c99e3,_0x48865e){const _0x2d6412=_0x2b5da1,_0x271229={'yXMYo':_0x2d6412(0x196),'lFpJw':_0x2d6412(0x110),'dWCNi':function(_0x57d45c,_0x1e25b9,_0x172204){return _0x57d45c(_0x1e25b9,_0x172204);},'IEfEm':'←\x20Debugger\x20detached\x20from\x20tab:'};switch(_0x1c99e3){case _0x271229[_0x2d6412(0x18d)]:const _0xdf1723=_0x48865e[_0x2d6412(0x143)]||this[_0x2d6412(0xfc)]?.[_0x2d6412(0x143)],_0x5018cc={};_0x5018cc[_0x2d6412(0x143)]=_0xdf1723,_0x5018cc[_0x2d6412(0x11b)]=_0x48865e[_0x2d6412(0x11b)],_0x5018cc[_0x2d6412(0xf9)]=_0x48865e[_0x2d6412(0xf9)],this[_0x2d6412(0x14b)](_0x5018cc);break;case _0x271229['lFpJw']:_0x271229[_0x2d6412(0x124)](debugLogger,_0x271229[_0x2d6412(0xd9)],_0x48865e),this[_0x2d6412(0xfc)]=undefined;break;}}async[_0x2b5da1(0xdd)](_0x461111){const _0x495e23=_0x2b5da1,_0x54ef1b={'NdQov':function(_0x12d4f5,_0x30b1bb,_0xc5fb98){return _0x12d4f5(_0x30b1bb,_0xc5fb98);},'Idvxb':_0x495e23(0x127)};_0x54ef1b[_0x495e23(0x147)](debugLogger,_0x495e23(0x13c),_0x461111[_0x495e23(0x11b)]+_0x495e23(0x199)+_0x461111['id']+')');const {id:_0x326338,sessionId:_0x3674f1,method:_0x30b306,params:_0x4a8005}=_0x461111;try{const _0x3af007=await this[_0x495e23(0x19f)](_0x30b306,_0x4a8005,_0x3674f1),_0x5c9a5b={};_0x5c9a5b['id']=_0x326338,_0x5c9a5b[_0x495e23(0x143)]=_0x3674f1,_0x5c9a5b['result']=_0x3af007,this[_0x495e23(0x14b)](_0x5c9a5b);}catch(_0x37781d){_0x54ef1b[_0x495e23(0x147)](debugLogger,_0x54ef1b[_0x495e23(0x166)],_0x37781d);const _0x1773ce={};_0x1773ce['message']=_0x37781d['message'];const _0x3d8857={};_0x3d8857['id']=_0x326338,_0x3d8857[_0x495e23(0x143)]=_0x3674f1,_0x3d8857[_0x495e23(0x12e)]=_0x1773ce,this[_0x495e23(0x14b)](_0x3d8857);}}async[_0x2b5da1(0x19f)](_0x42a08d,_0x53885b,_0x5caeea){const _0x1fef4d=_0x2b5da1,_0x207f9e={};_0x207f9e[_0x1fef4d(0x195)]=_0x1fef4d(0x120),_0x207f9e[_0x1fef4d(0x18f)]=_0x1fef4d(0x128),_0x207f9e[_0x1fef4d(0x18e)]=_0x1fef4d(0x14e),_0x207f9e[_0x1fef4d(0x141)]=_0x1fef4d(0x181),_0x207f9e[_0x1fef4d(0x198)]=_0x1fef4d(0x162),_0x207f9e['WjiKL']=_0x1fef4d(0x131);const _0x21dced=_0x207f9e;switch(_0x42a08d){case _0x21dced[_0x1fef4d(0x195)]:{const _0x9cde95={};return _0x9cde95[_0x1fef4d(0xeb)]=_0x21dced[_0x1fef4d(0x18f)],_0x9cde95[_0x1fef4d(0x167)]=_0x21dced[_0x1fef4d(0x18e)],_0x9cde95[_0x1fef4d(0x184)]=_0x21dced[_0x1fef4d(0x141)],_0x9cde95;}case'Browser.setDownloadBehavior':{return{};}case _0x1fef4d(0xe3):{if(_0x5caeea)break;const {targetInfo:_0x1e7537}=await this[_0x1fef4d(0xe9)][_0x1fef4d(0x13f)](_0x21dced[_0x1fef4d(0x198)]);return this[_0x1fef4d(0xfc)]={'targetInfo':_0x1e7537,'sessionId':_0x1fef4d(0x107)+this[_0x1fef4d(0xf5)]++},debugLogger(_0x21dced[_0x1fef4d(0xe4)]),this[_0x1fef4d(0x14b)]({'method':_0x1fef4d(0x13e),'params':{'sessionId':this[_0x1fef4d(0xfc)][_0x1fef4d(0x143)],'targetInfo':{...this[_0x1fef4d(0xfc)][_0x1fef4d(0x151)],'attached':!![]},'waitingForDebugger':![]}}),{};}case _0x1fef4d(0x10e):{return this[_0x1fef4d(0xfc)]?.[_0x1fef4d(0x151)];}}return await this[_0x1fef4d(0x148)](_0x42a08d,_0x53885b,_0x5caeea);}async[_0x2b5da1(0x148)](_0x1159c3,_0x5ef6db,_0x4dc17a){const _0x1ec8fc=_0x2b5da1,_0x44393b={};_0x44393b[_0x1ec8fc(0x15d)]=_0x1ec8fc(0x19c),_0x44393b[_0x1ec8fc(0x15a)]=function(_0x450acf,_0x4df1bf){return _0x450acf===_0x4df1bf;},_0x44393b[_0x1ec8fc(0x17e)]=_0x1ec8fc(0x106);const _0x4e7ec1=_0x44393b;if(!this[_0x1ec8fc(0xe9)])throw new Error(_0x4e7ec1[_0x1ec8fc(0x15d)]);if(_0x4e7ec1[_0x1ec8fc(0x15a)](this[_0x1ec8fc(0xfc)]?.[_0x1ec8fc(0x143)],_0x4dc17a))_0x4dc17a=undefined;const _0x5f26ca={};return _0x5f26ca[_0x1ec8fc(0x143)]=_0x4dc17a,_0x5f26ca[_0x1ec8fc(0x11b)]=_0x1159c3,_0x5f26ca[_0x1ec8fc(0xf9)]=_0x5ef6db,await this['_extensionConnection'][_0x1ec8fc(0x13f)](_0x4e7ec1[_0x1ec8fc(0x17e)],_0x5f26ca);}[_0x2b5da1(0x14b)](_0x55c483){const _0x3f8f17=_0x2b5da1,_0x559540={'HoTfT':function(_0x50c2dd,_0x43cad1,_0x58f306){return _0x50c2dd(_0x43cad1,_0x58f306);},'rieef':_0x3f8f17(0x10f)};_0x559540[_0x3f8f17(0x12b)](debugLogger,_0x559540[_0x3f8f17(0x115)],''+(_0x55c483[_0x3f8f17(0x11b)]??_0x3f8f17(0x161)+_0x55c483['id']+')')),this[_0x3f8f17(0x108)]?.[_0x3f8f17(0x13f)](JSON[_0x3f8f17(0x113)](_0x55c483));}}class ExtensionConnection{[_0x2b5da1(0x174)];['_callbacks']=new Map();[_0x2b5da1(0x109)]=0x0;[_0x2b5da1(0xe1)];[_0x2b5da1(0x15f)];constructor(_0x46b1e4){const _0x454f49=_0x2b5da1,_0x60ff31={};_0x60ff31[_0x454f49(0xfa)]=_0x454f49(0x12d),_0x60ff31[_0x454f49(0x152)]=_0x454f49(0x11c),_0x60ff31[_0x454f49(0xf2)]=_0x454f49(0x12e);const _0x54d429=_0x60ff31;this[_0x454f49(0x174)]=_0x46b1e4,this[_0x454f49(0x174)]['on'](_0x54d429[_0x454f49(0xfa)],this[_0x454f49(0x10b)][_0x454f49(0x157)](this)),this[_0x454f49(0x174)]['on'](_0x54d429[_0x454f49(0x152)],this[_0x454f49(0x188)][_0x454f49(0x157)](this)),this[_0x454f49(0x174)]['on'](_0x54d429[_0x454f49(0xf2)],this[_0x454f49(0x16b)][_0x454f49(0x157)](this));}async[_0x2b5da1(0x13f)](_0x5c8ac7,_0x5529f2,_0x380795){const _0x3895dc=_0x2b5da1,_0x50e58a={};_0x50e58a[_0x3895dc(0xee)]=function(_0x29934e,_0x26dc5e){return _0x29934e!==_0x26dc5e;};const _0x397599=_0x50e58a;if(_0x397599[_0x3895dc(0xee)](this[_0x3895dc(0x174)][_0x3895dc(0x190)],WebSocket['OPEN']))throw new Error(_0x3895dc(0x19b)+this[_0x3895dc(0x174)][_0x3895dc(0x190)]);const _0x1bafb1=++this[_0x3895dc(0x109)],_0x8f39b0={};_0x8f39b0['id']=_0x1bafb1,_0x8f39b0[_0x3895dc(0x11b)]=_0x5c8ac7,_0x8f39b0[_0x3895dc(0xf9)]=_0x5529f2,_0x8f39b0[_0x3895dc(0x143)]=_0x380795,this['_ws'][_0x3895dc(0x13f)](JSON[_0x3895dc(0x113)](_0x8f39b0));const _0x4122e5=new Error(_0x3895dc(0x18b)+_0x5c8ac7);return new Promise((_0x2b49a0,_0xad6c22)=>{const _0x441968=_0x3895dc,_0x51b58e={};_0x51b58e[_0x441968(0x111)]=_0x2b49a0,_0x51b58e[_0x441968(0x170)]=_0xad6c22,_0x51b58e[_0x441968(0x12e)]=_0x4122e5,this[_0x441968(0xf6)][_0x441968(0xfd)](_0x1bafb1,_0x51b58e);});}[_0x2b5da1(0x11c)](_0x4584ac){const _0xbe7420=_0x2b5da1,_0x5db975={};_0x5db975[_0xbe7420(0x171)]='closing\x20extension\x20connection:',_0x5db975[_0xbe7420(0xe5)]=function(_0x1c8ea5,_0x6c08e3){return _0x1c8ea5===_0x6c08e3;};const _0x3c6b90=_0x5db975;debugLogger(_0x3c6b90[_0xbe7420(0x171)],_0x4584ac);if(_0x3c6b90[_0xbe7420(0xe5)](this[_0xbe7420(0x174)][_0xbe7420(0x190)],WebSocket['OPEN']))this[_0xbe7420(0x174)][_0xbe7420(0x11c)](0x3e8,_0x4584ac);}[_0x2b5da1(0x10b)](_0x1d3377){const _0x421e26=_0x2b5da1,_0x39a8bb={'SSVRG':function(_0x239328,_0x31a121){return _0x239328(_0x31a121);}},_0x58bd6e=_0x1d3377[_0x421e26(0x12f)]();let _0x150da3;try{_0x150da3=JSON[_0x421e26(0x180)](_0x58bd6e);}catch(_0x179069){_0x39a8bb[_0x421e26(0x140)](debugLogger,_0x421e26(0x144)+_0x58bd6e+_0x421e26(0x114)+_0x179069?.[_0x421e26(0x12d)]),this['_ws']['close']();return;}try{this['_handleParsedMessage'](_0x150da3);}catch(_0x42c87d){_0x39a8bb[_0x421e26(0x140)](debugLogger,_0x421e26(0xe2)+_0x58bd6e+_0x421e26(0x114)+_0x42c87d?.[_0x421e26(0x12d)]),this[_0x421e26(0x174)][_0x421e26(0x11c)]();}}[_0x2b5da1(0x175)](_0x4e6204){const _0x29bbf7=_0x2b5da1,_0x4f0892={'nlnDW':function(_0x55eea8,_0x5e8066,_0x1f5246){return _0x55eea8(_0x5e8066,_0x1f5246);},'VcLLJ':_0x29bbf7(0x19d)};if(_0x4e6204['id']&&this[_0x29bbf7(0xf6)][_0x29bbf7(0x17d)](_0x4e6204['id'])){const _0x5dc590=this[_0x29bbf7(0xf6)][_0x29bbf7(0x121)](_0x4e6204['id']);this[_0x29bbf7(0xf6)][_0x29bbf7(0x125)](_0x4e6204['id']);if(_0x4e6204[_0x29bbf7(0x12e)]){const _0x5538da=_0x5dc590[_0x29bbf7(0x12e)];_0x5538da[_0x29bbf7(0x12d)]=_0x4e6204[_0x29bbf7(0x12e)],_0x5dc590[_0x29bbf7(0x170)](_0x5538da);}else _0x5dc590[_0x29bbf7(0x111)](_0x4e6204['result']);}else _0x4e6204['id']?_0x4f0892['nlnDW'](debugLogger,_0x4f0892[_0x29bbf7(0x13d)],_0x4e6204):this[_0x29bbf7(0xe1)]?.(_0x4e6204[_0x29bbf7(0x11b)],_0x4e6204[_0x29bbf7(0xf9)]);}[_0x2b5da1(0x188)](_0x5cfce7){const _0x4cd5c9=_0x2b5da1,_0x15cb8a={'DiQSX':function(_0xf241d9,_0x4e7e73){return _0xf241d9(_0x4e7e73);}};_0x15cb8a[_0x4cd5c9(0x191)](debugLogger,_0x4cd5c9(0x102)+_0x5cfce7[_0x4cd5c9(0x16c)]+_0x4cd5c9(0x138)+_0x5cfce7[_0x4cd5c9(0x173)]),this[_0x4cd5c9(0xea)](),this[_0x4cd5c9(0x15f)]?.(this,_0x5cfce7[_0x4cd5c9(0x173)]);}[_0x2b5da1(0x16b)](_0x217db5){const _0x450f49=_0x2b5da1,_0x51605d={'mpbxu':function(_0x15591e,_0x565046){return _0x15591e(_0x565046);}};_0x51605d[_0x450f49(0x160)](debugLogger,_0x450f49(0x14f)+_0x217db5[_0x450f49(0x12d)]+'\x20type='+_0x217db5[_0x450f49(0x16a)]+_0x450f49(0x176)+_0x217db5[_0x450f49(0xda)]),this[_0x450f49(0xea)]();}[_0x2b5da1(0xea)](){const _0x2f8f94=_0x2b5da1,_0x2379db={};_0x2379db[_0x2f8f94(0x10d)]=_0x2f8f94(0x18c);const _0x190fa4=_0x2379db;for(const _0xee19b0 of this[_0x2f8f94(0xf6)][_0x2f8f94(0x12c)]())_0xee19b0[_0x2f8f94(0x170)](new Error(_0x190fa4[_0x2f8f94(0x10d)]));this[_0x2f8f94(0xf6)][_0x2f8f94(0xdc)]();}}
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* WebSocket server that bridges Playwright MCP and Chrome Extension
|
|
18
|
+
*
|
|
19
|
+
* Endpoints:
|
|
20
|
+
* - /cdp/guid - Full CDP interface for Playwright MCP
|
|
21
|
+
* - /extension/guid - Extension connection for chrome.debugger forwarding
|
|
22
|
+
*/
|
|
23
|
+
import { spawn } from 'child_process';
|
|
24
|
+
import debug from 'debug';
|
|
25
|
+
import { WebSocket, WebSocketServer } from 'ws';
|
|
26
|
+
import { httpAddressToString } from '../httpServer.js';
|
|
27
|
+
import { logUnhandledError } from '../log.js';
|
|
28
|
+
import { ManualPromise } from '../manualPromise.js';
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
const { registry } = await import('playwright-core/lib/server/registry/index');
|
|
31
|
+
const debugLogger = debug('pw:mcp:relay');
|
|
32
|
+
export class CDPRelayServer {
|
|
33
|
+
_wsHost;
|
|
34
|
+
_browserChannel;
|
|
35
|
+
_userDataDir;
|
|
36
|
+
_cdpPath;
|
|
37
|
+
_extensionPath;
|
|
38
|
+
_wss;
|
|
39
|
+
_playwrightConnection = null;
|
|
40
|
+
_extensionConnection = null;
|
|
41
|
+
_connectedTabInfo;
|
|
42
|
+
_nextSessionId = 1;
|
|
43
|
+
_extensionConnectionPromise;
|
|
44
|
+
constructor(server, browserChannel, userDataDir) {
|
|
45
|
+
this._wsHost = httpAddressToString(server.address()).replace(/^http/, 'ws');
|
|
46
|
+
this._browserChannel = browserChannel;
|
|
47
|
+
this._userDataDir = userDataDir;
|
|
48
|
+
const uuid = crypto.randomUUID();
|
|
49
|
+
this._cdpPath = `/cdp/${uuid}`;
|
|
50
|
+
this._extensionPath = `/extension/${uuid}`;
|
|
51
|
+
this._resetExtensionConnection();
|
|
52
|
+
this._wss = new WebSocketServer({ server });
|
|
53
|
+
this._wss.on('connection', this._onConnection.bind(this));
|
|
54
|
+
}
|
|
55
|
+
cdpEndpoint() {
|
|
56
|
+
return `${this._wsHost}${this._cdpPath}`;
|
|
57
|
+
}
|
|
58
|
+
extensionEndpoint() {
|
|
59
|
+
return `${this._wsHost}${this._extensionPath}`;
|
|
60
|
+
}
|
|
61
|
+
async ensureExtensionConnectionForMCPContext(clientInfo, abortSignal) {
|
|
62
|
+
debugLogger('Ensuring extension connection for MCP context');
|
|
63
|
+
if (this._extensionConnection)
|
|
64
|
+
return;
|
|
65
|
+
this._connectBrowser(clientInfo);
|
|
66
|
+
debugLogger('Waiting for incoming extension connection');
|
|
67
|
+
await Promise.race([
|
|
68
|
+
this._extensionConnectionPromise,
|
|
69
|
+
new Promise((_, reject) => abortSignal.addEventListener('abort', reject))
|
|
70
|
+
]);
|
|
71
|
+
debugLogger('Extension connection established');
|
|
72
|
+
}
|
|
73
|
+
_connectBrowser(clientInfo) {
|
|
74
|
+
const mcpRelayEndpoint = `${this._wsHost}${this._extensionPath}`;
|
|
75
|
+
// Need to specify "key" in the manifest.json to make the id stable when loading from file.
|
|
76
|
+
const url = new URL('chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/connect.html');
|
|
77
|
+
url.searchParams.set('mcpRelayUrl', mcpRelayEndpoint);
|
|
78
|
+
url.searchParams.set('client', JSON.stringify(clientInfo));
|
|
79
|
+
const href = url.toString();
|
|
80
|
+
const executableInfo = registry.findExecutable(this._browserChannel);
|
|
81
|
+
if (!executableInfo)
|
|
82
|
+
throw new Error(`Unsupported channel: "${this._browserChannel}"`);
|
|
83
|
+
const executablePath = executableInfo.executablePath();
|
|
84
|
+
if (!executablePath)
|
|
85
|
+
throw new Error(`"${this._browserChannel}" executable not found. Make sure it is installed at a standard location.`);
|
|
86
|
+
const args = [];
|
|
87
|
+
if (this._userDataDir)
|
|
88
|
+
args.push(`--user-data-dir=${this._userDataDir}`);
|
|
89
|
+
args.push(href);
|
|
90
|
+
spawn(executablePath, args, {
|
|
91
|
+
windowsHide: true,
|
|
92
|
+
detached: true,
|
|
93
|
+
shell: false,
|
|
94
|
+
stdio: 'ignore',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
stop() {
|
|
98
|
+
this.closeConnections('Server stopped');
|
|
99
|
+
this._wss.close();
|
|
100
|
+
}
|
|
101
|
+
closeConnections(reason) {
|
|
102
|
+
this._closePlaywrightConnection(reason);
|
|
103
|
+
this._closeExtensionConnection(reason);
|
|
104
|
+
}
|
|
105
|
+
_onConnection(ws, request) {
|
|
106
|
+
const url = new URL(`http://localhost${request.url}`);
|
|
107
|
+
debugLogger(`New connection to ${url.pathname}`);
|
|
108
|
+
if (url.pathname === this._cdpPath) {
|
|
109
|
+
this._handlePlaywrightConnection(ws);
|
|
110
|
+
}
|
|
111
|
+
else if (url.pathname === this._extensionPath) {
|
|
112
|
+
this._handleExtensionConnection(ws);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
debugLogger(`Invalid path: ${url.pathname}`);
|
|
116
|
+
ws.close(4004, 'Invalid path');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
_handlePlaywrightConnection(ws) {
|
|
120
|
+
if (this._playwrightConnection) {
|
|
121
|
+
debugLogger('Rejecting second Playwright connection');
|
|
122
|
+
ws.close(1000, 'Another CDP client already connected');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
this._playwrightConnection = ws;
|
|
126
|
+
ws.on('message', async (data) => {
|
|
127
|
+
try {
|
|
128
|
+
const message = JSON.parse(data.toString());
|
|
129
|
+
await this._handlePlaywrightMessage(message);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
debugLogger(`Error while handling Playwright message\n${data.toString()}\n`, error);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
ws.on('close', () => {
|
|
136
|
+
if (this._playwrightConnection !== ws)
|
|
137
|
+
return;
|
|
138
|
+
this._playwrightConnection = null;
|
|
139
|
+
this._closeExtensionConnection('Playwright client disconnected');
|
|
140
|
+
debugLogger('Playwright WebSocket closed');
|
|
141
|
+
});
|
|
142
|
+
ws.on('error', error => {
|
|
143
|
+
debugLogger('Playwright WebSocket error:', error);
|
|
144
|
+
});
|
|
145
|
+
debugLogger('Playwright MCP connected');
|
|
146
|
+
}
|
|
147
|
+
_closeExtensionConnection(reason) {
|
|
148
|
+
this._extensionConnection?.close(reason);
|
|
149
|
+
this._extensionConnectionPromise.reject(new Error(reason));
|
|
150
|
+
this._resetExtensionConnection();
|
|
151
|
+
}
|
|
152
|
+
_resetExtensionConnection() {
|
|
153
|
+
this._connectedTabInfo = undefined;
|
|
154
|
+
this._extensionConnection = null;
|
|
155
|
+
this._extensionConnectionPromise = new ManualPromise();
|
|
156
|
+
void this._extensionConnectionPromise.catch(logUnhandledError);
|
|
157
|
+
}
|
|
158
|
+
_closePlaywrightConnection(reason) {
|
|
159
|
+
if (this._playwrightConnection?.readyState === WebSocket.OPEN)
|
|
160
|
+
this._playwrightConnection.close(1000, reason);
|
|
161
|
+
this._playwrightConnection = null;
|
|
162
|
+
}
|
|
163
|
+
_handleExtensionConnection(ws) {
|
|
164
|
+
if (this._extensionConnection) {
|
|
165
|
+
ws.close(1000, 'Another extension connection already established');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
this._extensionConnection = new ExtensionConnection(ws);
|
|
169
|
+
this._extensionConnection.onclose = (c, reason) => {
|
|
170
|
+
debugLogger('Extension WebSocket closed:', reason, c === this._extensionConnection);
|
|
171
|
+
if (this._extensionConnection !== c)
|
|
172
|
+
return;
|
|
173
|
+
this._resetExtensionConnection();
|
|
174
|
+
this._closePlaywrightConnection(`Extension disconnected: ${reason}`);
|
|
175
|
+
};
|
|
176
|
+
this._extensionConnection.onmessage = this._handleExtensionMessage.bind(this);
|
|
177
|
+
this._extensionConnectionPromise.resolve();
|
|
178
|
+
}
|
|
179
|
+
_handleExtensionMessage(method, params) {
|
|
180
|
+
switch (method) {
|
|
181
|
+
case 'forwardCDPEvent':
|
|
182
|
+
const sessionId = params.sessionId || this._connectedTabInfo?.sessionId;
|
|
183
|
+
this._sendToPlaywright({
|
|
184
|
+
sessionId,
|
|
185
|
+
method: params.method,
|
|
186
|
+
params: params.params
|
|
187
|
+
});
|
|
188
|
+
break;
|
|
189
|
+
case 'detachedFromTab':
|
|
190
|
+
debugLogger('← Debugger detached from tab:', params);
|
|
191
|
+
this._connectedTabInfo = undefined;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async _handlePlaywrightMessage(message) {
|
|
196
|
+
debugLogger('← Playwright:', `${message.method} (id=${message.id})`);
|
|
197
|
+
const { id, sessionId, method, params } = message;
|
|
198
|
+
try {
|
|
199
|
+
const result = await this._handleCDPCommand(method, params, sessionId);
|
|
200
|
+
this._sendToPlaywright({ id, sessionId, result });
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
debugLogger('Error in the extension:', e);
|
|
204
|
+
this._sendToPlaywright({
|
|
205
|
+
id,
|
|
206
|
+
sessionId,
|
|
207
|
+
error: { message: e.message }
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async _handleCDPCommand(method, params, sessionId) {
|
|
212
|
+
switch (method) {
|
|
213
|
+
case 'Browser.getVersion': {
|
|
214
|
+
return {
|
|
215
|
+
protocolVersion: '1.3',
|
|
216
|
+
product: 'Chrome/Extension-Bridge',
|
|
217
|
+
userAgent: 'CDP-Bridge-Server/1.0.0',
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
case 'Browser.setDownloadBehavior': {
|
|
221
|
+
return {};
|
|
222
|
+
}
|
|
223
|
+
case 'Target.setAutoAttach': {
|
|
224
|
+
// Forward child session handling.
|
|
225
|
+
if (sessionId)
|
|
226
|
+
break;
|
|
227
|
+
// Simulate auto-attach behavior with real target info
|
|
228
|
+
const { targetInfo } = await this._extensionConnection.send('attachToTab');
|
|
229
|
+
this._connectedTabInfo = {
|
|
230
|
+
targetInfo,
|
|
231
|
+
sessionId: `pw-tab-${this._nextSessionId++}`,
|
|
232
|
+
};
|
|
233
|
+
debugLogger('Simulating auto-attach');
|
|
234
|
+
this._sendToPlaywright({
|
|
235
|
+
method: 'Target.attachedToTarget',
|
|
236
|
+
params: {
|
|
237
|
+
sessionId: this._connectedTabInfo.sessionId,
|
|
238
|
+
targetInfo: {
|
|
239
|
+
...this._connectedTabInfo.targetInfo,
|
|
240
|
+
attached: true,
|
|
241
|
+
},
|
|
242
|
+
waitingForDebugger: false
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
return {};
|
|
246
|
+
}
|
|
247
|
+
case 'Target.getTargetInfo': {
|
|
248
|
+
return this._connectedTabInfo?.targetInfo;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return await this._forwardToExtension(method, params, sessionId);
|
|
252
|
+
}
|
|
253
|
+
async _forwardToExtension(method, params, sessionId) {
|
|
254
|
+
if (!this._extensionConnection)
|
|
255
|
+
throw new Error('Extension not connected');
|
|
256
|
+
// Top level sessionId is only passed between the relay and the client.
|
|
257
|
+
if (this._connectedTabInfo?.sessionId === sessionId)
|
|
258
|
+
sessionId = undefined;
|
|
259
|
+
return await this._extensionConnection.send('forwardCDPCommand', { sessionId, method, params });
|
|
260
|
+
}
|
|
261
|
+
_sendToPlaywright(message) {
|
|
262
|
+
debugLogger('→ Playwright:', `${message.method ?? `response(id=${message.id})`}`);
|
|
263
|
+
this._playwrightConnection?.send(JSON.stringify(message));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
class ExtensionConnection {
|
|
267
|
+
_ws;
|
|
268
|
+
_callbacks = new Map();
|
|
269
|
+
_lastId = 0;
|
|
270
|
+
onmessage;
|
|
271
|
+
onclose;
|
|
272
|
+
constructor(ws) {
|
|
273
|
+
this._ws = ws;
|
|
274
|
+
this._ws.on('message', this._onMessage.bind(this));
|
|
275
|
+
this._ws.on('close', this._onClose.bind(this));
|
|
276
|
+
this._ws.on('error', this._onError.bind(this));
|
|
277
|
+
}
|
|
278
|
+
async send(method, params, sessionId) {
|
|
279
|
+
if (this._ws.readyState !== WebSocket.OPEN)
|
|
280
|
+
throw new Error(`Unexpected WebSocket state: ${this._ws.readyState}`);
|
|
281
|
+
const id = ++this._lastId;
|
|
282
|
+
this._ws.send(JSON.stringify({ id, method, params, sessionId }));
|
|
283
|
+
const error = new Error(`Protocol error: ${method}`);
|
|
284
|
+
return new Promise((resolve, reject) => {
|
|
285
|
+
this._callbacks.set(id, { resolve, reject, error });
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
close(message) {
|
|
289
|
+
debugLogger('closing extension connection:', message);
|
|
290
|
+
if (this._ws.readyState === WebSocket.OPEN)
|
|
291
|
+
this._ws.close(1000, message);
|
|
292
|
+
}
|
|
293
|
+
_onMessage(event) {
|
|
294
|
+
const eventData = event.toString();
|
|
295
|
+
let parsedJson;
|
|
296
|
+
try {
|
|
297
|
+
parsedJson = JSON.parse(eventData);
|
|
298
|
+
}
|
|
299
|
+
catch (e) {
|
|
300
|
+
debugLogger(`<closing ws> Closing websocket due to malformed JSON. eventData=${eventData} e=${e?.message}`);
|
|
301
|
+
this._ws.close();
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
this._handleParsedMessage(parsedJson);
|
|
306
|
+
}
|
|
307
|
+
catch (e) {
|
|
308
|
+
debugLogger(`<closing ws> Closing websocket due to failed onmessage callback. eventData=${eventData} e=${e?.message}`);
|
|
309
|
+
this._ws.close();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
_handleParsedMessage(object) {
|
|
313
|
+
if (object.id && this._callbacks.has(object.id)) {
|
|
314
|
+
const callback = this._callbacks.get(object.id);
|
|
315
|
+
this._callbacks.delete(object.id);
|
|
316
|
+
if (object.error) {
|
|
317
|
+
const error = callback.error;
|
|
318
|
+
error.message = object.error;
|
|
319
|
+
callback.reject(error);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
callback.resolve(object.result);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
else if (object.id) {
|
|
326
|
+
debugLogger('← Extension: unexpected response', object);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
this.onmessage?.(object.method, object.params);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
_onClose(event) {
|
|
333
|
+
debugLogger(`<ws closed> code=${event.code} reason=${event.reason}`);
|
|
334
|
+
this._dispose();
|
|
335
|
+
this.onclose?.(this, event.reason);
|
|
336
|
+
}
|
|
337
|
+
_onError(event) {
|
|
338
|
+
debugLogger(`<ws error> message=${event.message} type=${event.type} target=${event.target}`);
|
|
339
|
+
this._dispose();
|
|
340
|
+
}
|
|
341
|
+
_dispose() {
|
|
342
|
+
for (const callback of this._callbacks.values())
|
|
343
|
+
callback.reject(new Error('WebSocket closed'));
|
|
344
|
+
this._callbacks.clear();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -1 +1,56 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import debug from 'debug';
|
|
17
|
+
import * as playwright from 'playwright';
|
|
18
|
+
import { startHttpServer } from '../httpServer.js';
|
|
19
|
+
import { CDPRelayServer } from './cdpRelay.js';
|
|
20
|
+
const debugLogger = debug('pw:mcp:relay');
|
|
21
|
+
export class ExtensionContextFactory {
|
|
22
|
+
name = 'extension';
|
|
23
|
+
description = 'Connect to a browser using the Playwright MCP extension';
|
|
24
|
+
_browserChannel;
|
|
25
|
+
_userDataDir;
|
|
26
|
+
constructor(browserChannel, userDataDir) {
|
|
27
|
+
this._browserChannel = browserChannel;
|
|
28
|
+
this._userDataDir = userDataDir;
|
|
29
|
+
}
|
|
30
|
+
async createContext(clientInfo, abortSignal) {
|
|
31
|
+
const browser = await this._obtainBrowser(clientInfo, abortSignal);
|
|
32
|
+
return {
|
|
33
|
+
browserContext: browser.contexts()[0],
|
|
34
|
+
close: async () => {
|
|
35
|
+
debugLogger('close() called for browser context');
|
|
36
|
+
await browser.close();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async _obtainBrowser(clientInfo, abortSignal) {
|
|
41
|
+
const relay = await this._startRelay(abortSignal);
|
|
42
|
+
await relay.ensureExtensionConnectionForMCPContext(clientInfo, abortSignal);
|
|
43
|
+
return await playwright.chromium.connectOverCDP(relay.cdpEndpoint());
|
|
44
|
+
}
|
|
45
|
+
async _startRelay(abortSignal) {
|
|
46
|
+
const httpServer = await startHttpServer({});
|
|
47
|
+
if (abortSignal.aborted) {
|
|
48
|
+
httpServer.close();
|
|
49
|
+
throw new Error(abortSignal.reason);
|
|
50
|
+
}
|
|
51
|
+
const cdpRelayServer = new CDPRelayServer(httpServer, this._browserChannel, this._userDataDir);
|
|
52
|
+
abortSignal.addEventListener('abort', () => cdpRelayServer.stop());
|
|
53
|
+
debugLogger(`CDP relay server started, extension endpoint: ${cdpRelayServer.extensionEndpoint()}.`);
|
|
54
|
+
return cdpRelayServer;
|
|
55
|
+
}
|
|
56
|
+
}
|
package/lib/extension/main.js
CHANGED
|
@@ -1 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { ExtensionContextFactory } from './extensionContextFactory.js';
|
|
17
|
+
import { BrowserServerBackend } from '../browserServerBackend.js';
|
|
18
|
+
import * as mcpTransport from '../mcp/transport.js';
|
|
19
|
+
export async function runWithExtension(config) {
|
|
20
|
+
const contextFactory = new ExtensionContextFactory(config.browser.launchOptions.channel || 'chrome', config.browser.userDataDir);
|
|
21
|
+
const serverBackendFactory = () => new BrowserServerBackend(config, [contextFactory]);
|
|
22
|
+
await mcpTransport.start(serverBackendFactory, config.server);
|
|
23
|
+
}
|
|
24
|
+
export function createExtensionContextFactory(config) {
|
|
25
|
+
return new ExtensionContextFactory(config.browser.launchOptions.channel || 'chrome', config.browser.userDataDir);
|
|
26
|
+
}
|
package/lib/fileUtils.js
CHANGED
|
@@ -1 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import os from 'node:os';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
export function cacheDir() {
|
|
19
|
+
let cacheDirectory;
|
|
20
|
+
if (process.platform === 'linux')
|
|
21
|
+
cacheDirectory = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
|
|
22
|
+
else if (process.platform === 'darwin')
|
|
23
|
+
cacheDirectory = path.join(os.homedir(), 'Library', 'Caches');
|
|
24
|
+
else if (process.platform === 'win32')
|
|
25
|
+
cacheDirectory = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
26
|
+
else
|
|
27
|
+
throw new Error('Unsupported platform: ' + process.platform);
|
|
28
|
+
return path.join(cacheDirectory, 'ms-playwright');
|
|
29
|
+
}
|
|
30
|
+
export async function userDataDir(browserConfig) {
|
|
31
|
+
return path.join(cacheDir(), 'ms-playwright', `mcp-${browserConfig.launchOptions?.channel ?? browserConfig?.browserName}-profile`);
|
|
32
|
+
}
|
package/lib/httpServer.js
CHANGED
|
@@ -1 +1,39 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import assert from 'assert';
|
|
17
|
+
import http from 'http';
|
|
18
|
+
export async function startHttpServer(config) {
|
|
19
|
+
const { host, port } = config;
|
|
20
|
+
const httpServer = http.createServer();
|
|
21
|
+
await new Promise((resolve, reject) => {
|
|
22
|
+
httpServer.on('error', reject);
|
|
23
|
+
httpServer.listen(port, host, () => {
|
|
24
|
+
resolve();
|
|
25
|
+
httpServer.removeListener('error', reject);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
return httpServer;
|
|
29
|
+
}
|
|
30
|
+
export function httpAddressToString(address) {
|
|
31
|
+
assert(address, 'Could not bind server socket');
|
|
32
|
+
if (typeof address === 'string')
|
|
33
|
+
return address;
|
|
34
|
+
const resolvedPort = address.port;
|
|
35
|
+
let resolvedHost = address.family === 'IPv4' ? address.address : `[${address.address}]`;
|
|
36
|
+
if (resolvedHost === '0.0.0.0' || resolvedHost === '[::]')
|
|
37
|
+
resolvedHost = 'localhost';
|
|
38
|
+
return `http://${resolvedHost}:${resolvedPort}`;
|
|
39
|
+
}
|