momentic 0.0.23 → 0.0.24
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/bin/cli.js +1 -1
- package/extension/background.js +27 -0
- package/extension/manifest.json +19 -0
- package/package.json +2 -2
package/bin/cli.js
CHANGED
|
@@ -22,7 +22,7 @@ You have already executed the following commands successfully (most recent liste
|
|
|
22
22
|
`).forEach(l=>n.push(` ${l}`))):n.push("PAGE CONTENT CHANGE: <TOO_LONG_TO_DISPLAY/>"):n.push("PAGE CONTENT CHANGE: <NONE/>")}n.push("-".repeat(10))}),n.push(`STARTING URL: ${this.browser.baseURL}`),n.join(`
|
|
23
23
|
`)}getListHistory(){return Us`Here are the commands that you have successfully executed:
|
|
24
24
|
${this.commandHistory.filter(e=>e.type==="AI_ACTION").map(e=>`- ${e.serializedCommand}`).join(`
|
|
25
|
-
`)}`}async executeCommand(e,o,r=!1){let n=this.commandHistory[this.commandHistory.length-1];if(!r&&(!n||n.state!=="PENDING"))throw new x("InternalWebAgentError","Executing command but there is no pending entry in the history");if(this.closed)throw new Error("Attempting to execute command on a closed controller");let s;try{let i=Date.now();s=await this.executePresetStep(e,o);let a=Date.now()-i;this.logger.debug({result:s,duration:a},"Got execution result")}catch(i){throw i instanceof Error?new Re(`Failed to execute command: ${i}`,{cause:i}):new Re("Unexpected throw from executing command",{cause:new Error(`${i}`)})}return s.succeedImmediately&&!r&&(this.pendingInstructions.pop(),this.pendingInstructions.length>0&&(s.succeedImmediately=!1)),s.elementInteracted&&"target"in e&&e.target&&!e.target.elementDescriptor&&(e.target.elementDescriptor=s.elementInteracted.trim()),r||(n.generatedStep=e,n.serializedCommand=Se(e),n.state="DONE"),s}async executeAssertion(e){let o=await this.getBrowserState(),r=await this.browser.url(),n;if(e.useVision)n={goal:e.assertion,url:r,screenshot:await this.browser.screenshot(),browserState:"",history:"",numPrevious:-1,lastCommand:null};else{let i=this.getSerializedHistory(r,o);n={goal:e.assertion,url:r,browserState:o,history:i,lastCommand:this.lastExecutedCommand,numPrevious:this.commandHistory.length}}let s=await this.generator.getAssertionResult(n,e.useVision,e.disableCache);if(s.relevantElements&&Promise.all(s.relevantElements.map(i=>this.browser.highlight({id:i}))),!s.result)throw new x("AssertionFailureError",s.thoughts);return{succeedImmediately:!1,thoughts:s.thoughts,urlAfterCommand:r}}async wrapElementTargetingCommand(e,o,r,n,s=1){if(!e.a11yData&&!e.elementDescriptor)throw new x("ActionFailureError","Cannot target element with no target data or element descriptor");let i=e.a11yData&&Ge(e.a11yData);e.a11yData||(this.logger.debug("No cached locator data for target, prompting AI for fresh location"),s--,e.a11yData=je.parse(await this.locateElement(e.elementDescriptor,o,r)));try{let a=await n(e.a11yData);return i?this.logger.debug({target:e},"Successfully used cached target to perform action"):this.logger.debug({target:e},"Successfully generated and used new a11y target information"),a}catch(a){if(s>0&&e.elementDescriptor)return this.logger.warn({err:a,target:e},"Failed to execute action with cached target, retrying with AI"),e.a11yData=void 0,this.wrapElementTargetingCommand(e,o,r,n,s);if(a instanceof x)throw a;let l=`Failed to find ${e.elementDescriptor?`${e.elementDescriptor}`:"element"}: ${a instanceof Error?a.message:a}`;throw this.logger.error({err:a,target:e},l),new x("ActionFailureError",l,{cause:a})}}async executePresetStep(e,o){try{return await this.executePresetStepHelper(e,o)}catch(r){throw r instanceof x?r:new x("InternalWebAgentError",r instanceof Error?r.message:`${r}`,{cause:r})}}async executePresetStepHelper(e,o){switch(e.type){case"SUCCESS":return e.condition?.assertion.trim()?this.executeAssertion(e.condition):{succeedImmediately:!1,urlAfterCommand:await this.browser.url()};case"AI_ASSERTION":return this.executeAssertion(e);case"NAVIGATE":if(!Dt(e.url)&&!Nt(e.url,this.browser.baseURL))throw new x("ActionFailureError",`Invalid URL: ${e.url}`);await this.browser.navigate({url:e.url});break;case"CAPTCHA":let r=await this.browser.solveCaptcha();r&&(await this.wrapElementTargetingCommand({elementDescriptor:"the captcha image solution input"},e.useVision,o,l=>this.browser.click(l)),await this.browser.type(r,{clearContent:!0,pressKeysSequentially:!1}));break;case"GO_BACK":await this.browser.goBack();break;case"GO_FORWARD":await this.browser.goForward();break;case"SCROLL_DOWN":case"SCROLL_UP":let n;return e.target&&(e.target.elementDescriptor.trim()||e.target.a11yData)&&(n=await this.wrapElementTargetingCommand(e.target,e.useVision,o,l=>this.browser.hover(l))),e.type==="SCROLL_UP"?await this.browser.scrollUp(e.deltaY):await this.browser.scrollDown(e.deltaY),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:n};case"WAIT":await this.browser.wait(e.delay*1e3);break;case"REFRESH":await this.browser.refresh();break;case"CLICK":{let l=await this.browser.url(),c=await this.wrapElementTargetingCommand(e.target,e.useVision,o,u=>this.browser.click(u,{doubleClick:e.doubleClick,rightClick:e.rightClick,force:e.force})),d={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:c};return de(l,d.urlAfterCommand)&&(d.succeedImmediately=!0,d.succeedImmediatelyReason="URL changed"),d}case"SELECT_OPTION":{let l=await this.wrapElementTargetingCommand(e.target,!1,o,c=>this.browser.selectOption(c,e.option));return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:l}}case"TAB":await this.browser.switchToPage(e.url);break;case"COOKIE":if(!e.value)break;await this.browser.setCookie(e.value);break;case"LOCAL_STORAGE":if(!e.value||!e.key)break;await this.browser.setLocalStorage(e.key,e.value);break;case"TYPE":{let l=await this.browser.url(),c=await this.wrapElementTargetingCommand(e.target,e.useVision,o,u=>this.browser.click(u,{force:e.force}));await this.browser.type(e.value,{clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially}),e.pressEnter&&await this.browser.press("Enter");let d={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:c};return de(l,d.urlAfterCommand)&&(d.succeedImmediately=!0,d.succeedImmediatelyReason="URL changed"),d}case"HOVER":{let l=await this.wrapElementTargetingCommand(e.target,e.useVision,o,c=>this.browser.hover(c));return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:l}}case"PRESS":let s=await this.browser.url();await this.browser.press(e.value);let i={urlAfterCommand:await this.browser.url(),succeedImmediately:!1};return de(s,i.urlAfterCommand)&&(i.succeedImmediately=!0,i.succeedImmediatelyReason="URL changed"),i;case"REQUEST":{let l=e.timeout??30,c=null,d=new AbortController,u=Object.fromEntries(Object.entries(e.headers||{}).filter(([b,E])=>b&&E)),m=new URLSearchParams;Object.entries(e.params||{}).filter(([b,E])=>b&&E).forEach(([b,E])=>{m.append(b,E)});let h;if(Dt(e.url)&&(h=e.url),Nt(e.url,this.browser.baseURL)&&(h=new URL(e.url,this.browser.baseURL).toString()),!h)throw new x("ActionFailureError",`Invalid URL: ${e.url}`);let p=async()=>{try{c=await fetch(`${h}?${m.toString()}`,{headers:u,method:e.method,body:e.body,signal:d.signal})}catch(b){this.logger.error({err:b},"Failed to make fetch request")}},w=async()=>{await new Promise(b=>setTimeout(b,l*1e3)),d.abort()};await Promise.race([w(),p()]);let g=c;if(!g)throw new x("ActionFailureError",`Fetch request timed out after ${l} seconds`);if(!g.ok)throw new x("ActionFailureError",`Fetch request failed with status ${g.status}`);let C={};g.headers.forEach((b,E)=>{C[E]=b});let T={status:g.status,headers:C};return g.headers.get("content-type")?.includes("json")?T.json=await g.json():g.headers.get("content-type")?.includes("text")&&(T.text=await g.text()),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),data:T}}default:return(l=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})(e)}return{succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}async getReverseMappedTarget(e,o,r){return(await this.generator.getReverseMappedDescription({browserState:e.browserState,goal:`${o}`},r)).phrase}};async function wr({socket:t,generator:e,logger:o,rootController:r,localApp:n}){let s=o.child({package:"web-agent"}),i=!0;n==="iframe"&&(i=!1);let a=t.handshake.query.testId,l=t.handshake.query.baseUrl;if(!a)throw new Error("Socket connection is missing testID");let c=t.handshake.query.orgId,d=Hs(),u=p=>{t.emit("screenshot",{...p,url:l})},m=r;if(m)m.browser.setActiveFrame(Je);else{let p=await I.init({baseUrl:l,logger:s,takeScreenshots:i,onScreenshot:u});m=new Y({browser:p,generator:e,config:ue,logger:s})}i&&Qt(t,d,o),k.registerSession(m,d);let h=I.USER_AGENT;return t.emit("session",{url:l,userAgent:h,viewport:await m.browser.viewport(),localApp:n}),{sessionId:d,testId:a,orgId:c}}var br=[gr,Zt,Sr,yr];var Ar=t=>{let{logger:e}=t,o=new Bs(t.baseServer,{cors:{origin:"*",methods:["GET","POST"]}});return o.on("connection",async r=>{let n;try{n=await wr({...t,socket:r})}catch(s){e.error({event:"connection",type:"websocket",err:s},"Failed to setup connection"),r.emit("error",{message:`${s}`});return}br.forEach(s=>js(s,{socket:r,metadata:n,...t}))}),o},js=(t,e)=>{let o=t.createHandler(e),r=(...n)=>{e.logger.debug({event:t.event,metadata:e.metadata,args:n},"Websocket event");let s=i=>{e.logger.error({event:t.event,type:"websocket",args:n,err:i instanceof Error?i:new Error(`${i}`)},"Unhandled exception in socket handler"),e.socket.emit("error",{message:`${i}`})};try{let i=o.apply(void 0,n);i&&typeof i.catch=="function"&&i.catch(s)}catch(i){s(i)}};e.socket.on(t.event,r)};import Gs from"fetch-retry";var Vs=Gs(global.fetch),pe="v1",he=class{baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async getElementLocation(e,o){let r=await this.sendRequest(`/${pe}/web-agent/locate-element`,{browserState:e.browserState,goal:e.goal,disableCache:o});return Rt.parse(r)}async getElementLocationWithVision(e,o){let r=await this.sendRequest(`/${pe}/web-agent/visual-locate`,{goal:e.goal,screenshot:e.screenshot?.toString("base64"),hintActivatedScreenshot:e.hintActivatedScreenshot?.toString("base64"),disableCache:o});return Rt.parse(r)}async getAssertionResult(e,o,r){if(o){let s=await this.sendRequest(`/${pe}/web-agent/assertion`,{url:e.url,goal:e.goal,screenshot:e.screenshot?.toString("base64"),disableCache:r,vision:!0});return Tt.parse(s)}let n=await this.sendRequest(`/${pe}/web-agent/assertion`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:r,vision:!1});return Tt.parse(n)}async getProposedCommand(e,o){let r=await this.sendRequest(`/${pe}/web-agent/next-command`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:o});return Ao.parse(r)}async getGranularGoals(e,o){let r=await this.sendRequest(`/${pe}/web-agent/split-goal`,{url:e.url,goal:e.goal,disableCache:o});return Eo.parse(r)}async getReverseMappedDescription(e,o){let r=await this.sendRequest(`/${pe}/web-agent/reverse-mapped-description`,{goal:e.goal,browserState:e.browserState,disableCache:o});return vo.parse(r)}async sendRequest(e,o){let r=await Vs(`${this.baseURL}${e}`,{retries:1,retryDelay:1e3,method:"POST",body:JSON.stringify(o),headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}});if(!r.ok)throw new Error(`Request to ${e} failed with status ${r.status}: ${await r.text()}`);return r.json()}};import{existsSync as Ws,readFileSync as Ks,writeFileSync as $t}from"fs";import Ht from"path";import{parse as qs,stringify as Ys}from"yaml";var _=process.env.MOMENTIC_DIR??ae;function Er(t,e){let o=No(t);for(let[s,i]of Object.entries(o.modules)){let a=Me(s);$t(Ht.join(_,V,`${a}.yaml`),i,"utf-8")}let r=Me(e),n=Ht.join(_,`${r}.yaml`);return $t(n,o.test,"utf-8"),`${r}.yaml`}function vr(t,e){if(!Ws(e))throw new Error(`Test not found at path: ${Ht}`);let o=Ks(e,"utf-8"),n={...qs(o),...t},s=Ys(n);$t(e,s,"utf-8")}import{readFileSync as ct,readdirSync as Cr,writeFileSync as Tr}from"fs";import{join as Bt}from"path";import{v4 as Xs}from"uuid";import{parse as dt,stringify as Rr}from"yaml";var ke=Bt(_,V);function xr(t,e){let o=rt(ke,e).path,r=ct(o,"utf-8"),s={...dt(r),...t},i=Rr(s);Tr(o,i,"utf-8")}function Ir(t,e){let o=Me(t),r=Bt(ke,`${o}.yaml`),n={fileType:"momentic/module",schemaVersion:z,moduleId:Xs(),name:t,steps:e},s=Rr(n);return Tr(r,s,"utf-8"),{type:"RESOLVED_MODULE",moduleId:n.moduleId,name:n.name,steps:n.steps}}function Lr(){let t=[];for(let e in Cr(ke)){if(!e.endsWith(".yaml"))continue;let o=ct(e,"utf-8"),r=dt(o),n={name:r.name,moduleId:r.moduleId,numSteps:r.steps.length};t.push(n)}return t}function Or(){let t=[];for(let e of Cr(ke)){if(!e.endsWith(".yaml"))continue;let o=Bt(ke,e),r=ct(o,"utf-8"),n=dt(r);try{let s=xt.parse(n);t.push(s)}catch(s){y.warn({err:s},"Error parsing module, skipping...")}}return t}function Mr(t){let e=rt(V,t).path,o=ct(e,"utf-8"),r=dt(o);return xt.parse(r)}import{Router as Js}from"express";function j(t){return function(...e){let o=e[e.length-1],r=t(...e);Promise.resolve(r).catch(o)}}var ze=Js();ze.get("/",j((t,e)=>{let r=Or().map(n=>({...n,type:"RESOLVED_MODULE"}));e.status(200).json(r)}));ze.get("/metadata",j((t,e)=>{let o=Lr();e.status(200).json(o)}));ze.post("/",j((t,e)=>{let o;try{o=So.parse(t.body)}catch(n){e.status(400).json({error:`Invalid request body: ${n}`});return}try{De(o.name)}catch(n){e.status(400).json({error:`Invalid module name: ${n}`});return}let r=Ir(o.name,o.steps);e.status(201).json(r)}));ze.get("/:moduleId",j((t,e)=>{if(!t.params.moduleId){e.status(400).json({error:"Missing moduleId in url path."});return}let o=Mr(t.params.moduleId);e.json(o)}));var Dr=ze;import{Router as Qs}from"express";import{existsSync as Zs}from"fs";import{join as jt}from"path";import{v4 as ei}from"uuid";var Ue=Qs();Ue.get("/",j((t,e)=>{let o=Ne(_).filter(r=>r.localOnly);e.status(200).json(o)}));Ue.post("/",j((t,e)=>{let o;try{o=yo.parse(t.body)}catch(a){e.status(400).json({error:`Invalid request body: ${a}`});return}try{De(o.name)}catch(a){e.status(400).json({error:a.message});return}let n={id:ei(),name:o.name,baseUrl:o.baseUrl,schemaVersion:z,advanced:{disableAICaching:!1,availableAsModule:!0},retries:0,steps:[],localOnly:!0},s=Er(n,o.name),i={...n,testPath:s};e.status(201).json(i)}));Ue.get("/:testPath",j(async(t,e)=>{if(!t.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let o=jt(_,t.params.testPath);if(!Zs(o)){e.status(400).json({error:`Test not found at path: ${o}`});return}let r=await nt(o,jt(_,V));e.status(200).json(r)}));Ue.patch("/:testPath",j((t,e)=>{if(!t.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let o;try{o=go.parse(t.body)}catch(s){e.status(400).json({error:`Invalid request body: ${s}`});return}let r=[],n={};for(let s of o.steps){if(s.type!=="RESOLVED_MODULE"){r.push(s);continue}r.push({type:"MODULE",moduleId:s.moduleId}),n[s.moduleId]=s.steps}for(let[s,i]of Object.entries(n))xr({steps:i},s);vr({steps:r},jt(_,t.params.testPath)),e.status(201).json({message:"ok"})}));var Nr=Ue;var mt=process.env.MOMENTIC_SERVER??"https://api.momentic.ai";var ti,_r=t=>{ti=t};var Pr,kr=async t=>{if(Pr)return;let e=await fetch(`${mt}/v1/auth/check`,{headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`}});if(!e.ok)throw new Error(`Error checking API key with server (code ${e.status}): ${await e.text()}`);let o=await e.json(),{orgId:r}=Mo.parse(o);Pr=r};async function Ur(t,e,o,r,n,s){if(!ni(_)||!si(_).isDirectory()){let m=li.isAbsolute(_);throw new Error(`Root folder ${_} does not exist${m?"":` in the current directory ("${process.cwd()}")`}. Please run \`npx momentic init\` if you wish to setup Momentic here for the first time.`)}y.info("Checking API key"),await kr(e);let a=`http://localhost:${o}`;r&&(a=`http://localhost:${r}`);let l=ci(n);await new Promise(m=>{l.listen(o,()=>{y.info(`Server is running at http://localhost:${o}`),m()})});let d=new he({baseURL:mt,apiKey:e});if(t==="web"){await zr("web",l,d),await ai(a);return}let u=await di(a,d,s,()=>{y.info("Browser closed, closing app."),l.close(),process.exit(0)});await zr("iframe",l,d,u)}function ci(t){let e=Gt();e.use(ri()),e.use(oi.json({limit:"50mb"}));let o=Gt.Router();if(o.use("/tests",Nr),o.use("/modules",Dr),e.use("/api",o),e.use((n,s,i)=>{n.path!=="/healthcheck"&&y.debug({url:n.url,path:n.path,query:n.query,method:n.method,body:n.body,headers:n.rawHeaders,client:n.ip},"Received desktop-server request"),s.on("close",()=>{s.statusCode>=400&&y.error({url:n.url,method:n.method,statusCode:s.statusCode},"Request completed in error")}),i()}),e.use((n,s,i,a)=>{y.error({stack:n.stack,msg:n.message,url:s.url,method:s.method},"Unhandled exception leading to 500 on desktop-server"),i.status(500).send("Internal Server Error"),a(n)}),t){let n=Gt.static(t);e.use("/",n),e.use("*",n)}return ii.createServer(e)}async function di(t,e,o,r){let n=await I.init({baseUrl:t,logger:y,browserArgs:{headless:!1,handleSIGTERM:!0},contextArgs:{bypassCSP:!0,viewport:null,deviceScaleFactor:void 0},localMode:!0,localAppUrl:t,extensionPath:o,onClose:r}),s=new Y({browser:n,config:ue,generator:e,logger:y});return _r(s),s}async function zr(t,e,o,r){Ar({baseServer:e,generator:o,logger:y,localApp:t,rootController:r})}import{existsSync as Fi}from"fs";import an,{dirname as $i}from"path";import{fileURLToPath as Hi}from"url";var Fr="0.0.23";var X="v1",Ee=class{baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async getRun(e){let o=await this.sendRequest(`/${X}/runs/${e}`,{method:"GET"});return Lo.parse(o)}async createRun(e){let o=await this.sendRequest(`/${X}/runs`,{method:"POST",body:e});return Io.parse(o)}async updateRun(e,o){await this.sendRequest(`/${X}/runs/${e}`,{method:"PATCH",body:o})}async getTest(e){let o=await this.sendRequest(`/${X}/tests/${e}`,{method:"GET"});return To.parse(o)}async getAllTestIds(){let e=await this.sendRequest(`/${X}/tests`,{method:"GET"});return Ro.parse(e)}async getTestYAMLExport(e){let o=await this.sendRequest(`/${X}/tests/export`,{method:"POST",body:e});return xo.parse(o)}async updateTestWithYAML(e){await this.sendRequest(`/${X}/tests/update`,{method:"POST",body:e})}async queueTests(e){let o=await this.sendRequest(`/${X}/tests/queue`,{method:"POST",body:e});return Co.parse(o)}async uploadScreenshot(e){let o=await this.sendRequest(`/${X}/screenshots`,{method:"POST",body:e});return Oo.parse(o)}async sendRequest(e,o){let r=await fetch(`${this.baseURL}${e}`,{method:o.method,body:o.body?JSON.stringify(o.body):void 0,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}});if(!r.ok)throw new Error(`Request to ${e} failed with status ${r.status}: ${await r.text()}`);return r.status===204?r.text():r.json()}};import{existsSync as hi,mkdirSync as ut,statSync as fi}from"fs";import{homedir as gi}from"os";import{join as Vt}from"path";import ui from"chalk";import pi from"readline/promises";async function ve(t,e){if(process.env.CI)return!0;let o=pi.createInterface({input:process.stdin,output:process.stdout});t=`${t} (y/N) `;let r=e?ui.bold.yellow(t):t,n=await o.question(r);return o.close(),n.toLowerCase()==="y"}var pt=ae,fe=Vt(ae,V),Wt=Vt(ae,fo),yi=Vt(gi(),"momentic","chromium");function Si(t){return hi(t)&&fi(t).isDirectory()}async function Fe(t=!1){Si(pt)||(!t&&!await ve(`A '${ae}' folder was not found in the current directory. Setup the required Momentic folder structure?`)&&(y.error("Setup cancelled"),process.exit(1)),ut(pt),ut(fe),ut(Wt),ut(yi,{recursive:!0}),y.info("Setup complete!"))}import{registry as Kt}from"playwright-core/lib/server";function wi(t){let e=[],o=[];for(let r of t){let n=Kt.findExecutable(r);!n||n.installType==="none"?e.push(r):o.push(n)}return o}async function $r(){let t=wi(["chromium"]);await Kt.installDeps(t,!1),await Kt.install(t,!1)}import{Argument as qt,Option as $e}from"commander";var He=new $e("--api-key <key>","API key for authentication. If not supplied, attempts to read the MOMENTIC_API_KEY env var.").env("MOMENTIC_API_KEY").makeOptionMandatory(!0),ht=new $e("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise.").default("https://api.momentic.ai"),Be=new $e("-y, --yes","Skip confirmation prompts.").env("CI").default(!1),Hr=new $e("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag.").default(!0),Yt=new $e("-a --all","Select all tests in your organization from Momentic Cloud. Cannot be used together with <tests> arguments.").default(!1).preset(!0),Br=new qt("<tests...>",`One or more test paths to pull from Momentic Cloud.
|
|
25
|
+
`)}`}async executeCommand(e,o,r=!1){let n=this.commandHistory[this.commandHistory.length-1];if(!r&&(!n||n.state!=="PENDING"))throw new x("InternalWebAgentError","Executing command but there is no pending entry in the history");if(this.closed)throw new Error("Attempting to execute command on a closed controller");let s;try{let i=Date.now();s=await this.executePresetStep(e,o);let a=Date.now()-i;this.logger.debug({result:s,duration:a},"Got execution result")}catch(i){throw i instanceof Error?new Re(`Failed to execute command: ${i}`,{cause:i}):new Re("Unexpected throw from executing command",{cause:new Error(`${i}`)})}return s.succeedImmediately&&!r&&(this.pendingInstructions.pop(),this.pendingInstructions.length>0&&(s.succeedImmediately=!1)),s.elementInteracted&&"target"in e&&e.target&&!e.target.elementDescriptor&&(e.target.elementDescriptor=s.elementInteracted.trim()),r||(n.generatedStep=e,n.serializedCommand=Se(e),n.state="DONE"),s}async executeAssertion(e){let o=await this.getBrowserState(),r=await this.browser.url(),n;if(e.useVision)n={goal:e.assertion,url:r,screenshot:await this.browser.screenshot(),browserState:"",history:"",numPrevious:-1,lastCommand:null};else{let i=this.getSerializedHistory(r,o);n={goal:e.assertion,url:r,browserState:o,history:i,lastCommand:this.lastExecutedCommand,numPrevious:this.commandHistory.length}}let s=await this.generator.getAssertionResult(n,e.useVision,e.disableCache);if(s.relevantElements&&Promise.all(s.relevantElements.map(i=>this.browser.highlight({id:i}))),!s.result)throw new x("AssertionFailureError",s.thoughts);return{succeedImmediately:!1,thoughts:s.thoughts,urlAfterCommand:r}}async wrapElementTargetingCommand(e,o,r,n,s=1){if(!e.a11yData&&!e.elementDescriptor)throw new x("ActionFailureError","Cannot target element with no target data or element descriptor");let i=e.a11yData&&Ge(e.a11yData);e.a11yData||(this.logger.debug("No cached locator data for target, prompting AI for fresh location"),s--,e.a11yData=je.parse(await this.locateElement(e.elementDescriptor,o,r)));try{let a=await n(e.a11yData);return i?this.logger.debug({target:e},"Successfully used cached target to perform action"):this.logger.debug({target:e},"Successfully generated and used new a11y target information"),a}catch(a){if(s>0&&e.elementDescriptor)return this.logger.warn({err:a,target:e},"Failed to execute action with cached target, retrying with AI"),e.a11yData=void 0,this.wrapElementTargetingCommand(e,o,r,n,s);if(a instanceof x)throw a;let l=`Failed to find ${e.elementDescriptor?`${e.elementDescriptor}`:"element"}: ${a instanceof Error?a.message:a}`;throw this.logger.error({err:a,target:e},l),new x("ActionFailureError",l,{cause:a})}}async executePresetStep(e,o){try{return await this.executePresetStepHelper(e,o)}catch(r){throw r instanceof x?r:new x("InternalWebAgentError",r instanceof Error?r.message:`${r}`,{cause:r})}}async executePresetStepHelper(e,o){switch(e.type){case"SUCCESS":return e.condition?.assertion.trim()?this.executeAssertion(e.condition):{succeedImmediately:!1,urlAfterCommand:await this.browser.url()};case"AI_ASSERTION":return this.executeAssertion(e);case"NAVIGATE":if(!Dt(e.url)&&!Nt(e.url,this.browser.baseURL))throw new x("ActionFailureError",`Invalid URL: ${e.url}`);await this.browser.navigate({url:e.url});break;case"CAPTCHA":let r=await this.browser.solveCaptcha();r&&(await this.wrapElementTargetingCommand({elementDescriptor:"the captcha image solution input"},e.useVision,o,l=>this.browser.click(l)),await this.browser.type(r,{clearContent:!0,pressKeysSequentially:!1}));break;case"GO_BACK":await this.browser.goBack();break;case"GO_FORWARD":await this.browser.goForward();break;case"SCROLL_DOWN":case"SCROLL_UP":let n;return e.target&&(e.target.elementDescriptor.trim()||e.target.a11yData)&&(n=await this.wrapElementTargetingCommand(e.target,e.useVision,o,l=>this.browser.hover(l))),e.type==="SCROLL_UP"?await this.browser.scrollUp(e.deltaY):await this.browser.scrollDown(e.deltaY),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:n};case"WAIT":await this.browser.wait(e.delay*1e3);break;case"REFRESH":await this.browser.refresh();break;case"CLICK":{let l=await this.browser.url(),c=await this.wrapElementTargetingCommand(e.target,e.useVision,o,u=>this.browser.click(u,{doubleClick:e.doubleClick,rightClick:e.rightClick,force:e.force})),d={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:c};return de(l,d.urlAfterCommand)&&(d.succeedImmediately=!0,d.succeedImmediatelyReason="URL changed"),d}case"SELECT_OPTION":{let l=await this.wrapElementTargetingCommand(e.target,!1,o,c=>this.browser.selectOption(c,e.option));return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:l}}case"TAB":await this.browser.switchToPage(e.url);break;case"COOKIE":if(!e.value)break;await this.browser.setCookie(e.value);break;case"LOCAL_STORAGE":if(!e.value||!e.key)break;await this.browser.setLocalStorage(e.key,e.value);break;case"TYPE":{let l=await this.browser.url(),c=await this.wrapElementTargetingCommand(e.target,e.useVision,o,u=>this.browser.click(u,{force:e.force}));await this.browser.type(e.value,{clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially}),e.pressEnter&&await this.browser.press("Enter");let d={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:c};return de(l,d.urlAfterCommand)&&(d.succeedImmediately=!0,d.succeedImmediatelyReason="URL changed"),d}case"HOVER":{let l=await this.wrapElementTargetingCommand(e.target,e.useVision,o,c=>this.browser.hover(c));return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:l}}case"PRESS":let s=await this.browser.url();await this.browser.press(e.value);let i={urlAfterCommand:await this.browser.url(),succeedImmediately:!1};return de(s,i.urlAfterCommand)&&(i.succeedImmediately=!0,i.succeedImmediatelyReason="URL changed"),i;case"REQUEST":{let l=e.timeout??30,c=null,d=new AbortController,u=Object.fromEntries(Object.entries(e.headers||{}).filter(([b,E])=>b&&E)),m=new URLSearchParams;Object.entries(e.params||{}).filter(([b,E])=>b&&E).forEach(([b,E])=>{m.append(b,E)});let h;if(Dt(e.url)&&(h=e.url),Nt(e.url,this.browser.baseURL)&&(h=new URL(e.url,this.browser.baseURL).toString()),!h)throw new x("ActionFailureError",`Invalid URL: ${e.url}`);let p=async()=>{try{c=await fetch(`${h}?${m.toString()}`,{headers:u,method:e.method,body:e.body,signal:d.signal})}catch(b){this.logger.error({err:b},"Failed to make fetch request")}},w=async()=>{await new Promise(b=>setTimeout(b,l*1e3)),d.abort()};await Promise.race([w(),p()]);let g=c;if(!g)throw new x("ActionFailureError",`Fetch request timed out after ${l} seconds`);if(!g.ok)throw new x("ActionFailureError",`Fetch request failed with status ${g.status}`);let C={};g.headers.forEach((b,E)=>{C[E]=b});let T={status:g.status,headers:C};return g.headers.get("content-type")?.includes("json")?T.json=await g.json():g.headers.get("content-type")?.includes("text")&&(T.text=await g.text()),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),data:T}}default:return(l=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})(e)}return{succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}async getReverseMappedTarget(e,o,r){return(await this.generator.getReverseMappedDescription({browserState:e.browserState,goal:`${o}`},r)).phrase}};async function wr({socket:t,generator:e,logger:o,rootController:r,localApp:n}){let s=o.child({package:"web-agent"}),i=!0;n==="iframe"&&(i=!1);let a=t.handshake.query.testId,l=t.handshake.query.baseUrl;if(!a)throw new Error("Socket connection is missing testID");let c=t.handshake.query.orgId,d=Hs(),u=p=>{t.emit("screenshot",{...p,url:l})},m=r;if(m)m.browser.setActiveFrame(Je);else{let p=await I.init({baseUrl:l,logger:s,takeScreenshots:i,onScreenshot:u});m=new Y({browser:p,generator:e,config:ue,logger:s})}i&&Qt(t,d,o),k.registerSession(m,d);let h=I.USER_AGENT;return t.emit("session",{url:l,userAgent:h,viewport:await m.browser.viewport(),localApp:n}),{sessionId:d,testId:a,orgId:c}}var br=[gr,Zt,Sr,yr];var Ar=t=>{let{logger:e}=t,o=new Bs(t.baseServer,{cors:{origin:"*",methods:["GET","POST"]}});return o.on("connection",async r=>{let n;try{n=await wr({...t,socket:r})}catch(s){e.error({event:"connection",type:"websocket",err:s},"Failed to setup connection"),r.emit("error",{message:`${s}`});return}br.forEach(s=>js(s,{socket:r,metadata:n,...t}))}),o},js=(t,e)=>{let o=t.createHandler(e),r=(...n)=>{e.logger.debug({event:t.event,metadata:e.metadata,args:n},"Websocket event");let s=i=>{e.logger.error({event:t.event,type:"websocket",args:n,err:i instanceof Error?i:new Error(`${i}`)},"Unhandled exception in socket handler"),e.socket.emit("error",{message:`${i}`})};try{let i=o.apply(void 0,n);i&&typeof i.catch=="function"&&i.catch(s)}catch(i){s(i)}};e.socket.on(t.event,r)};import Gs from"fetch-retry";var Vs=Gs(global.fetch),pe="v1",he=class{baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async getElementLocation(e,o){let r=await this.sendRequest(`/${pe}/web-agent/locate-element`,{browserState:e.browserState,goal:e.goal,disableCache:o});return Rt.parse(r)}async getElementLocationWithVision(e,o){let r=await this.sendRequest(`/${pe}/web-agent/visual-locate`,{goal:e.goal,screenshot:e.screenshot?.toString("base64"),hintActivatedScreenshot:e.hintActivatedScreenshot?.toString("base64"),disableCache:o});return Rt.parse(r)}async getAssertionResult(e,o,r){if(o){let s=await this.sendRequest(`/${pe}/web-agent/assertion`,{url:e.url,goal:e.goal,screenshot:e.screenshot?.toString("base64"),disableCache:r,vision:!0});return Tt.parse(s)}let n=await this.sendRequest(`/${pe}/web-agent/assertion`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:r,vision:!1});return Tt.parse(n)}async getProposedCommand(e,o){let r=await this.sendRequest(`/${pe}/web-agent/next-command`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:o});return Ao.parse(r)}async getGranularGoals(e,o){let r=await this.sendRequest(`/${pe}/web-agent/split-goal`,{url:e.url,goal:e.goal,disableCache:o});return Eo.parse(r)}async getReverseMappedDescription(e,o){let r=await this.sendRequest(`/${pe}/web-agent/reverse-mapped-description`,{goal:e.goal,browserState:e.browserState,disableCache:o});return vo.parse(r)}async sendRequest(e,o){let r=await Vs(`${this.baseURL}${e}`,{retries:1,retryDelay:1e3,method:"POST",body:JSON.stringify(o),headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}});if(!r.ok)throw new Error(`Request to ${e} failed with status ${r.status}: ${await r.text()}`);return r.json()}};import{existsSync as Ws,readFileSync as Ks,writeFileSync as $t}from"fs";import Ht from"path";import{parse as qs,stringify as Ys}from"yaml";var _=process.env.MOMENTIC_DIR??ae;function Er(t,e){let o=No(t);for(let[s,i]of Object.entries(o.modules)){let a=Me(s);$t(Ht.join(_,V,`${a}.yaml`),i,"utf-8")}let r=Me(e),n=Ht.join(_,`${r}.yaml`);return $t(n,o.test,"utf-8"),`${r}.yaml`}function vr(t,e){if(!Ws(e))throw new Error(`Test not found at path: ${Ht}`);let o=Ks(e,"utf-8"),n={...qs(o),...t},s=Ys(n);$t(e,s,"utf-8")}import{readFileSync as ct,readdirSync as Cr,writeFileSync as Tr}from"fs";import{join as Bt}from"path";import{v4 as Xs}from"uuid";import{parse as dt,stringify as Rr}from"yaml";var ke=Bt(_,V);function xr(t,e){let o=rt(ke,e).path,r=ct(o,"utf-8"),s={...dt(r),...t},i=Rr(s);Tr(o,i,"utf-8")}function Ir(t,e){let o=Me(t),r=Bt(ke,`${o}.yaml`),n={fileType:"momentic/module",schemaVersion:z,moduleId:Xs(),name:t,steps:e},s=Rr(n);return Tr(r,s,"utf-8"),{type:"RESOLVED_MODULE",moduleId:n.moduleId,name:n.name,steps:n.steps}}function Lr(){let t=[];for(let e in Cr(ke)){if(!e.endsWith(".yaml"))continue;let o=ct(e,"utf-8"),r=dt(o),n={name:r.name,moduleId:r.moduleId,numSteps:r.steps.length};t.push(n)}return t}function Or(){let t=[];for(let e of Cr(ke)){if(!e.endsWith(".yaml"))continue;let o=Bt(ke,e),r=ct(o,"utf-8"),n=dt(r);try{let s=xt.parse(n);t.push(s)}catch(s){y.warn({err:s},"Error parsing module, skipping...")}}return t}function Mr(t){let e=rt(V,t).path,o=ct(e,"utf-8"),r=dt(o);return xt.parse(r)}import{Router as Js}from"express";function j(t){return function(...e){let o=e[e.length-1],r=t(...e);Promise.resolve(r).catch(o)}}var ze=Js();ze.get("/",j((t,e)=>{let r=Or().map(n=>({...n,type:"RESOLVED_MODULE"}));e.status(200).json(r)}));ze.get("/metadata",j((t,e)=>{let o=Lr();e.status(200).json(o)}));ze.post("/",j((t,e)=>{let o;try{o=So.parse(t.body)}catch(n){e.status(400).json({error:`Invalid request body: ${n}`});return}try{De(o.name)}catch(n){e.status(400).json({error:`Invalid module name: ${n}`});return}let r=Ir(o.name,o.steps);e.status(201).json(r)}));ze.get("/:moduleId",j((t,e)=>{if(!t.params.moduleId){e.status(400).json({error:"Missing moduleId in url path."});return}let o=Mr(t.params.moduleId);e.json(o)}));var Dr=ze;import{Router as Qs}from"express";import{existsSync as Zs}from"fs";import{join as jt}from"path";import{v4 as ei}from"uuid";var Ue=Qs();Ue.get("/",j((t,e)=>{let o=Ne(_).filter(r=>r.localOnly);e.status(200).json(o)}));Ue.post("/",j((t,e)=>{let o;try{o=yo.parse(t.body)}catch(a){e.status(400).json({error:`Invalid request body: ${a}`});return}try{De(o.name)}catch(a){e.status(400).json({error:a.message});return}let n={id:ei(),name:o.name,baseUrl:o.baseUrl,schemaVersion:z,advanced:{disableAICaching:!1,availableAsModule:!0},retries:0,steps:[],localOnly:!0},s=Er(n,o.name),i={...n,testPath:s};e.status(201).json(i)}));Ue.get("/:testPath",j(async(t,e)=>{if(!t.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let o=jt(_,t.params.testPath);if(!Zs(o)){e.status(400).json({error:`Test not found at path: ${o}`});return}let r=await nt(o,jt(_,V));e.status(200).json(r)}));Ue.patch("/:testPath",j((t,e)=>{if(!t.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let o;try{o=go.parse(t.body)}catch(s){e.status(400).json({error:`Invalid request body: ${s}`});return}let r=[],n={};for(let s of o.steps){if(s.type!=="RESOLVED_MODULE"){r.push(s);continue}r.push({type:"MODULE",moduleId:s.moduleId}),n[s.moduleId]=s.steps}for(let[s,i]of Object.entries(n))xr({steps:i},s);vr({steps:r},jt(_,t.params.testPath)),e.status(201).json({message:"ok"})}));var Nr=Ue;var mt=process.env.MOMENTIC_SERVER??"https://api.momentic.ai";var ti,_r=t=>{ti=t};var Pr,kr=async t=>{if(Pr)return;let e=await fetch(`${mt}/v1/auth/check`,{headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`}});if(!e.ok)throw new Error(`Error checking API key with server (code ${e.status}): ${await e.text()}`);let o=await e.json(),{orgId:r}=Mo.parse(o);Pr=r};async function Ur(t,e,o,r,n,s){if(!ni(_)||!si(_).isDirectory()){let m=li.isAbsolute(_);throw new Error(`Root folder ${_} does not exist${m?"":` in the current directory ("${process.cwd()}")`}. Please run \`npx momentic init\` if you wish to setup Momentic here for the first time.`)}y.info("Checking API key"),await kr(e);let a=`http://localhost:${o}`;r&&(a=`http://localhost:${r}`);let l=ci(n);await new Promise(m=>{l.listen(o,()=>{y.info(`Server is running at http://localhost:${o}`),m()})});let d=new he({baseURL:mt,apiKey:e});if(t==="web"){await zr("web",l,d),await ai(a);return}let u=await di(a,d,s,()=>{y.info("Browser closed, closing app."),l.close(),process.exit(0)});await zr("iframe",l,d,u)}function ci(t){let e=Gt();e.use(ri()),e.use(oi.json({limit:"50mb"}));let o=Gt.Router();if(o.use("/tests",Nr),o.use("/modules",Dr),e.use("/api",o),e.use((n,s,i)=>{n.path!=="/healthcheck"&&y.debug({url:n.url,path:n.path,query:n.query,method:n.method,body:n.body,headers:n.rawHeaders,client:n.ip},"Received desktop-server request"),s.on("close",()=>{s.statusCode>=400&&y.error({url:n.url,method:n.method,statusCode:s.statusCode},"Request completed in error")}),i()}),e.use((n,s,i,a)=>{y.error({stack:n.stack,msg:n.message,url:s.url,method:s.method},"Unhandled exception leading to 500 on desktop-server"),i.status(500).send("Internal Server Error"),a(n)}),t){let n=Gt.static(t);e.use("/",n),e.use("*",n)}return ii.createServer(e)}async function di(t,e,o,r){let n=await I.init({baseUrl:t,logger:y,browserArgs:{headless:!1,handleSIGTERM:!0},contextArgs:{bypassCSP:!0,viewport:null,deviceScaleFactor:void 0},localMode:!0,localAppUrl:t,extensionPath:o,onClose:r}),s=new Y({browser:n,config:ue,generator:e,logger:y});return _r(s),s}async function zr(t,e,o,r){Ar({baseServer:e,generator:o,logger:y,localApp:t,rootController:r})}import{existsSync as Fi}from"fs";import an,{dirname as $i}from"path";import{fileURLToPath as Hi}from"url";var Fr="0.0.24";var X="v1",Ee=class{baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async getRun(e){let o=await this.sendRequest(`/${X}/runs/${e}`,{method:"GET"});return Lo.parse(o)}async createRun(e){let o=await this.sendRequest(`/${X}/runs`,{method:"POST",body:e});return Io.parse(o)}async updateRun(e,o){await this.sendRequest(`/${X}/runs/${e}`,{method:"PATCH",body:o})}async getTest(e){let o=await this.sendRequest(`/${X}/tests/${e}`,{method:"GET"});return To.parse(o)}async getAllTestIds(){let e=await this.sendRequest(`/${X}/tests`,{method:"GET"});return Ro.parse(e)}async getTestYAMLExport(e){let o=await this.sendRequest(`/${X}/tests/export`,{method:"POST",body:e});return xo.parse(o)}async updateTestWithYAML(e){await this.sendRequest(`/${X}/tests/update`,{method:"POST",body:e})}async queueTests(e){let o=await this.sendRequest(`/${X}/tests/queue`,{method:"POST",body:e});return Co.parse(o)}async uploadScreenshot(e){let o=await this.sendRequest(`/${X}/screenshots`,{method:"POST",body:e});return Oo.parse(o)}async sendRequest(e,o){let r=await fetch(`${this.baseURL}${e}`,{method:o.method,body:o.body?JSON.stringify(o.body):void 0,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}});if(!r.ok)throw new Error(`Request to ${e} failed with status ${r.status}: ${await r.text()}`);return r.status===204?r.text():r.json()}};import{existsSync as hi,mkdirSync as ut,statSync as fi}from"fs";import{homedir as gi}from"os";import{join as Vt}from"path";import ui from"chalk";import pi from"readline/promises";async function ve(t,e){if(process.env.CI)return!0;let o=pi.createInterface({input:process.stdin,output:process.stdout});t=`${t} (y/N) `;let r=e?ui.bold.yellow(t):t,n=await o.question(r);return o.close(),n.toLowerCase()==="y"}var pt=ae,fe=Vt(ae,V),Wt=Vt(ae,fo),yi=Vt(gi(),"momentic","chromium");function Si(t){return hi(t)&&fi(t).isDirectory()}async function Fe(t=!1){Si(pt)||(!t&&!await ve(`A '${ae}' folder was not found in the current directory. Setup the required Momentic folder structure?`)&&(y.error("Setup cancelled"),process.exit(1)),ut(pt),ut(fe),ut(Wt),ut(yi,{recursive:!0}),y.info("Setup complete!"))}import{registry as Kt}from"playwright-core/lib/server";function wi(t){let e=[],o=[];for(let r of t){let n=Kt.findExecutable(r);!n||n.installType==="none"?e.push(r):o.push(n)}return o}async function $r(){let t=wi(["chromium"]);await Kt.installDeps(t,!1),await Kt.install(t,!1)}import{Argument as qt,Option as $e}from"commander";var He=new $e("--api-key <key>","API key for authentication. If not supplied, attempts to read the MOMENTIC_API_KEY env var.").env("MOMENTIC_API_KEY").makeOptionMandatory(!0),ht=new $e("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise.").default("https://api.momentic.ai"),Be=new $e("-y, --yes","Skip confirmation prompts.").env("CI").default(!1),Hr=new $e("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag.").default(!0),Yt=new $e("-a --all","Select all tests in your organization from Momentic Cloud. Cannot be used together with <tests> arguments.").default(!1).preset(!0),Br=new qt("<tests...>",`One or more test paths to pull from Momentic Cloud.
|
|
26
26
|
|
|
27
27
|
A test path is a lowercased version of your test name where spaces are replaced with underscores: 'npx momentic pull hello-world'.`).argOptional(),jr=new qt("<tests...>",`One or more test identifiers.
|
|
28
28
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
async function updateRules() {
|
|
2
|
+
// First, remove any existing rules
|
|
3
|
+
await chrome.declarativeNetRequest.updateDynamicRules({removeRuleIds: [1]});
|
|
4
|
+
|
|
5
|
+
const headersToFilter = ["x-frame-options", "content-security-policy", "content-security-policy-report-only"]
|
|
6
|
+
|
|
7
|
+
// Define a new rule to remove specific headers
|
|
8
|
+
const rule = {
|
|
9
|
+
id: 1,
|
|
10
|
+
priority: 1,
|
|
11
|
+
action: {
|
|
12
|
+
type: 'modifyHeaders',
|
|
13
|
+
responseHeaders: headersToFilter.map(header =>
|
|
14
|
+
({operation: chrome.declarativeNetRequest.HeaderOperation.REMOVE, header}))
|
|
15
|
+
},
|
|
16
|
+
condition: {
|
|
17
|
+
urlFilter: '|*|',
|
|
18
|
+
resourceTypes: ['sub_frame']
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Add the new rule
|
|
23
|
+
await chrome.declarativeNetRequest.updateDynamicRules({addRules: [rule]});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Call updateRules to initialize the rule setup
|
|
27
|
+
updateRules();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "Header Filter Extension",
|
|
4
|
+
"version": "1.0",
|
|
5
|
+
"permissions": [
|
|
6
|
+
"activeTab",
|
|
7
|
+
"scripting",
|
|
8
|
+
"webNavigation",
|
|
9
|
+
"webRequest",
|
|
10
|
+
"declarativeNetRequest",
|
|
11
|
+
"declarativeNetRequestWithHostAccess"
|
|
12
|
+
],
|
|
13
|
+
"host_permissions": [
|
|
14
|
+
"*://*/*"
|
|
15
|
+
],
|
|
16
|
+
"background": {
|
|
17
|
+
"service_worker": "background.js"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "momentic",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"description": "The Momentic SDK for Node.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"dist/**/*",
|
|
13
13
|
"bin/**/*",
|
|
14
14
|
"static/**/*",
|
|
15
|
-
"
|
|
15
|
+
"extension/**/*"
|
|
16
16
|
],
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"publishConfig": {
|