Skip to content

Commit

Permalink
fix: prototype pollution alert from AI
Browse files Browse the repository at this point in the history
  • Loading branch information
mayank1513 committed Jun 22, 2024
1 parent 8d0f911 commit 9143209
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 58 deletions.
12 changes: 6 additions & 6 deletions lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ export type { SetterArgType, SetStateAction, Plugin } from "./utils";
const useRGS = <T>(key: string, value?: ValueType<T>): [T, SetStateAction<T>] => {
/** Initialize the named store when invoked for the first time. */
if (!globalRGS[key])
globalRGS[key] = [
globalRGS[key] = {
// @ts-expect-error -- ok
typeof value === "function" ? value() : value,
[],
createSetter(key),
createSubcriber(key),
];
v: typeof value === "function" ? value() : value,
l: [],
s: createSetter(key),
u: createSubcriber(key),
};

return createHook<T>(key);
};
Expand Down
38 changes: 18 additions & 20 deletions lib/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ export type ValueType<T> = T | (() => T);
/**
* This is a hack to reduce lib size + readability + not encouraging direct access to globalThis
*/
const [VALUE, LISTENERS, SETTER, SUBSCRIBER] = [0, 1, 2, 3, 4];
type RGS = [unknown, Listener[], SetStateAction<unknown> | null, Subscriber];
type RGS = { v: unknown; l: Listener[]; s: SetStateAction<unknown> | null; u: Subscriber };

declare global {
// eslint-disable-next-line no-var -- var required for global declaration.
Expand All @@ -24,16 +23,15 @@ globalThisForBetterMinification.rgs = {};
export const globalRGS = globalThisForBetterMinification.rgs;

/** trigger all listeners */
const triggerListeners = (rgs: RGS) =>
(rgs[LISTENERS] as Listener[]).forEach(listener => listener());
const triggerListeners = (rgs: RGS) => rgs.l.forEach(listener => listener());

/** craete subscriber function to subscribe to the store. */
export const createSubcriber = (key: string): Subscriber => {
return listener => {
const rgs = globalRGS[key] as RGS;
(rgs[LISTENERS] as Listener[]).push(listener);
(rgs.l as Listener[]).push(listener);
return () => {
rgs[LISTENERS] = (rgs[LISTENERS] as Listener[]).filter(l => l !== listener);
rgs.l = (rgs.l as Listener[]).filter(l => l !== listener);
};
};
};
Expand All @@ -42,18 +40,18 @@ export const createSubcriber = (key: string): Subscriber => {
export const createSetter = <T>(key: string): SetStateAction<unknown> => {
return val => {
const rgs = globalRGS[key] as RGS;
rgs[VALUE] = typeof val === "function" ? val(rgs[VALUE] as T) : val;
(rgs[LISTENERS] as Listener[]).forEach(listener => listener());
rgs.v = typeof val === "function" ? val(rgs.v as T) : val;
(rgs.l as Listener[]).forEach(listener => listener());
};
};

/** Extract coomon create hook logic to utils */
export const createHook = <T>(key: string): [T, SetStateAction<T>] => {
const rgs = globalRGS[key] as RGS;
/** This function is called by react to get the current stored value. */
const getSnapshot = () => rgs[VALUE] as T;
const val = useSyncExternalStore<T>(rgs[SUBSCRIBER] as Subscriber, getSnapshot, getSnapshot);
return [val, rgs[SETTER] as SetStateAction<T>];
const getSnapshot = () => rgs.v as T;
const val = useSyncExternalStore<T>(rgs.u as Subscriber, getSnapshot, getSnapshot);
return [val, rgs.s as SetStateAction<T>];
};

type Mutate<T> = (value?: T) => void;
Expand All @@ -69,12 +67,12 @@ const initPlugins = async <T>(key: string, plugins: Plugin<T>[]) => {
const rgs = globalRGS[key] as RGS;
/** Mutate function to update the value */
const mutate: Mutate<T> = newValue => {
rgs[VALUE] = newValue;
rgs.v = newValue;
triggerListeners(rgs);
};
for (const plugin of plugins) {
/** Next plugins initializer will get the new value if updated by previous one */
await plugin.init?.(key, rgs[VALUE] as T, mutate);
await plugin.init?.(key, rgs.v as T, mutate);
}
allExtentionsInitialized = true;
};
Expand All @@ -89,24 +87,24 @@ export const initWithPlugins = <T>(
value = value instanceof Function ? value() : value;
if (doNotInit) {
/** You will not have access to the setter until initialized */
globalRGS[key] = [value, [], null, createSubcriber(key)];
globalRGS[key] = { v: value, l: [], s: null, u: createSubcriber(key) };
return;
}
/** setter function to set the state. */
const setterWithPlugins: SetStateAction<unknown> = val => {
/** Do not allow mutating the store before all extentions are initialized */
if (!allExtentionsInitialized) return;
const rgs = globalRGS[key] as RGS;
rgs[VALUE] = val instanceof Function ? val(rgs[VALUE] as T) : val;
rgs.v = val instanceof Function ? val(rgs.v as T) : val;
triggerListeners(rgs);
plugins.forEach(plugin => plugin.onChange?.(key, rgs[VALUE] as T));
plugins.forEach(plugin => plugin.onChange?.(key, rgs.v as T));
};

const rgs = globalRGS[key];
if (rgs) {
rgs[VALUE] = value;
rgs[SETTER] = setterWithPlugins;
} else globalRGS[key] = [value, [], setterWithPlugins, createSubcriber(key)];
rgs.v = value;
rgs.s = setterWithPlugins;
} else globalRGS[key] = { v: value, l: [], s: setterWithPlugins, u: createSubcriber(key) };
initPlugins(key, plugins);
};

Expand All @@ -133,6 +131,6 @@ export const useRGSWithPlugins = <T>(
plugins?: Plugin<T>[],
doNotInit = false,
): [T, SetStateAction<T>] => {
if (!globalRGS[key]?.[SETTER]) initWithPlugins(key, value, plugins, doNotInit);
if (!globalRGS[key]?.s) initWithPlugins(key, value, plugins, doNotInit);
return createHook<T>(key);
};
32 changes: 0 additions & 32 deletions lib/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,4 @@ export default defineConfig(options => ({
clean: !options.watch,
bundle: true,
minify: !options.watch,
esbuildPlugins: [
{
name: "improve-minify",
setup(build) {
build.onLoad({ filter: /utils.ts/ }, args => {
let contents = fs.readFileSync(args.path, "utf8");
const lines = contents.split("\n");
const hackLine = lines.find(line => line.startsWith("const [VALUE,"));

if (!hackLine) return { contents, loader: "ts" };
/** remove hackLine */
contents = contents.replace(hackLine, "");

/** clean up */
const tokens = hackLine
.replace("const", "")
.split("=")[0]
.trim()
.replace(/\]|\[/g, "")
.split(",")
.map((t, i) => ({ token: t.trim(), i }));

tokens.sort((a, b) => b.token.length - a.token.length);

for (const t of tokens) {
contents = contents.replace(new RegExp(`${t.token}`, "g"), String(t.i));
}
return { contents, loader: "ts" };
});
},
},
],
}));

0 comments on commit 9143209

Please sign in to comment.