Socket
Socket
Sign inDemoInstall

@stackflow/plugin-history-sync

Package Overview
Dependencies
10
Maintainers
2
Versions
109
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.19.2 to 0.20.0

511

dist/index.js

@@ -1,2 +0,511 @@

"use strict";var et=Object.create;var x=Object.defineProperty,nt=Object.defineProperties,rt=Object.getOwnPropertyDescriptor,it=Object.getOwnPropertyDescriptors,st=Object.getOwnPropertyNames,M=Object.getOwnPropertySymbols,at=Object.getPrototypeOf,O=Object.prototype.hasOwnProperty,ot=Object.prototype.propertyIsEnumerable;var K=(t,n,r)=>n in t?x(t,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[n]=r,o=(t,n)=>{for(var r in n||(n={}))O.call(n,r)&&K(t,r,n[r]);if(M)for(var r of M(n))ot.call(n,r)&&K(t,r,n[r]);return t},p=(t,n)=>nt(t,it(n));var ct=(t,n)=>{for(var r in n)x(t,r,{get:n[r],enumerable:!0})},D=(t,n,r,e)=>{if(n&&typeof n=="object"||typeof n=="function")for(let i of st(n))!O.call(t,i)&&i!==r&&x(t,i,{get:()=>n[i],enumerable:!(e=rt(n,i))||e.enumerable});return t};var E=(t,n,r)=>(r=t!=null?et(at(t)):{},D(n||!t||!t.__esModule?x(r,"default",{value:t,enumerable:!0}):r,t)),ut=t=>D(x({},"__esModule",{value:!0}),t);var vt={};ct(vt,{historySyncPlugin:()=>ht,makeTemplate:()=>d,normalizeRoute:()=>f,useRoutes:()=>W});module.exports=ut(vt);var P=require("@stackflow/core"),Y=E(require("react"));var F="@stackflow/plugin-history-sync@0.19.1",C=typeof window=="undefined";function N(){return C?null:window.history.state}function T(t){let n=t;return typeof n=="object"&&n!==null&&"_TAG"in n&&typeof n._TAG=="string"&&n._TAG===F?t:null}function L(t){return p(o({},t),{pushedBy:"activityContext"in t.pushedBy?p(o({},t.pushedBy),{activityContext:void 0}):o({},t.pushedBy)})}function pt(t){return p(o({},t),{context:void 0,pushedBy:p(o({},t.pushedBy),{activityContext:void 0}),steps:t.steps.map(L)})}function j(t){return{_TAG:F,activity:pt(t.activity),step:t.step?L(t.step):void 0}}function I({state:t,url:n,useHash:r}){if(C)return;let e=r?`${window.location.pathname}#${n}`:n;window.history.pushState(j(t),"",e)}function B({url:t,state:n,useHash:r}){if(C)return;let e=r?`${window.location.pathname}#${t}`:t;window.history.replaceState(j(n),"",e)}function _(t){return t.length===0?void 0:t[t.length-1]}var q=E(require("url-pattern"));function ft(t){return new URL(t,"file://")}function lt(t){let n={};return t.forEach((r,e)=>{n[e]=r}),n}function dt(t){return t.endsWith("/")?t:`${t}/`}function yt(t){return t.toString().length>0?`?${t}`:t}function d(t){let n=new q.default(`${t}(/)`);return{fill(r){var u;let e=n.stringify(r),i=(u=n.match(e))!=null?u:{},a=o({},r);Object.keys(i).forEach(y=>{delete a[y]});let c=new URLSearchParams(a);return dt(e)+yt(c)},parse(r){let e=ft(r),i=n.match(e.pathname),a=lt(e.searchParams);return i?o(o({},a),i):null}}}function f(t){return typeof t=="string"?[t]:t}var w=E(require("react")),Q=(0,w.createContext)({}),V=t=>w.default.createElement(Q.Provider,{value:t.routes},t.children);function W(){return(0,w.useContext)(Q)}var mt=1e3,J=60*mt,X=typeof window=="undefined";function ht(t){return()=>{let n=0,r=0;return{key:"plugin-history-sync",wrapStack({stack:e}){return Y.default.createElement(V,{routes:t.routes},e.render())},overrideInitialEvents({initialContext:e}){var h,v;let i=T(N());if(i)return[p(o({},i.activity.pushedBy),{name:"Pushed"}),...((h=i.step)==null?void 0:h.pushedBy.name)==="StepPushed"||((v=i.step)==null?void 0:v.pushedBy.name)==="StepReplaced"?[p(o({},i.step.pushedBy),{name:"StepPushed"})]:[]];function a(){var l,s;return((l=e==null?void 0:e.req)==null?void 0:l.path)&&typeof e.req.path=="string"?e.req.path:X?null:t.useHash?(s=window.location.hash.split("#")[1])!=null?s:"/":window.location.pathname+window.location.search}let c=a(),u=Object.keys(t.routes);if(c)for(let l=0;l<u.length;l+=1){let s=u[l],g=f(t.routes[s]);for(let m=0;m<g.length;m+=1){let S=g[m],k=d(S).parse(c);if(!!k){let b=(0,P.id)();return[(0,P.makeEvent)("Pushed",{activityId:b,activityName:s,activityParams:o({},k),eventDate:new Date().getTime()-J,activityContext:{path:c}})]}}}let y=(0,P.id)(),R=t.fallbackActivity({initialContext:e}),H=f(t.routes[R])[0];return[(0,P.makeEvent)("Pushed",{activityId:y,activityName:R,activityParams:{},eventDate:new Date().getTime()-J,activityContext:{path:H}})]},onInit({actions:{getStack:e,dispatchEvent:i,push:a,stepPush:c}}){let u=e().activities[0],y=d(f(t.routes[u.name])[0]);window.getStack=e;let R=_(u.steps);B({url:y.fill(u.params),state:{activity:u,step:R},useHash:t.useHash});let G=H=>{if(r){r-=1;return}let h=T(H.state);if(!h)return;let v=h.activity,l=h.activity.id,s=h.step,{activities:g}=e(),m=g.find(A=>A.isActive);if(!m)return;let S=_(m.steps),U=g.find(A=>A.id===l),k=m.steps.find(A=>A.id===(s==null?void 0:s.id)),$=()=>m.id>l,b=()=>m.id<l,z=()=>m.id===l,Z=()=>z()?!!(!s||S&&S.id>s.id):!1,tt=()=>z()?!!(!S||s&&S.id<s.id):!1;if($()&&(i("Popped",{}),U||(n+=1,a(o({},v.pushedBy)),((s==null?void 0:s.pushedBy.name)==="StepPushed"||(s==null?void 0:s.pushedBy.name)==="StepReplaced")&&(n+=1,c(o({},s.pushedBy))))),Z()&&(!k&&s&&((s==null?void 0:s.pushedBy.name)==="StepPushed"||(s==null?void 0:s.pushedBy.name)==="StepReplaced")&&(n+=1,c(o({},s.pushedBy))),i("StepPopped",{})),b()&&(n+=1,a({activityId:v.id,activityName:v.name,activityParams:v.params})),tt()){if(!s)return;n+=1,c({stepId:s.id,stepParams:s.params})}};X||window.addEventListener("popstate",G)},onPushed({effect:{activity:e}}){if(n){n-=1;return}let i=d(f(t.routes[e.name])[0]);I({url:i.fill(e.params),state:{activity:e},useHash:t.useHash})},onStepPushed({effect:{activity:e,step:i}}){if(n){n-=1;return}let a=d(f(t.routes[e.name])[0]);I({url:a.fill(e.params),state:{activity:e,step:i},useHash:t.useHash})},onReplaced({effect:{activity:e}}){if(!e.isActive)return;let i=d(f(t.routes[e.name])[0]);B({url:i.fill(e.params),state:{activity:e},useHash:t.useHash})},onStepReplaced({effect:{activity:e,step:i}}){if(!e.isActive)return;let a=d(f(t.routes[e.name])[0]);B({url:a.fill(e.params),state:{activity:e,step:i},useHash:t.useHash})},onBeforePush({actionParams:e,actions:{overrideActionParams:i}}){let c=d(f(t.routes[e.activityName])[0]).fill(e.activityParams);i(p(o({},e),{activityContext:p(o({},e.activityContext),{path:c})}))},onBeforeReplace({actionParams:e,actions:{overrideActionParams:i}}){let c=d(f(t.routes[e.activityName])[0]).fill(e.activityParams);i(p(o({},e),{activityContext:p(o({},e.activityContext),{path:c})}))},onBeforeStepPop({actions:{getStack:e}}){var c;if(typeof window=="undefined")return;let{activities:i}=e(),a=i.find(u=>u.isActive);((c=a==null?void 0:a.steps.length)!=null?c:0)>1&&(r+=1,window.history.back())},onBeforePop({actions:{getStack:e}}){var u;if(typeof window=="undefined")return;let{activities:i}=e(),a=i.find(y=>y.isActive),c=(u=a==null?void 0:a.steps.length)!=null?u:0;r+=c;do for(let y=0;y<c;y+=1)window.history.back();while(!T(N()))}}}}
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
historySyncPlugin: () => historySyncPlugin,
makeTemplate: () => makeTemplate,
normalizeRoute: () => normalizeRoute,
useRoutes: () => useRoutes
});
module.exports = __toCommonJS(src_exports);
// src/historySyncPlugin.tsx
var import_core = require("@stackflow/core");
var import_react2 = __toESM(require("react"));
// src/historyState.ts
var STATE_TAG = `${"@stackflow/plugin-history-sync"}@${"0.19.2"}`;
var isServer = typeof window === "undefined";
function getCurrentState() {
if (isServer) {
return null;
}
return window.history.state;
}
function parseState(state) {
const _state = state;
if (typeof _state === "object" && _state !== null && "_TAG" in _state && typeof _state._TAG === "string" && _state._TAG === STATE_TAG) {
return state;
}
return null;
}
function serializeStep(step) {
return __spreadProps(__spreadValues({}, step), {
pushedBy: "activityContext" in step.pushedBy ? __spreadProps(__spreadValues({}, step.pushedBy), {
activityContext: void 0
}) : __spreadValues({}, step.pushedBy)
});
}
function serializeActivity(activity) {
return __spreadProps(__spreadValues({}, activity), {
context: void 0,
pushedBy: __spreadProps(__spreadValues({}, activity.pushedBy), {
activityContext: void 0
}),
steps: activity.steps.map(serializeStep)
});
}
function serializeState(state) {
return {
_TAG: STATE_TAG,
activity: serializeActivity(state.activity),
step: state.step ? serializeStep(state.step) : void 0
};
}
function pushState({
state,
url,
useHash
}) {
if (isServer) {
return;
}
const nextUrl = useHash ? `${window.location.pathname}#${url}` : url;
window.history.pushState(serializeState(state), "", nextUrl);
}
function replaceState({
url,
state,
useHash
}) {
if (isServer) {
return;
}
const nextUrl = useHash ? `${window.location.pathname}#${url}` : url;
window.history.replaceState(serializeState(state), "", nextUrl);
}
// src/last.ts
function last(arr) {
return arr.length === 0 ? void 0 : arr[arr.length - 1];
}
// src/makeTemplate.ts
var import_url_pattern = __toESM(require("url-pattern"));
function pathToUrl(path) {
return new URL(path, "file://");
}
function urlSearchParamsToMap(urlSearchParams) {
const map = {};
urlSearchParams.forEach((value, key) => {
map[key] = value;
});
return map;
}
function appendTrailingSlashInPathname(pathname) {
if (pathname.endsWith("/")) {
return pathname;
}
return `${pathname}/`;
}
function prependQuestionMarkInSearchParams(searchParams) {
const searchParamsStr = searchParams.toString();
if (searchParamsStr.length > 0) {
return `?${searchParams}`;
}
return searchParams;
}
function makeTemplate(templateStr) {
const pattern = new import_url_pattern.default(`${templateStr}(/)`);
return {
fill(params) {
var _a;
const pathname = pattern.stringify(params);
const pathParams = (_a = pattern.match(pathname)) != null ? _a : {};
const searchParamsMap = __spreadValues({}, params);
Object.keys(pathParams).forEach((key) => {
delete searchParamsMap[key];
});
const searchParams = new URLSearchParams(searchParamsMap);
return appendTrailingSlashInPathname(pathname) + prependQuestionMarkInSearchParams(searchParams);
},
parse(path) {
const url = pathToUrl(path);
const pathParams = pattern.match(url.pathname);
const searchParams = urlSearchParamsToMap(url.searchParams);
if (!pathParams) {
return null;
}
return __spreadValues(__spreadValues({}, searchParams), pathParams);
}
};
}
// src/normalizeRoute.ts
function normalizeRoute(route) {
return typeof route === "string" ? [route] : route;
}
// src/RoutesContext.tsx
var import_react = __toESM(require("react"));
var RoutesContext = (0, import_react.createContext)({});
var RoutesProvider = (props) => /* @__PURE__ */ import_react.default.createElement(RoutesContext.Provider, {
value: props.routes
}, props.children);
RoutesProvider.displayName = "RoutesProvider";
function useRoutes() {
return (0, import_react.useContext)(RoutesContext);
}
// src/historySyncPlugin.tsx
var SECOND = 1e3;
var MINUTE = 60 * SECOND;
var isServer2 = typeof window === "undefined";
function historySyncPlugin(options) {
return () => {
let pushFlag = 0;
let popFlag = 0;
return {
key: "plugin-history-sync",
wrapStack({ stack }) {
return /* @__PURE__ */ import_react2.default.createElement(RoutesProvider, {
routes: options.routes
}, stack.render());
},
overrideInitialEvents({ initialContext }) {
var _a, _b;
const initialHistoryState = parseState(getCurrentState());
if (initialHistoryState) {
return [
__spreadProps(__spreadValues({}, initialHistoryState.activity.pushedBy), {
name: "Pushed"
}),
...((_a = initialHistoryState.step) == null ? void 0 : _a.pushedBy.name) === "StepPushed" || ((_b = initialHistoryState.step) == null ? void 0 : _b.pushedBy.name) === "StepReplaced" ? [
__spreadProps(__spreadValues({}, initialHistoryState.step.pushedBy), {
name: "StepPushed"
})
] : []
];
}
function resolvePath() {
var _a2, _b2;
if (((_a2 = initialContext == null ? void 0 : initialContext.req) == null ? void 0 : _a2.path) && typeof initialContext.req.path === "string") {
return initialContext.req.path;
}
if (isServer2) {
return null;
}
if (options.useHash) {
return (_b2 = window.location.hash.split("#")[1]) != null ? _b2 : "/";
}
return window.location.pathname + window.location.search;
}
const path = resolvePath();
const activityNames = Object.keys(options.routes);
if (path) {
for (let i = 0; i < activityNames.length; i += 1) {
const activityName = activityNames[i];
const routes = normalizeRoute(options.routes[activityName]);
for (let j = 0; j < routes.length; j += 1) {
const route = routes[j];
const template = makeTemplate(route);
const activityParams = template.parse(path);
const matched = !!activityParams;
if (matched) {
const activityId = (0, import_core.id)();
return [
(0, import_core.makeEvent)("Pushed", {
activityId,
activityName,
activityParams: __spreadValues({}, activityParams),
eventDate: new Date().getTime() - MINUTE,
activityContext: {
path
}
})
];
}
}
}
}
const fallbackActivityId = (0, import_core.id)();
const fallbackActivityName = options.fallbackActivity({
initialContext
});
const fallbackActivityRoutes = normalizeRoute(
options.routes[fallbackActivityName]
);
const fallbackActivityPath = fallbackActivityRoutes[0];
return [
(0, import_core.makeEvent)("Pushed", {
activityId: fallbackActivityId,
activityName: fallbackActivityName,
activityParams: {},
eventDate: new Date().getTime() - MINUTE,
activityContext: {
path: fallbackActivityPath
}
})
];
},
onInit({ actions: { getStack, dispatchEvent, push, stepPush } }) {
const rootActivity = getStack().activities[0];
const template = makeTemplate(
normalizeRoute(options.routes[rootActivity.name])[0]
);
window.getStack = getStack;
const lastStep = last(rootActivity.steps);
replaceState({
url: template.fill(rootActivity.params),
state: {
activity: rootActivity,
step: lastStep
},
useHash: options.useHash
});
const onPopState = (e) => {
if (popFlag) {
popFlag -= 1;
return;
}
const historyState = parseState(e.state);
if (!historyState) {
return;
}
const targetActivity = historyState.activity;
const targetActivityId = historyState.activity.id;
const targetStep = historyState.step;
const { activities } = getStack();
const currentActivity = activities.find(
(activity) => activity.isActive
);
if (!currentActivity) {
return;
}
const currentStep = last(currentActivity.steps);
const nextActivity = activities.find(
(activity) => activity.id === targetActivityId
);
const nextStep = currentActivity.steps.find(
(step) => step.id === (targetStep == null ? void 0 : targetStep.id)
);
const isBackward = () => currentActivity.id > targetActivityId;
const isForward = () => currentActivity.id < targetActivityId;
const isStep = () => currentActivity.id === targetActivityId;
const isStepBackward = () => {
if (!isStep()) {
return false;
}
if (!targetStep) {
return true;
}
if (currentStep && currentStep.id > targetStep.id) {
return true;
}
return false;
};
const isStepForward = () => {
if (!isStep()) {
return false;
}
if (!currentStep) {
return true;
}
if (targetStep && currentStep.id < targetStep.id) {
return true;
}
return false;
};
if (isBackward()) {
dispatchEvent("Popped", {});
if (!nextActivity) {
pushFlag += 1;
push(__spreadValues({}, targetActivity.pushedBy));
if ((targetStep == null ? void 0 : targetStep.pushedBy.name) === "StepPushed" || (targetStep == null ? void 0 : targetStep.pushedBy.name) === "StepReplaced") {
pushFlag += 1;
stepPush(__spreadValues({}, targetStep.pushedBy));
}
}
}
if (isStepBackward()) {
if (!nextStep && targetStep && ((targetStep == null ? void 0 : targetStep.pushedBy.name) === "StepPushed" || (targetStep == null ? void 0 : targetStep.pushedBy.name) === "StepReplaced")) {
pushFlag += 1;
stepPush(__spreadValues({}, targetStep.pushedBy));
}
dispatchEvent("StepPopped", {});
}
if (isForward()) {
pushFlag += 1;
push({
activityId: targetActivity.id,
activityName: targetActivity.name,
activityParams: targetActivity.params
});
}
if (isStepForward()) {
if (!targetStep) {
return;
}
pushFlag += 1;
stepPush({
stepId: targetStep.id,
stepParams: targetStep.params
});
}
};
if (!isServer2) {
window.addEventListener("popstate", onPopState);
}
},
onPushed({ effect: { activity } }) {
if (pushFlag) {
pushFlag -= 1;
return;
}
const template = makeTemplate(
normalizeRoute(options.routes[activity.name])[0]
);
pushState({
url: template.fill(activity.params),
state: {
activity
},
useHash: options.useHash
});
},
onStepPushed({ effect: { activity, step } }) {
if (pushFlag) {
pushFlag -= 1;
return;
}
const template = makeTemplate(
normalizeRoute(options.routes[activity.name])[0]
);
pushState({
url: template.fill(activity.params),
state: {
activity,
step
},
useHash: options.useHash
});
},
onReplaced({ effect: { activity } }) {
if (!activity.isActive) {
return;
}
const template = makeTemplate(
normalizeRoute(options.routes[activity.name])[0]
);
replaceState({
url: template.fill(activity.params),
state: {
activity
},
useHash: options.useHash
});
},
onStepReplaced({ effect: { activity, step } }) {
if (!activity.isActive) {
return;
}
const template = makeTemplate(
normalizeRoute(options.routes[activity.name])[0]
);
replaceState({
url: template.fill(activity.params),
state: {
activity,
step
},
useHash: options.useHash
});
},
onBeforePush({ actionParams, actions: { overrideActionParams } }) {
const template = makeTemplate(
normalizeRoute(options.routes[actionParams.activityName])[0]
);
const path = template.fill(actionParams.activityParams);
overrideActionParams(__spreadProps(__spreadValues({}, actionParams), {
activityContext: __spreadProps(__spreadValues({}, actionParams.activityContext), {
path
})
}));
},
onBeforeReplace({ actionParams, actions: { overrideActionParams } }) {
const template = makeTemplate(
normalizeRoute(options.routes[actionParams.activityName])[0]
);
const path = template.fill(actionParams.activityParams);
overrideActionParams(__spreadProps(__spreadValues({}, actionParams), {
activityContext: __spreadProps(__spreadValues({}, actionParams.activityContext), {
path
})
}));
},
onBeforeStepPop({ actions: { getStack } }) {
var _a;
if (typeof window === "undefined") {
return;
}
const { activities } = getStack();
const currentActivity = activities.find(
(activity) => activity.isActive
);
if (((_a = currentActivity == null ? void 0 : currentActivity.steps.length) != null ? _a : 0) > 1) {
popFlag += 1;
window.history.back();
}
},
onBeforePop({ actions: { getStack } }) {
var _a;
if (typeof window === "undefined") {
return;
}
const { activities } = getStack();
const currentActivity = activities.find(
(activity) => activity.isActive
);
const popCount = (_a = currentActivity == null ? void 0 : currentActivity.steps.length) != null ? _a : 0;
popFlag += popCount;
do {
for (let i = 0; i < popCount; i += 1) {
window.history.back();
}
} while (!parseState(getCurrentState()));
}
};
};
}
//# sourceMappingURL=index.js.map

12

package.json
{
"name": "@stackflow/plugin-history-sync",
"version": "0.19.2",
"version": "0.20.0",
"license": "MIT",

@@ -30,6 +30,6 @@ "exports": {

"devDependencies": {
"@stackflow/core": "^0.19.1",
"@stackflow/esbuild-config": "^0.19.0",
"@stackflow/eslint-config": "^0.19.0",
"@stackflow/react": "^0.19.1",
"@stackflow/core": "^0.20.0",
"@stackflow/esbuild-config": "^0.20.0",
"@stackflow/eslint-config": "^0.20.0",
"@stackflow/react": "^0.20.0",
"@types/node": "^18.6.3",

@@ -68,3 +68,3 @@ "@types/react": "^18.0.10",

},
"gitHead": "14fbdfe7fc610c57baaf1b838ff517d39c71909c"
"gitHead": "764a17cfd23ebdad801b4b58f67c53505149f4db"
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

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

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc