@wulu007/stealth-hook 0.0.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,15 @@
1
-
2
1
  # StealthHook
3
2
 
4
3
  A lightweight JavaScript hook library for intercepting and modifying function behavior in the browser. Designed for stealth modifications with automatic iframe detection and recursive hooking.
5
4
 
6
- [![npm version](https://img.shields.io/npm/v/stealth-hook)](https://www.npmjs.com/package/stealth-hook)
7
- [![MIT license](https://img.shields.io/npm/l/stealth-hook)](https://github.com/wulu007/StealthHook/blob/main/LICENSE)
5
+ <div align="center">
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@wulu007/stealth-hook.svg?style=flat-square)](https://www.npmjs.com/package/@wulu007/stealth-hook)
8
+ [![npm downloads](https://img.shields.io/npm/dm/@wulu007/stealth-hook.svg?style=flat-square)](https://www.npmjs.com/package/@wulu007/stealth-hook)
9
+ [![License](https://img.shields.io/npm/l/@wulu007/stealth-hook.svg?style=flat-square)](https://github.com/wulu007/StealthHook/blob/main/LICENSE)
10
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@wulu007/stealth-hook?style=flat-square)](https://bundlephobia.com/package/@wulu007/stealth-hook)
11
+
12
+ </div>
8
13
 
9
14
  ## Features
10
15
 
@@ -29,7 +34,7 @@ pnpm add stealth-hook
29
34
  ### In Userscripts
30
35
 
31
36
  ```javascript
32
- // @require https://unpkg.com/stealth-hook@latest/dist/index.js
37
+ // @require https://unpkg.com/@wulu007/stealth-hook@latest/dist/index.global.js
33
38
  // @grant unsafeWindow
34
39
  // @run-at document-start
35
40
  ```
@@ -60,7 +65,7 @@ hookScope(({ hookMethod }, win) => {
60
65
  // @description Hook Example
61
66
  // @author You
62
67
  // @match *://*/*
63
- // @require https://unpkg.com/stealth-hook@latest/dist/index.js
68
+ // @require https://unpkg.com/@wulu007/stealth-hook@latest/dist/index.global.js
64
69
  // @grant unsafeWindow
65
70
  // @run-at document-start
66
71
  // ==/UserScript==
package/dist/index.cjs CHANGED
@@ -124,16 +124,18 @@ var createLogger = /* @__PURE__ */ (() => {
124
124
 
125
125
  // src/index.ts
126
126
  function hookScope(callback, rootWindow) {
127
+ const hookedProtoSet = /* @__PURE__ */ new WeakSet();
127
128
  const interceptorSetupMap = /* @__PURE__ */ new WeakSet();
128
- const hookedWindows = /* @__PURE__ */ new WeakMap();
129
+ const windowIdMap = /* @__PURE__ */ new WeakMap();
130
+ const iframeListenerMap = /* @__PURE__ */ new WeakMap();
129
131
  let windowCounter = 0;
130
132
  const $reflectApply = Reflect.apply;
131
133
  const getWindowId = (win) => {
132
- if (!hookedWindows.has(win)) {
134
+ if (!windowIdMap.has(win)) {
133
135
  const id = win === rootWindow ? "main" : `iframe-${++windowCounter}`;
134
- hookedWindows.set(win, id);
136
+ windowIdMap.set(win, id);
135
137
  }
136
- return hookedWindows.get(win);
138
+ return windowIdMap.get(win);
137
139
  };
138
140
  const setupToStringHook = (win) => {
139
141
  hook.method(win.Function.prototype, "toString", (target, args, thisArg) => {
@@ -143,54 +145,93 @@ function hookScope(callback, rootWindow) {
143
145
  return $reflectApply(target, thisArg, args);
144
146
  });
145
147
  };
146
- function setupIframeInterceptor(win, applyCallback) {
147
- if (interceptorSetupMap.has(win))
148
+ const handleIframeNode = (node, applyCallback) => {
149
+ const element = node;
150
+ const tryHook = () => {
151
+ try {
152
+ const childWin = element.contentWindow;
153
+ if (childWin) {
154
+ applyCallback(childWin);
155
+ setupObserver(childWin, childWin.document, applyCallback);
156
+ }
157
+ } catch {
158
+ }
159
+ };
160
+ tryHook();
161
+ const oldListener = iframeListenerMap.get(element);
162
+ if (oldListener)
163
+ element.removeEventListener("load", oldListener);
164
+ element.addEventListener("load", tryHook);
165
+ iframeListenerMap.set(element, tryHook);
166
+ };
167
+ function setupObserver(win, targetNode, applyCallback) {
168
+ if (interceptorSetupMap.has(targetNode))
148
169
  return;
149
- interceptorSetupMap.add(win);
170
+ interceptorSetupMap.add(targetNode);
150
171
  const logger = createLogger(getWindowId(win));
151
172
  try {
152
- const NodeProto = win.Node.prototype;
153
- const hookInsertMethod = (methodName) => {
154
- hook.method(NodeProto, methodName, (target, args, thisArg) => {
155
- const result = $reflectApply(target, thisArg, args);
156
- try {
157
- const node = args[0];
158
- if (!node || node.tagName !== "IFRAME")
159
- return result;
160
- const iframe = node;
161
- const handleIframe = () => {
162
- const childWin = iframe.contentWindow;
163
- if (childWin) {
164
- applyCallback(childWin);
165
- setupIframeInterceptor(childWin, applyCallback);
173
+ const observer = new win.MutationObserver((mutations) => {
174
+ for (const mutation of mutations) {
175
+ if (mutation.type === "childList") {
176
+ mutation.addedNodes.forEach((node) => {
177
+ const tagName = node.tagName;
178
+ if (tagName === "IFRAME" || tagName === "FRAME" || tagName === "OBJECT") {
179
+ handleIframeNode(node, applyCallback);
180
+ } else if (node instanceof win.HTMLElement) {
181
+ if (node.childElementCount > 0) {
182
+ const frames = node.querySelectorAll("iframe, frame, object");
183
+ frames.forEach((frame) => handleIframeNode(frame, applyCallback));
184
+ }
166
185
  }
167
- };
168
- handleIframe();
169
- } catch (e) {
170
- logger.warn(`Error in ${String(methodName)} hook:`, e);
186
+ });
187
+ } else if (mutation.type === "attributes") {
188
+ if (mutation.target instanceof win.Element) {
189
+ logger.log(`\u{1F504} Detected attribute change on <${mutation.target.tagName}>`, mutation.attributeName);
190
+ handleIframeNode(mutation.target, applyCallback);
191
+ }
171
192
  }
172
- return result;
173
- });
174
- };
175
- hookInsertMethod("appendChild");
176
- hookInsertMethod("insertBefore");
177
- hookInsertMethod("replaceChild");
193
+ }
194
+ });
195
+ observer.observe(targetNode, {
196
+ childList: true,
197
+ subtree: true,
198
+ attributes: true,
199
+ attributeFilter: ["src", "srcdoc", "data"]
200
+ });
178
201
  } catch (e) {
179
- logger.warn("\u274C Failed to setup iframe interceptor", e);
202
+ logger.warn("\u274C Failed to setup MutationObserver", e);
180
203
  }
181
204
  }
205
+ const setupShadowDomHook = (win, applyCallback) => {
206
+ try {
207
+ if (win.Element.prototype.attachShadow) {
208
+ hook.method(win.Element.prototype, "attachShadow", (original, args, scope) => {
209
+ const shadowRoot = $reflectApply(original, scope, args);
210
+ if (shadowRoot) {
211
+ setupObserver(win, shadowRoot, applyCallback);
212
+ }
213
+ return shadowRoot;
214
+ });
215
+ }
216
+ } catch {
217
+ }
218
+ };
182
219
  const applyToWindow = (win) => {
183
- if (hookedWindows.has(win))
220
+ try {
221
+ const proto = win.Function.prototype;
222
+ if (hookedProtoSet.has(proto))
223
+ return;
224
+ hookedProtoSet.add(proto);
225
+ } catch {
184
226
  return;
227
+ }
185
228
  const logger = createLogger(getWindowId(win));
186
229
  setupToStringHook(win);
230
+ setupShadowDomHook(win, applyToWindow);
187
231
  const utils = {
188
232
  hookMethod: (obj, fnName, handler) => {
189
233
  hook.method(obj, fnName, handler);
190
234
  },
191
- hookBindThisMethod(obj, fnName, handler) {
192
- hook.bindThisMethod(obj, fnName, handler);
193
- },
194
235
  getNativeFunction: hook.getNativeFunction,
195
236
  getNativeString: hook.getNativeString,
196
237
  getCurrentWindowId: () => getWindowId(win)
@@ -203,7 +244,7 @@ function hookScope(callback, rootWindow) {
203
244
  }
204
245
  };
205
246
  applyToWindow(rootWindow);
206
- setupIframeInterceptor(rootWindow, applyToWindow);
247
+ setupObserver(rootWindow, rootWindow.document, applyToWindow);
207
248
  }
208
249
  // Annotate the CommonJS export names for ESM import in node:
209
250
  0 && (module.exports = {
package/dist/index.d.cts CHANGED
@@ -20,11 +20,14 @@ declare global {
20
20
  interface Window {
21
21
  Node: typeof Node;
22
22
  Function: typeof Function;
23
+ MutationObserver: typeof MutationObserver;
24
+ HTMLIFrameElement: typeof HTMLIFrameElement;
25
+ HTMLElement: typeof HTMLElement;
26
+ Element: typeof Element;
23
27
  }
24
28
  }
25
29
  interface HookUtils {
26
30
  hookMethod: <T extends object, K extends keyof T>(obj: T, fnName: K, handler: HookHandler<T[K] extends AnyFunction ? T[K] : AnyFunction>) => void;
27
- hookBindThisMethod: <T extends object, K extends keyof T>(obj: T, fnName: K, handler: HookHandler<T[K] extends AnyFunction ? T[K] : AnyFunction>) => void;
28
31
  getCurrentWindowId: () => string | number;
29
32
  getNativeFunction: typeof hook.getNativeFunction;
30
33
  getNativeString: typeof hook.getNativeString;
package/dist/index.d.ts CHANGED
@@ -20,11 +20,14 @@ declare global {
20
20
  interface Window {
21
21
  Node: typeof Node;
22
22
  Function: typeof Function;
23
+ MutationObserver: typeof MutationObserver;
24
+ HTMLIFrameElement: typeof HTMLIFrameElement;
25
+ HTMLElement: typeof HTMLElement;
26
+ Element: typeof Element;
23
27
  }
24
28
  }
25
29
  interface HookUtils {
26
30
  hookMethod: <T extends object, K extends keyof T>(obj: T, fnName: K, handler: HookHandler<T[K] extends AnyFunction ? T[K] : AnyFunction>) => void;
27
- hookBindThisMethod: <T extends object, K extends keyof T>(obj: T, fnName: K, handler: HookHandler<T[K] extends AnyFunction ? T[K] : AnyFunction>) => void;
28
31
  getCurrentWindowId: () => string | number;
29
32
  getNativeFunction: typeof hook.getNativeFunction;
30
33
  getNativeString: typeof hook.getNativeString;
@@ -124,16 +124,18 @@ var StealthHook = (() => {
124
124
 
125
125
  // src/index.ts
126
126
  function hookScope(callback, rootWindow) {
127
+ const hookedProtoSet = /* @__PURE__ */ new WeakSet();
127
128
  const interceptorSetupMap = /* @__PURE__ */ new WeakSet();
128
- const hookedWindows = /* @__PURE__ */ new WeakMap();
129
+ const windowIdMap = /* @__PURE__ */ new WeakMap();
130
+ const iframeListenerMap = /* @__PURE__ */ new WeakMap();
129
131
  let windowCounter = 0;
130
132
  const $reflectApply = Reflect.apply;
131
133
  const getWindowId = (win) => {
132
- if (!hookedWindows.has(win)) {
134
+ if (!windowIdMap.has(win)) {
133
135
  const id = win === rootWindow ? "main" : `iframe-${++windowCounter}`;
134
- hookedWindows.set(win, id);
136
+ windowIdMap.set(win, id);
135
137
  }
136
- return hookedWindows.get(win);
138
+ return windowIdMap.get(win);
137
139
  };
138
140
  const setupToStringHook = (win) => {
139
141
  hook.method(win.Function.prototype, "toString", (target, args, thisArg) => {
@@ -143,54 +145,93 @@ var StealthHook = (() => {
143
145
  return $reflectApply(target, thisArg, args);
144
146
  });
145
147
  };
146
- function setupIframeInterceptor(win, applyCallback) {
147
- if (interceptorSetupMap.has(win))
148
+ const handleIframeNode = (node, applyCallback) => {
149
+ const element = node;
150
+ const tryHook = () => {
151
+ try {
152
+ const childWin = element.contentWindow;
153
+ if (childWin) {
154
+ applyCallback(childWin);
155
+ setupObserver(childWin, childWin.document, applyCallback);
156
+ }
157
+ } catch {
158
+ }
159
+ };
160
+ tryHook();
161
+ const oldListener = iframeListenerMap.get(element);
162
+ if (oldListener)
163
+ element.removeEventListener("load", oldListener);
164
+ element.addEventListener("load", tryHook);
165
+ iframeListenerMap.set(element, tryHook);
166
+ };
167
+ function setupObserver(win, targetNode, applyCallback) {
168
+ if (interceptorSetupMap.has(targetNode))
148
169
  return;
149
- interceptorSetupMap.add(win);
170
+ interceptorSetupMap.add(targetNode);
150
171
  const logger = createLogger(getWindowId(win));
151
172
  try {
152
- const NodeProto = win.Node.prototype;
153
- const hookInsertMethod = (methodName) => {
154
- hook.method(NodeProto, methodName, (target, args, thisArg) => {
155
- const result = $reflectApply(target, thisArg, args);
156
- try {
157
- const node = args[0];
158
- if (!node || node.tagName !== "IFRAME")
159
- return result;
160
- const iframe = node;
161
- const handleIframe = () => {
162
- const childWin = iframe.contentWindow;
163
- if (childWin) {
164
- applyCallback(childWin);
165
- setupIframeInterceptor(childWin, applyCallback);
173
+ const observer = new win.MutationObserver((mutations) => {
174
+ for (const mutation of mutations) {
175
+ if (mutation.type === "childList") {
176
+ mutation.addedNodes.forEach((node) => {
177
+ const tagName = node.tagName;
178
+ if (tagName === "IFRAME" || tagName === "FRAME" || tagName === "OBJECT") {
179
+ handleIframeNode(node, applyCallback);
180
+ } else if (node instanceof win.HTMLElement) {
181
+ if (node.childElementCount > 0) {
182
+ const frames = node.querySelectorAll("iframe, frame, object");
183
+ frames.forEach((frame) => handleIframeNode(frame, applyCallback));
184
+ }
166
185
  }
167
- };
168
- handleIframe();
169
- } catch (e) {
170
- logger.warn(`Error in ${String(methodName)} hook:`, e);
186
+ });
187
+ } else if (mutation.type === "attributes") {
188
+ if (mutation.target instanceof win.Element) {
189
+ logger.log(`\u{1F504} Detected attribute change on <${mutation.target.tagName}>`, mutation.attributeName);
190
+ handleIframeNode(mutation.target, applyCallback);
191
+ }
171
192
  }
172
- return result;
173
- });
174
- };
175
- hookInsertMethod("appendChild");
176
- hookInsertMethod("insertBefore");
177
- hookInsertMethod("replaceChild");
193
+ }
194
+ });
195
+ observer.observe(targetNode, {
196
+ childList: true,
197
+ subtree: true,
198
+ attributes: true,
199
+ attributeFilter: ["src", "srcdoc", "data"]
200
+ });
178
201
  } catch (e) {
179
- logger.warn("\u274C Failed to setup iframe interceptor", e);
202
+ logger.warn("\u274C Failed to setup MutationObserver", e);
180
203
  }
181
204
  }
205
+ const setupShadowDomHook = (win, applyCallback) => {
206
+ try {
207
+ if (win.Element.prototype.attachShadow) {
208
+ hook.method(win.Element.prototype, "attachShadow", (original, args, scope) => {
209
+ const shadowRoot = $reflectApply(original, scope, args);
210
+ if (shadowRoot) {
211
+ setupObserver(win, shadowRoot, applyCallback);
212
+ }
213
+ return shadowRoot;
214
+ });
215
+ }
216
+ } catch {
217
+ }
218
+ };
182
219
  const applyToWindow = (win) => {
183
- if (hookedWindows.has(win))
220
+ try {
221
+ const proto = win.Function.prototype;
222
+ if (hookedProtoSet.has(proto))
223
+ return;
224
+ hookedProtoSet.add(proto);
225
+ } catch {
184
226
  return;
227
+ }
185
228
  const logger = createLogger(getWindowId(win));
186
229
  setupToStringHook(win);
230
+ setupShadowDomHook(win, applyToWindow);
187
231
  const utils = {
188
232
  hookMethod: (obj, fnName, handler) => {
189
233
  hook.method(obj, fnName, handler);
190
234
  },
191
- hookBindThisMethod(obj, fnName, handler) {
192
- hook.bindThisMethod(obj, fnName, handler);
193
- },
194
235
  getNativeFunction: hook.getNativeFunction,
195
236
  getNativeString: hook.getNativeString,
196
237
  getCurrentWindowId: () => getWindowId(win)
@@ -203,7 +244,7 @@ var StealthHook = (() => {
203
244
  }
204
245
  };
205
246
  applyToWindow(rootWindow);
206
- setupIframeInterceptor(rootWindow, applyToWindow);
247
+ setupObserver(rootWindow, rootWindow.document, applyToWindow);
207
248
  }
208
249
  return __toCommonJS(index_exports);
209
250
  })();
package/dist/index.js CHANGED
@@ -98,16 +98,18 @@ var createLogger = /* @__PURE__ */ (() => {
98
98
 
99
99
  // src/index.ts
100
100
  function hookScope(callback, rootWindow) {
101
+ const hookedProtoSet = /* @__PURE__ */ new WeakSet();
101
102
  const interceptorSetupMap = /* @__PURE__ */ new WeakSet();
102
- const hookedWindows = /* @__PURE__ */ new WeakMap();
103
+ const windowIdMap = /* @__PURE__ */ new WeakMap();
104
+ const iframeListenerMap = /* @__PURE__ */ new WeakMap();
103
105
  let windowCounter = 0;
104
106
  const $reflectApply = Reflect.apply;
105
107
  const getWindowId = (win) => {
106
- if (!hookedWindows.has(win)) {
108
+ if (!windowIdMap.has(win)) {
107
109
  const id = win === rootWindow ? "main" : `iframe-${++windowCounter}`;
108
- hookedWindows.set(win, id);
110
+ windowIdMap.set(win, id);
109
111
  }
110
- return hookedWindows.get(win);
112
+ return windowIdMap.get(win);
111
113
  };
112
114
  const setupToStringHook = (win) => {
113
115
  hook.method(win.Function.prototype, "toString", (target, args, thisArg) => {
@@ -117,54 +119,93 @@ function hookScope(callback, rootWindow) {
117
119
  return $reflectApply(target, thisArg, args);
118
120
  });
119
121
  };
120
- function setupIframeInterceptor(win, applyCallback) {
121
- if (interceptorSetupMap.has(win))
122
+ const handleIframeNode = (node, applyCallback) => {
123
+ const element = node;
124
+ const tryHook = () => {
125
+ try {
126
+ const childWin = element.contentWindow;
127
+ if (childWin) {
128
+ applyCallback(childWin);
129
+ setupObserver(childWin, childWin.document, applyCallback);
130
+ }
131
+ } catch {
132
+ }
133
+ };
134
+ tryHook();
135
+ const oldListener = iframeListenerMap.get(element);
136
+ if (oldListener)
137
+ element.removeEventListener("load", oldListener);
138
+ element.addEventListener("load", tryHook);
139
+ iframeListenerMap.set(element, tryHook);
140
+ };
141
+ function setupObserver(win, targetNode, applyCallback) {
142
+ if (interceptorSetupMap.has(targetNode))
122
143
  return;
123
- interceptorSetupMap.add(win);
144
+ interceptorSetupMap.add(targetNode);
124
145
  const logger = createLogger(getWindowId(win));
125
146
  try {
126
- const NodeProto = win.Node.prototype;
127
- const hookInsertMethod = (methodName) => {
128
- hook.method(NodeProto, methodName, (target, args, thisArg) => {
129
- const result = $reflectApply(target, thisArg, args);
130
- try {
131
- const node = args[0];
132
- if (!node || node.tagName !== "IFRAME")
133
- return result;
134
- const iframe = node;
135
- const handleIframe = () => {
136
- const childWin = iframe.contentWindow;
137
- if (childWin) {
138
- applyCallback(childWin);
139
- setupIframeInterceptor(childWin, applyCallback);
147
+ const observer = new win.MutationObserver((mutations) => {
148
+ for (const mutation of mutations) {
149
+ if (mutation.type === "childList") {
150
+ mutation.addedNodes.forEach((node) => {
151
+ const tagName = node.tagName;
152
+ if (tagName === "IFRAME" || tagName === "FRAME" || tagName === "OBJECT") {
153
+ handleIframeNode(node, applyCallback);
154
+ } else if (node instanceof win.HTMLElement) {
155
+ if (node.childElementCount > 0) {
156
+ const frames = node.querySelectorAll("iframe, frame, object");
157
+ frames.forEach((frame) => handleIframeNode(frame, applyCallback));
158
+ }
140
159
  }
141
- };
142
- handleIframe();
143
- } catch (e) {
144
- logger.warn(`Error in ${String(methodName)} hook:`, e);
160
+ });
161
+ } else if (mutation.type === "attributes") {
162
+ if (mutation.target instanceof win.Element) {
163
+ logger.log(`\u{1F504} Detected attribute change on <${mutation.target.tagName}>`, mutation.attributeName);
164
+ handleIframeNode(mutation.target, applyCallback);
165
+ }
145
166
  }
146
- return result;
147
- });
148
- };
149
- hookInsertMethod("appendChild");
150
- hookInsertMethod("insertBefore");
151
- hookInsertMethod("replaceChild");
167
+ }
168
+ });
169
+ observer.observe(targetNode, {
170
+ childList: true,
171
+ subtree: true,
172
+ attributes: true,
173
+ attributeFilter: ["src", "srcdoc", "data"]
174
+ });
152
175
  } catch (e) {
153
- logger.warn("\u274C Failed to setup iframe interceptor", e);
176
+ logger.warn("\u274C Failed to setup MutationObserver", e);
154
177
  }
155
178
  }
179
+ const setupShadowDomHook = (win, applyCallback) => {
180
+ try {
181
+ if (win.Element.prototype.attachShadow) {
182
+ hook.method(win.Element.prototype, "attachShadow", (original, args, scope) => {
183
+ const shadowRoot = $reflectApply(original, scope, args);
184
+ if (shadowRoot) {
185
+ setupObserver(win, shadowRoot, applyCallback);
186
+ }
187
+ return shadowRoot;
188
+ });
189
+ }
190
+ } catch {
191
+ }
192
+ };
156
193
  const applyToWindow = (win) => {
157
- if (hookedWindows.has(win))
194
+ try {
195
+ const proto = win.Function.prototype;
196
+ if (hookedProtoSet.has(proto))
197
+ return;
198
+ hookedProtoSet.add(proto);
199
+ } catch {
158
200
  return;
201
+ }
159
202
  const logger = createLogger(getWindowId(win));
160
203
  setupToStringHook(win);
204
+ setupShadowDomHook(win, applyToWindow);
161
205
  const utils = {
162
206
  hookMethod: (obj, fnName, handler) => {
163
207
  hook.method(obj, fnName, handler);
164
208
  },
165
- hookBindThisMethod(obj, fnName, handler) {
166
- hook.bindThisMethod(obj, fnName, handler);
167
- },
168
209
  getNativeFunction: hook.getNativeFunction,
169
210
  getNativeString: hook.getNativeString,
170
211
  getCurrentWindowId: () => getWindowId(win)
@@ -177,7 +218,7 @@ function hookScope(callback, rootWindow) {
177
218
  }
178
219
  };
179
220
  applyToWindow(rootWindow);
180
- setupIframeInterceptor(rootWindow, applyToWindow);
221
+ setupObserver(rootWindow, rootWindow.document, applyToWindow);
181
222
  }
182
223
  export {
183
224
  hookScope
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@wulu007/stealth-hook",
3
3
  "type": "module",
4
- "version": "0.0.1",
4
+ "version": "0.1.2",
5
+ "packageManager": "pnpm@10.26.1",
5
6
  "description": "A stealth JavaScript hook library with automatic iframe detection and function toString spoofing.",
6
7
  "author": "wulu007",
7
8
  "license": "MIT",
@@ -33,10 +34,19 @@
33
34
  "README.md",
34
35
  "dist"
35
36
  ],
37
+ "scripts": {
38
+ "build": "tsup",
39
+ "dev": "tsup --watch",
40
+ "typecheck": "tsc --noEmit",
41
+ "lint": "eslint .",
42
+ "lint:fix": "eslint . --fix",
43
+ "prepare": "husky install"
44
+ },
36
45
  "devDependencies": {
37
46
  "@antfu/eslint-config": "^7.1.0",
38
47
  "@types/node": "^25.0.9",
39
48
  "eslint": "^9.39.2",
49
+ "husky": "^9.1.7",
40
50
  "tsup": "^8.5.1",
41
51
  "tsx": "^4.21.0",
42
52
  "typescript": "^5.9.3"
@@ -45,9 +55,7 @@
45
55
  "access": "public",
46
56
  "registry": "https://registry.npmjs.org/"
47
57
  },
48
- "scripts": {
49
- "build": "tsup",
50
- "dev": "tsup --watch",
51
- "typecheck": "tsc --noEmit"
58
+ "lint-staged": {
59
+ "*.{js,ts}": "eslint --fix"
52
60
  }
53
- }
61
+ }