Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@appetize/playwright

Package Overview
Dependencies
Maintainers
4
Versions
51
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@appetize/playwright - npm Package Compare versions

Comparing version 1.0.3-beta.2 to 1.0.3

2

dist/index.umd.js

@@ -18,5 +18,5 @@ var ie=(d,w,b)=>{if(!w.has(d))throw TypeError("Cannot "+b)};var _=(d,w,b)=>(ie(d,w,"read from private field"),b?b.call(d):w.get(d)),j=(d,w,b)=>{if(w.has(d))throw TypeError("Cannot add the same private member more than once");w instanceof WeakSet?w.add(d):w.set(d,b)},B=(d,w,b,M)=>(ie(d,w,"write to private field"),M?M.call(d,b):w.set(d,b),b);(function(d,w){typeof exports=="object"&&typeof module!="undefined"?w(exports,require("events"),require("@playwright/test"),require("fs")):typeof define=="function"&&define.amd?define(["exports","events","@playwright/test","fs"],w):(d=typeof globalThis!="undefined"?globalThis:d||self,w(d.playwright={},d.events,d.test$1,d.fs))})(this,function(d,w,b,M){var A,$;"use strict";function ne(i){return i&&typeof i=="object"&&"default"in i?i:{default:i}}var F=ne(M);async function re(i,{retries:e=3,timeout:t=1e3,predicate:s=()=>!0}){for(let n=1;n<=e;n++)try{return await i()}catch(r){if(n===e||!s(r,n))throw r;await new Promise(o=>setTimeout(o,t))}throw null}function oe(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,i=>{const e=Math.random()*16|0;return(i==="x"?e:e&3|8).toString(16)})}function ae(i){return Object.entries(i).map(([e,t])=>`${e}=${encodeURIComponent(t)}`).join("&")}function x(i){return Array.isArray(i)?i.map(x).filter(e=>e!=null):typeof i=="object"&&i!==null?Object.entries(i).reduce((e,[t,s])=>{const n=x(s);return n!=null&&(e[t]=n),e},{}):i}function J(i,e){if("captureStackTrace"in Error)Error.captureStackTrace(i,e);else{const t=new Error;Object.defineProperty(i,"stack",{configurable:!0,get(){const{stack:s}=t;return Object.defineProperty(this,"stack",{value:s}),s}})}}async function l(i,e){i instanceof f&&J(i,e)}class f extends Error{constructor(e){super(e),this.name="Error",this.isOperational=!0,J(this,this.constructor)}}class ce extends f{constructor(e){super(`No element found for selector

`)}return c});await F.default.promises.writeFile(e,a.join(`
`)),console.log("\u{1F7E2} Finished"),this.currentRecord+=1}}}function $e(i,e){const t=i[i.length-1];if(t)switch(e.type){case"keypress":{(t==null?void 0:t.type)==="keypress"&&!z(e.key)&&!z(t.key)?(i.pop(),i.push({type:"typeText",text:V(t)+V(e)})):(t==null?void 0:t.type)==="typeText"&&!z(e.key)?(i.pop(),i.push({type:"typeText",text:t.text+V(e)})):i.push(e);break}default:i.push(e)}else i.push(e)}function Y(i){var t,s,n,r,o,a;let e="";switch(i.type){case"swipe":case"tap":{const c=i.element;return typeof c=="string"?e=` on element "${c}"`:(t=c==null?void 0:c.attributes)!=null&&t.accessibilityIdentifier?e=`element with accessibilityIdentifier "${(s=c.attributes)==null?void 0:s.accessibilityIdentifier}"`:(n=c==null?void 0:c.attributes)!=null&&n.class?e=`element with class "${(r=c.attributes)==null?void 0:r.class}"`:"position"in i&&((o=i.position)==null?void 0:o.x)&&((a=i.position)==null?void 0:a.y)&&(e=`position ${Math.round(i.position.x*100)}%, ${Math.round(i.position.y*100)}%`),e?`${i.type} on ${e}`:i.type}case"keypress":return`type "${V(i)}"`;case"typeText":return`type "${i.text}"`}}function Q({type:i,value:e}){switch(i){case"deviceInfo":return{type:"deviceInfo",value:e};case"sessionRequested":return{type:"sessionRequested"};case"chromeDevToolsUrl":return{type:"networkInspectorUrl",value:e};case"orientationChanged":return{type:"orientationChanged",value:e}}}const Ce="1.0.3-beta.2";class Fe extends w.EventEmitter{constructor({page:e}){super(),this.ready=!1,this.page=e,this.page.exposeFunction("__appetize_on",t=>{const s=t==="string"?t:t.type;this.emit(s,t.value),this.emit("*",{type:s,value:t.value})})}async init(){this.ready=!1,await this.page.evaluate(async([e])=>new Promise((t,s)=>{const n=setTimeout(()=>{clearInterval(r),s(new D("Timed out after 60000ms waiting for connection to Appetize page"))},6e4),r=setInterval(()=>{const o=new MessageChannel;o.port1.onmessage=()=>{clearInterval(r),clearTimeout(n),t(!1)},window.postMessage({type:"init",appetizeClient:!0,version:e},"*",[o.port2]),window.__appetize_postMessage=async(a,c=!1)=>{const h=new MessageChannel;if(window.postMessage(a,"*",[h.port2]),c)return new Promise((u,p)=>{const y=setTimeout(()=>{p(new D("Timed out after 60000ms while waiting for postMessage response"))},6e4);h.port1.onmessage=g=>{clearTimeout(y),u(g.data)}})}},100)}),[Ce]),await this.page.evaluate(()=>{window.__appetize__video_frames=[],window.__appetize__audio_frames=[],window.addEventListener("message",e=>{var t;if(e.source===window){const s=typeof e.data=="string"?e.data:(t=e.data)==null?void 0:t.type;if(s==="socketEvent")switch(e.data.value.type){case"h264Data":case"frameData":if(window.__appetize__video_frames.push(e.data.value),!window.__appetize_emit_video)return;break;case"audioData":if(window.__appetize__audio_frames.push(e.data.value),!window.__appetize_emit_audio)return;break}else switch(s){case"frameData":case"recordedAction":case"playbackFoundAndSent":case"playbackNotFound":case"debug":case"interceptRequest":case"interceptResponse":case"interceptError":case"uiDump":return}window.__appetize_on(e.data)}})},[]),this.ready=!0}async waitUntilReady(){return H(async()=>{if(!this.ready)throw new D("Timed out after 60000ms while waiting for Appetize window to be ready.")},6e4)}enableVideoEvents(e=!0){return this.page.evaluate(t=>{window.__appetize_emit_video=t},e)}enableAudioEvents(e=!0){return this.page.evaluate(t=>{window.__appetize_emit_audio=t},e)}async postMessage(e,t=!1){return await this.waitUntilReady(),this.page.evaluate(async([s,n])=>window.__appetize_postMessage(s,n),[e,t])}async getVideoFrames(){return this.page.evaluate(()=>window.__appetize__video_frames)}async getAudioFrames(){return this.page.evaluate(()=>window.__appetize__audio_frames)}async resetVideoFrames(){return this.page.evaluate(()=>{window.__appetize__video_frames=[]})}async resetAudioFrames(){return this.page.evaluate(()=>{window.__appetize__audio_frames=[]})}}class X extends w.EventEmitter{constructor({page:e,type:t,window:s}){super(),this.page=e,this.type=t,this.window=s,this.window.on("*",({type:n,value:r})=>{switch(n){case"socketEvent":r.socket===this.type&&(this.emit(r.type,r.value),this.emit("*",{type:r.type,value:r.value}));break;case"disconnect":this.emit("disconnect"),this.emit("*",{type:"disconnect"});break;case"sessionInfo":case"chromeDevToolsUrl":case"orientationChanged":case"deviceInfo":if(this.type==="appetizer"){const o=Q({type:n,value:r});o&&(this.emit(o.type,o.value),this.emit("*",o))}break;case"sessionRequested":if(this.type==="webserver"){const o=Q({type:n,value:r});o&&(this.emit(o.type,o.value),this.emit("*",o))}break}})}async send(e,t){return this.window.postMessage({type:"emitSocketEvent",value:{type:e,value:t,socket:this.type}})}async disconnect(){await this.send("disconnect")}waitForEvent(e,t){return S(this,e,t)}}class Ie extends Ee{constructor({page:e,config:t,window:s,testInfo:n,...r}){const o=new X({page:e,window:s,type:"appetizer"});super({...r,socket:o,config:t,logger:T}),this.actionLogs=[],this.window=s,this.page=e,this.config=t,this.testInfo=n,this.page.on("load",()=>{this.emit("disconnect")}),t.record&&this.on("disconnect",()=>{this.teardownUi()})}async addActionLog(e){if(e.error){const t=await this.getUI().catch(this.logger.warn);t&&(e.ui=t)}this.actionLogs.push(e)}teardownUi(){return this.window.page.evaluate(()=>{const e=document.querySelector("app-ui");e&&e.remove()})}async rotate(e){try{const[t]=await Promise.all([S(this.window,"orientationChanged"),await this.window.postMessage(e==="left"?"rotateLeft":"rotateRight")]);return this.window.page.waitForTimeout(1e3),t}catch(t){throw l(t,this.rotate),t}}async screenshot(e="buffer"){try{const[t]=await Promise.all([S(this.socket,"screenshot",{timeout:6e4}),this.socket.send("getScreenshot")]),s=new Uint8Array(Object.values(t.data).map(Number));return{data:e==="buffer"?Buffer.from(s):t.data,mimeType:t.mimeType}}catch(t){throw l(t,this.screenshot),t}}async record(){try{if(!this.config.record)throw new f("Recording is not enabled, please enable it by setting `record: true` in session config");if(!this.testInfo)throw new f("session.record() requires using `session` from the test() arguments");return new Pe({session:this,testInfo:this.testInfo}).record()}catch(e){throw l(e,this.record),e}}async waitForEvent(e,t){try{return await S(this,e,t)}catch(s){throw l(s,this.waitForEvent),s}}async waitForTimeout(e){try{return await I(e)}catch(t){throw l(t,this.waitForTimeout),t}}async waitForElement(e,t){try{const s=await this.findElements(e,t);if(s.length){if((t==null?void 0:t.matches)&&s.length!==t.matches)throw new Error(`Expected ${t.matches} elements, found ${s.length}`);return s}else throw new Error(`Element not found:
`)),console.log("\u{1F7E2} Finished"),this.currentRecord+=1}}}function $e(i,e){const t=i[i.length-1];if(t)switch(e.type){case"keypress":{(t==null?void 0:t.type)==="keypress"&&!z(e.key)&&!z(t.key)?(i.pop(),i.push({type:"typeText",text:V(t)+V(e)})):(t==null?void 0:t.type)==="typeText"&&!z(e.key)?(i.pop(),i.push({type:"typeText",text:t.text+V(e)})):i.push(e);break}default:i.push(e)}else i.push(e)}function Y(i){var t,s,n,r,o,a;let e="";switch(i.type){case"swipe":case"tap":{const c=i.element;return typeof c=="string"?e=` on element "${c}"`:(t=c==null?void 0:c.attributes)!=null&&t.accessibilityIdentifier?e=`element with accessibilityIdentifier "${(s=c.attributes)==null?void 0:s.accessibilityIdentifier}"`:(n=c==null?void 0:c.attributes)!=null&&n.class?e=`element with class "${(r=c.attributes)==null?void 0:r.class}"`:"position"in i&&((o=i.position)==null?void 0:o.x)&&((a=i.position)==null?void 0:a.y)&&(e=`position ${Math.round(i.position.x*100)}%, ${Math.round(i.position.y*100)}%`),e?`${i.type} on ${e}`:i.type}case"keypress":return`type "${V(i)}"`;case"typeText":return`type "${i.text}"`}}function Q({type:i,value:e}){switch(i){case"deviceInfo":return{type:"deviceInfo",value:e};case"sessionRequested":return{type:"sessionRequested"};case"chromeDevToolsUrl":return{type:"networkInspectorUrl",value:e};case"orientationChanged":return{type:"orientationChanged",value:e}}}const Ce="1.0.3";class Fe extends w.EventEmitter{constructor({page:e}){super(),this.ready=!1,this.page=e,this.page.exposeFunction("__appetize_on",t=>{const s=t==="string"?t:t.type;this.emit(s,t.value),this.emit("*",{type:s,value:t.value})})}async init(){this.ready=!1,await this.page.evaluate(async([e])=>new Promise((t,s)=>{const n=setTimeout(()=>{clearInterval(r),s(new D("Timed out after 60000ms waiting for connection to Appetize page"))},6e4),r=setInterval(()=>{const o=new MessageChannel;o.port1.onmessage=()=>{clearInterval(r),clearTimeout(n),t(!1)},window.postMessage({type:"init",appetizeClient:!0,version:e},"*",[o.port2]),window.__appetize_postMessage=async(a,c=!1)=>{const h=new MessageChannel;if(window.postMessage(a,"*",[h.port2]),c)return new Promise((u,p)=>{const y=setTimeout(()=>{p(new D("Timed out after 60000ms while waiting for postMessage response"))},6e4);h.port1.onmessage=g=>{clearTimeout(y),u(g.data)}})}},100)}),[Ce]),await this.page.evaluate(()=>{window.__appetize__video_frames=[],window.__appetize__audio_frames=[],window.addEventListener("message",e=>{var t;if(e.source===window){const s=typeof e.data=="string"?e.data:(t=e.data)==null?void 0:t.type;if(s==="socketEvent")switch(e.data.value.type){case"h264Data":case"frameData":if(window.__appetize__video_frames.push(e.data.value),!window.__appetize_emit_video)return;break;case"audioData":if(window.__appetize__audio_frames.push(e.data.value),!window.__appetize_emit_audio)return;break}else switch(s){case"frameData":case"recordedAction":case"playbackFoundAndSent":case"playbackNotFound":case"debug":case"interceptRequest":case"interceptResponse":case"interceptError":case"uiDump":return}window.__appetize_on(e.data)}})},[]),this.ready=!0}async waitUntilReady(){return H(async()=>{if(!this.ready)throw new D("Timed out after 60000ms while waiting for Appetize window to be ready.")},6e4)}enableVideoEvents(e=!0){return this.page.evaluate(t=>{window.__appetize_emit_video=t},e)}enableAudioEvents(e=!0){return this.page.evaluate(t=>{window.__appetize_emit_audio=t},e)}async postMessage(e,t=!1){return await this.waitUntilReady(),this.page.evaluate(async([s,n])=>window.__appetize_postMessage(s,n),[e,t])}async getVideoFrames(){return this.page.evaluate(()=>window.__appetize__video_frames)}async getAudioFrames(){return this.page.evaluate(()=>window.__appetize__audio_frames)}async resetVideoFrames(){return this.page.evaluate(()=>{window.__appetize__video_frames=[]})}async resetAudioFrames(){return this.page.evaluate(()=>{window.__appetize__audio_frames=[]})}}class X extends w.EventEmitter{constructor({page:e,type:t,window:s}){super(),this.page=e,this.type=t,this.window=s,this.window.on("*",({type:n,value:r})=>{switch(n){case"socketEvent":r.socket===this.type&&(this.emit(r.type,r.value),this.emit("*",{type:r.type,value:r.value}));break;case"disconnect":this.emit("disconnect"),this.emit("*",{type:"disconnect"});break;case"sessionInfo":case"chromeDevToolsUrl":case"orientationChanged":case"deviceInfo":if(this.type==="appetizer"){const o=Q({type:n,value:r});o&&(this.emit(o.type,o.value),this.emit("*",o))}break;case"sessionRequested":if(this.type==="webserver"){const o=Q({type:n,value:r});o&&(this.emit(o.type,o.value),this.emit("*",o))}break}})}async send(e,t){return this.window.postMessage({type:"emitSocketEvent",value:{type:e,value:t,socket:this.type}})}async disconnect(){await this.send("disconnect")}waitForEvent(e,t){return S(this,e,t)}}class Ie extends Ee{constructor({page:e,config:t,window:s,testInfo:n,...r}){const o=new X({page:e,window:s,type:"appetizer"});super({...r,socket:o,config:t,logger:T}),this.actionLogs=[],this.window=s,this.page=e,this.config=t,this.testInfo=n,this.page.on("load",()=>{this.emit("disconnect")}),t.record&&this.on("disconnect",()=>{this.teardownUi()})}async addActionLog(e){if(e.error){const t=await this.getUI().catch(this.logger.warn);t&&(e.ui=t)}this.actionLogs.push(e)}teardownUi(){return this.window.page.evaluate(()=>{const e=document.querySelector("app-ui");e&&e.remove()})}async rotate(e){try{const[t]=await Promise.all([S(this.window,"orientationChanged"),await this.window.postMessage(e==="left"?"rotateLeft":"rotateRight")]);return this.window.page.waitForTimeout(1e3),t}catch(t){throw l(t,this.rotate),t}}async screenshot(e="buffer"){try{const[t]=await Promise.all([S(this.socket,"screenshot",{timeout:6e4}),this.socket.send("getScreenshot")]),s=new Uint8Array(Object.values(t.data).map(Number));return{data:e==="buffer"?Buffer.from(s):t.data,mimeType:t.mimeType}}catch(t){throw l(t,this.screenshot),t}}async record(){try{if(!this.config.record)throw new f("Recording is not enabled, please enable it by setting `record: true` in session config");if(!this.testInfo)throw new f("session.record() requires using `session` from the test() arguments");return new Pe({session:this,testInfo:this.testInfo}).record()}catch(e){throw l(e,this.record),e}}async waitForEvent(e,t){try{return await S(this,e,t)}catch(s){throw l(s,this.waitForEvent),s}}async waitForTimeout(e){try{return await I(e)}catch(t){throw l(t,this.waitForTimeout),t}}async waitForElement(e,t){try{const s=await this.findElements(e,t);if(s.length){if((t==null?void 0:t.matches)&&s.length!==t.matches)throw new Error(`Expected ${t.matches} elements, found ${s.length}`);return s}else throw new Error(`Element not found:
${JSON.stringify(e)}`)}catch(s){throw l(s,this.waitForElement),s}}on(e,t){return e==="video"&&(q.onVideo||(this.logger.warn("Listening to the `video` event will significantly slow down your tests. It is recommended to use session.getVideoFrames() instead."),q.onVideo=!0),this.window.enableVideoEvents()),e==="audio"&&(q.onAudio||(this.logger.warn("Listening to the `audio` event will significantly slow down your tests. It is recommended to use session.getAudioFrames() instead."),q.onAudio=!0),this.window.enableAudioEvents()),super.on(e,t)}once(e,t){return e==="video"&&this.window.enableVideoEvents(),e==="audio"&&this.window.enableAudioEvents(),super.once(e,t)}async getVideoFrames(){return await this.window.getVideoFrames()}async getAudioFrames(){return await this.window.getAudioFrames()}}const q={onVideo:!1,onAudio:!1},G=new WeakMap;class De extends _e{constructor({page:e}){var r;const t=(r=G.get(e))!=null?r:new Fe({page:e});G.set(e,t),t.init();const s=new X({type:"webserver",page:e,window:t});super({socket:s,window:t,logger:T}),this.window=t,this.page=e;let n=!1;this.on("queue",o=>{n||(n=!0,o.type==="concurrent"?this.logger.log(`Entered ${o.name}. Please wait until a slot becomes available.`):this.logger.log("All devices are currently in use. Please wait until requested device becomes available.")),o.position>0&&(o.type==="concurrent"?this.logger.log(`Position in ${o.name}: ${o.position}`):this.logger.log(`Position in queue: ${o.position}`))}),this.on("session",()=>{n&&(n=!1)})}validateConfig(e){var s;return{codec:((s=this.page.context().browser())==null?void 0:s.browserType().name())==="chromium"?"jpeg":"h264",...e}}createSession(e,t){return this.session=new Ie({config:e,page:this.page,window:this.window,path:t.path,token:t.token,device:this.device,app:this.app}),this.session}}class Re{constructor(e){this.queue=null,this.page=e}async start(e){const{config:t,baseURL:s}=e,{publicKey:n,...r}=t;await this.page.goto(`${s!=null?s:"https://appetize.io"}/embed/${n}?${ae(r)}`),this.client=new De({page:this.page}),this.client.on("queue",o=>{this.queue=o}),await this.client.waitUntilReady(),this.session=await re(()=>this.client.startSession({...t}),{retries:5,timeout:3e4,predicate:(o,a)=>o instanceof Error&&o.message.match(/too many requests/)?(console.warn(`Too many session requests. Retrying in 30 seconds... (attempt #${a})`),!0):!1})}}let P,Z;const K=b.test.extend({_useConfig:[{},{option:!0}],config:[async({_useConfig:i},e,t)=>{await e({...t.project.use.config,...i,autoplay:!1})},{auto:!0}],_autoSnapshotSuffix:[async({},i,e)=>{e.snapshotSuffix="",await i()},{auto:!0}],appetizePage:async({browser:i,config:e},t)=>{if(P&&JSON.stringify(Z)!==JSON.stringify(e)&&(await P.page.close(),await P.page.context().close(),P=null),!P){const s=await i.newContext();if(P=new Re(await s.newPage()),Z=e,!e.publicKey)throw new Error('Appetize public key not set. Make sure you have run test.use({ config: { publicKey: "..." } })')}await t(P)},session:async({appetizePage:i,config:e,baseURL:t,_doSetupAndTeardown:s},n,r)=>{if(i.session||await W.step("Start Appetize session",async()=>{await i.start({config:e,baseURL:t})}),!i.session)throw new Error("Appetize session failed to start");i.session.testInfo=r,await n(i.session)},client:async({appetizePage:i},e)=>{await e(i.client)},_doSetupAndTeardown:[async({appetizePage:i},e,t)=>{var s,n;T.clearLogHistory(),i.session&&(i.session.actionLogs=[],await i.session.window.resetVideoFrames(),await i.session.window.resetAudioFrames()),await e(),await se(i,t),t.status==="timedOut"&&((n=(s=i.queue)==null?void 0:s.position)!=null?n:0)>0&&T.warn("Test timed out while in queue for a device. You may increase your test timeout to account for higher queue times.")},{auto:!0}]}),Me=K.use,ee=K.afterEach;Object.assign(K,{setup(i){return{}.__APPETIZE__SETUP_WARNED||T.warn("test.setup() is deprecated and will be removed in a future release. Use test.use({ config: {...} }) instead"),W.use({config:i})},use(i){const{config:e,...t}=i;return Me({...t,_useConfig:e})},afterEach(i){return ee(async({appetizePage:e},t)=>{e&&await se(e,t)}),ee(i)}});const W=K,te=new WeakMap;async function se(i,e){if(te.get(e.fn))return;te.set(e.fn,!0);const t=i.session,s="fail-output";if(e.status==="failed"||e.status==="timedOut"){if(t){const o=t.actionLogs;await F.default.promises.mkdir(e.outputPath(s));const a=async()=>{try{const h=await t.screenshot("buffer");e.attach("screenshot",{body:h.data,contentType:"image/png"});const u=e.outputPath(s,"screenshot.png");await F.default.promises.writeFile(u,Buffer.from(h.data))}catch{}},c=async()=>{try{const h=o.map(y=>{if("screenshot"in y){const{screenshot:g,...v}=y;return v}return y});if(!h.length)return;const u=JSON.stringify(h,null,2);e.attach("actions",{body:u,contentType:"application/json"});const p=e.outputPath(s,"actions.json");await F.default.promises.writeFile(p,u)}catch{}};await Promise.all([a(),c()])}await(async()=>{if(!!T.logHistory.length)try{const o=`${T.logHistory.map(c=>`[${c.method}] ${JSON.stringify(c.data).slice(2,-2)}`).join(`
`)}`;e.attach("sdk-logs",{body:o,contentType:"application/text"});const a=e.outputPath(s,"sdk-logs.txt");await F.default.promises.writeFile(a,o)}catch{}})()}}Object.defineProperty(d,"expect",{enumerable:!0,get:function(){return b.expect}}),d.test=W,Object.defineProperties(d,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
//# sourceMappingURL=index.umd.js.map
{
"name": "@appetize/playwright",
"version": "1.0.3-beta.2",
"version": "1.0.3",
"description": "Test your mobile apps on Appetize.io with Playwright",

@@ -5,0 +5,0 @@ "files": [

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc