diff --git a/.gitignore b/.gitignore index 0c0aa04..a8e0bc9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /node_modules/ /npm-debug.log +/dist diff --git a/index.js b/index.ts similarity index 71% rename from index.js rename to index.ts index 370e70e..fd253d8 100644 --- a/index.js +++ b/index.ts @@ -1,9 +1,9 @@ -import ObjC from 'frida-objc-bridge'; +import ObjC from "frida-objc-bridge"; -import { UINode } from './lib/node.js'; -export { UINode } from './lib/node.js'; +import { UINode } from "./lib/node.js"; +export { UINode } from "./lib/node.js"; -export function get(predicate) { +export function get(predicate: (x: UINode) => boolean): Promise { return new Promise((resolve, reject) => { let tries = 0; function tryResolve() { @@ -22,7 +22,7 @@ export function get(predicate) { if (tries < 40) { setTimeout(tryResolve, 500); } else { - reject(new Error('Timed out')); + reject(new Error("Timed out")); } }); } @@ -30,7 +30,7 @@ export function get(predicate) { }); } -let _api = null; +let _api: { UIWindow: ObjC.Object } | null = null; function getApi() { if (_api === null) { diff --git a/lib/node.js b/lib/node.js deleted file mode 100644 index 13bcb00..0000000 --- a/lib/node.js +++ /dev/null @@ -1,243 +0,0 @@ -import ObjC from 'frida-objc-bridge'; - -import * as touch from './touch.js'; - -const activeInstances = {}; -let keepingAlive = false; - -function keepViewsAlive() { - if (keepingAlive) { - return; - } - keepingAlive = true; - - const { willMoveToWindow, originalWillMoveToWindow } = getApi(); - - willMoveToWindow.implementation = ObjC.implement(willMoveToWindow, (handle, selector, window) => { - originalWillMoveToWindow(handle, selector, window); - - const key = handle.toString(); - const nodes = activeInstances[key]; - - if (window.isNull() && nodes !== undefined) { - let view = null; - for (const node of nodes) { - view = node.instance; - node.instance = null; - } - delete activeInstances[key]; - view.release(); - } - }); -} - -export function UINode(view) { - this.instance = view; - keepViewsAlive(); - const activeKey = view.handle.toString(); - if (!(activeKey in activeInstances)) { - view.retain(); - activeInstances[activeKey] = new Set([this]); - } else { - activeInstances[activeKey].add(this); - } - - const api = getApi(); - - if (view.isKindOfClass_(api.UIWindowClass)) { - this.type = 'UIWindow'; - } else if (view.isKindOfClass_(api.UIButtonClass)) { - this.type = 'UIButton'; - } else if (view.isKindOfClass_(api.UILabelClass)) { - this.type = 'UILabel'; - } else if (view.isKindOfClass_(api.UITextFieldClass)) { - this.type = 'UITextField'; - } else if (view.isKindOfClass_(api.UITextViewClass)) { - this.type = 'UITextView'; - } else if (view.isKindOfClass_(api.UIWebViewClass)) { - this.type = 'UIWebView'; - } else if (view.isKindOfClass_(api.WKWebViewClass)) { - this.type = 'WKWebView'; - } else { - this.type = 'UIView'; - } - this.className = view.$className; - this._enabled = null; - this._label = null; - - const children = []; - const subviews = view.subviews(); - const count = subviews.count().valueOf(); - for (let i = 0; i !== count; i++) { - children.push(new UINode(subviews.objectAtIndex_(i))); - } - this.children = children; -} - -UINode.prototype = { - get enabled() { - if (this._enabled === null) { - const instance = this.instance; - if (instance === null) { - return false; - } - if ('enabled' in instance && instance.enabled.returnType === 'bool') { - this._enabled = !!instance.enabled(); - } else { - this._enabled = true; - } - } - return this._enabled; - }, - get label() { - if (this._label === null) { - const instance = this.instance; - if (instance === null) { - return ''; - } - if ('accessibilityLabel' in instance && instance.accessibilityLabel.returnType === 'pointer') { - const accLabel = instance.accessibilityLabel(); - if (accLabel !== null) { - this._label = accLabel.toString(); - } - } - if (this._label === null) { - if ('placeholder' in instance && instance.placeholder.returnType === 'pointer') { - this._label = readStringProperty(instance.placeholder()); - } else if ('text' in instance && instance.text.returnType === 'pointer') { - this._label = readStringProperty(instance.text()); - } else { - this._label = ''; - } - } - } - return this._label; - }, - forEach(fn) { - this._forEach(fn, 0, 0); - }, - _forEach(fn, depth, idx) { - fn(this, depth, idx); - this.children.forEach((child, i) => child._forEach(fn, depth + 1, i)); - }, - find(predicate) { - if (predicate(this)) { - return this; - } - - const children = this.children; - for (let i = 0; i !== children.length; i++) { - const child = children[i].find(predicate); - if (child !== null) { - return child; - } - } - - return null; - }, - setText(text) { - return performOnMainThread(() => { - const instance = this.instance; - if (instance === null) { - throw new Error('View is gone'); - } - - const api = getApi(); - - const delegate = instance.delegate(); - let valid = true; - if (delegate !== null && delegate.respondsToSelector_(api.textField_shouldChangeCharactersInRange_replacementString_)) { - const oldText = instance.text().toString(); - valid = delegate.textField_shouldChangeCharactersInRange_replacementString_(instance, [0, oldText.length], text); - } - - if (!valid) - return; - - instance.becomeFirstResponder(); - instance.setText_(''); - instance.deleteBackward(); - instance.insertText_(text); - instance.resignFirstResponder(); - }); - }, - tap() { - const view = this.instance; - if (view === null) { - return Promise.reject(new Error('View is gone')); - } - return new Promise(function (resolve, reject) { - performOnMainThread(() => { - const [_, [width, height]] = view.frame(); - const x = width / 2; - const y = height / 2; - touch.tap(view, x, y).then(resolve, reject); - }) - }); - }, - dispose() { - const view = this.instance; - if (view === null) { - return; - } - const activeKey = view.handle.toString(); - const nodes = activeInstances[activeKey]; - if (nodes !== undefined) { - nodes.delete(this); - if (nodes.size === 0) { - delete activeInstances[activeKey]; - view.release(); - } - } - this.children.forEach(child => child.dispose()); - } -}; - -function performOnMainThread(action) { - return new Promise(function (resolve, reject) { - const { NSThread } = getApi(); - if (NSThread.isMainThread()) { - performAction(); - } else { - ObjC.schedule(ObjC.mainQueue, performAction); - } - - function performAction() { - try { - const result = action(); - resolve(result); - } catch (e) { - reject(e); - } - } - }); -} - -function readStringProperty(value) { - return (value !== null) ? value.toString() : ''; -} - -let _api = null; - -function getApi() { - if (_api === null) { - const willMoveToWindow = ObjC.classes.UIView['- willMoveToWindow:']; - - _api = { - NSThread: ObjC.classes.NSThread, - UIButtonClass: ObjC.classes.UIButton.class(), - UILabelClass: ObjC.classes.UILabel.class(), - UITextFieldClass: ObjC.classes.UITextField.class(), - UITextViewClass: ObjC.classes.UITextView.class(), - UIWindowClass: ObjC.classes.UIWindow.class(), - UIWebViewClass: ObjC.classes.UIWebView.class(), - WKWebViewClass: ObjC.classes.WKWebView.class(), - textField_shouldChangeCharactersInRange_replacementString_: ObjC.selector('textField:shouldChangeCharactersInRange:replacementString:'), - willMoveToWindow, - originalWillMoveToWindow: willMoveToWindow.implementation, - }; - } - - return _api; -} - diff --git a/lib/node.ts b/lib/node.ts new file mode 100644 index 0000000..90fb388 --- /dev/null +++ b/lib/node.ts @@ -0,0 +1,255 @@ +import ObjC from "frida-objc-bridge"; + +import * as touch from "./touch.js"; + +const activeInstances: Record> = {}; +let keepingAlive = false; + +function keepViewsAlive() { + if (keepingAlive) { + return; + } + keepingAlive = true; + + const { willMoveToWindow, originalWillMoveToWindow } = getApi(); + + willMoveToWindow.implementation = ObjC.implement(willMoveToWindow, (handle, selector, window) => { + originalWillMoveToWindow(handle, selector, window); + + const key = handle.toString(); + const nodes = activeInstances[key]; + + if (window.isNull() && nodes !== undefined) { + let view = null; + for (const node of nodes) { + view = node.instance; + node.instance = null; + } + delete activeInstances[key]; + view!.release(); + } + }); +} +export class UINode { + instance: ObjC.Object | null; + className: string; + private _enabled: boolean | null; + private _label: string | null; + type: "UIWindow" | "UIButton" | "UILabel" | "UITextField" | "UITextView" | "UIWebView" | "WKWebView" | "UIView" + children: UINode[] + + constructor(view: ObjC.Object) { + this.instance = view; + keepViewsAlive(); + const activeKey = view.handle.toString(); + if (!(activeKey in activeInstances)) { + view.retain(); + activeInstances[activeKey] = new Set([this]); + } else { + activeInstances[activeKey].add(this); + } + + const api = getApi(); + + if (view.isKindOfClass_(api.UIWindowClass)) { + this.type = "UIWindow"; + } else if (view.isKindOfClass_(api.UIButtonClass)) { + this.type = "UIButton"; + } else if (view.isKindOfClass_(api.UILabelClass)) { + this.type = "UILabel"; + } else if (view.isKindOfClass_(api.UITextFieldClass)) { + this.type = "UITextField"; + } else if (view.isKindOfClass_(api.UITextViewClass)) { + this.type = "UITextView"; + } else if (view.isKindOfClass_(api.UIWebViewClass)) { + this.type = "UIWebView"; + } else if (view.isKindOfClass_(api.WKWebViewClass)) { + this.type = "WKWebView"; + } else { + this.type = "UIView"; + } + this.className = view.$className; + this._enabled = null; + this._label = null; + + const children = []; + const subviews = view.subviews(); + const count = subviews.count().valueOf(); + for (let i = 0; i !== count; i++) { + children.push(new UINode(subviews.objectAtIndex_(i))); + } + this.children = children; + } + + get enabled() { + if (this._enabled === null) { + const instance = this.instance; + if (instance === null) { + return false; + } + if ("enabled" in instance && instance.enabled.returnType === "bool") { + this._enabled = !!instance.enabled(); + } else { + this._enabled = true; + } + } + return this._enabled; + } + + get label() { + if (this._label === null) { + const instance = this.instance; + if (instance === null) { + return ""; + } + if ("accessibilityLabel" in instance && instance.accessibilityLabel.returnType === "pointer") { + const accLabel = instance.accessibilityLabel(); + if (accLabel !== null) { + this._label = accLabel.toString(); + } + } + if (this._label === null) { + if ("placeholder" in instance && instance.placeholder.returnType === "pointer") { + this._label = readStringProperty(instance.placeholder()); + } else if ("text" in instance && instance.text.returnType === "pointer") { + this._label = readStringProperty(instance.text()); + } else { + this._label = ""; + } + } + } + return this._label; + } + + forEach(fn: (x: UINode) => void) { + this._forEach(fn, 0, 0); + } + + private _forEach(fn: (x: UINode, depth: number, idx: number) => void, depth: number, idx: number) { + fn(this, depth, idx); + this.children.forEach((child, i) => child._forEach(fn, depth + 1, i)); + } + + find(predicate : (x: UINode) => boolean): UINode | null { + if (predicate(this)) { + return this; + } + + const children = this.children; + for (let i = 0; i !== children.length; i++) { + const child = children[i].find(predicate); + if (child !== null) { + return child; + } + } + + return null; + } + + setText(text: string): Promise { + return performOnMainThread(() => { + const instance = this.instance; + if (instance === null) { + throw new Error("View is gone"); + } + + const api = getApi(); + + const delegate = instance.delegate(); + let valid = true; + if (delegate !== null && delegate.respondsToSelector_(api.textField_shouldChangeCharactersInRange_replacementString_)) { + const oldText = instance.text().toString(); + valid = delegate.textField_shouldChangeCharactersInRange_replacementString_(instance, [0, oldText.length], text); + } + + if (!valid) + return; + + instance.becomeFirstResponder(); + instance.setText_(""); + instance.deleteBackward(); + instance.insertText_(text); + instance.resignFirstResponder(); + }); + } + + tap() { + const view = this.instance; + if (view === null) { + return Promise.reject(new Error("View is gone")); + } + return new Promise(function (resolve, reject) { + performOnMainThread(() => { + const [_, [width, height]] = view.frame(); + const x = width / 2; + const y = height / 2; + touch.tap(view, x, y).then(resolve, reject); + }) + }); + } + + dispose() { + const view = this.instance; + if (view === null) { + return; + } + const activeKey = view.handle.toString(); + const nodes = activeInstances[activeKey]; + if (nodes !== undefined) { + nodes.delete(this); + if (nodes.size === 0) { + delete activeInstances[activeKey]; + view.release(); + } + } + this.children.forEach(child => child.dispose()); + } + } + +function performOnMainThread(action: () => T): Promise { + return new Promise(function (resolve, reject) { + const { NSThread } = getApi(); + if (NSThread.isMainThread()) { + performAction(); + } else { + ObjC.schedule(ObjC.mainQueue, performAction); + } + + function performAction() { + try { + const result = action(); + resolve(result); + } catch (e) { + reject(e); + } + } + }); +} + +function readStringProperty(value: string | null) { + return (value !== null) ? value.toString() : ""; +} + +let _api: any = null; + +function getApi() { + if (_api === null) { + const willMoveToWindow = ObjC.classes.UIView["- willMoveToWindow:"]; + + _api = { + NSThread: ObjC.classes.NSThread, + UIButtonClass: ObjC.classes.UIButton.class(), + UILabelClass: ObjC.classes.UILabel.class(), + UITextFieldClass: ObjC.classes.UITextField.class(), + UITextViewClass: ObjC.classes.UITextView.class(), + UIWindowClass: ObjC.classes.UIWindow.class(), + UIWebViewClass: ObjC.classes.UIWebView.class(), + WKWebViewClass: ObjC.classes.WKWebView.class(), + textField_shouldChangeCharactersInRange_replacementString_: ObjC.selector("textField:shouldChangeCharactersInRange:replacementString:"), + willMoveToWindow, + originalWillMoveToWindow: willMoveToWindow.implementation, + }; + } + + return _api; +} diff --git a/lib/touch.js b/lib/touch.ts similarity index 66% rename from lib/touch.js rename to lib/touch.ts index 6cb6d24..eabc636 100644 --- a/lib/touch.js +++ b/lib/touch.ts @@ -1,4 +1,4 @@ -import ObjC from 'frida-objc-bridge'; +import ObjC from "frida-objc-bridge"; const UITouchPhaseBegan = 0; const UITouchPhaseMoved = 1; @@ -9,8 +9,9 @@ const UITouchPhaseCancelled = 4; const UITOUCH_FLAG_IS_FIRST_TOUCH_FOR_VIEW = 1; const UITOUCH_FLAG_IS_TAP = 2; -const CGFloat = (Process.pointerSize === 4) ? 'float' : 'double'; -const CGPoint = [CGFloat, CGFloat]; +type CGFloatType = "float" | "double"; +const CGFloat = (Process.pointerSize === 4) ? "float" : "double"; +const CGPoint: [CGFloatType, CGFloatType] = [CGFloat, CGFloat]; const CGSize = [CGFloat, CGFloat]; const kIOHIDDigitizerEventRange = 0x00000001; @@ -19,11 +20,11 @@ const kIOHIDDigitizerEventPosition = 0x00000004; const Injector = ObjC.registerClass({ methods: { - '- init': function () { + "- init": function () { const self = this.super.init(); const api = getApi(); if (self !== null) { - const displayLink = api.CADisplayLink.displayLinkWithTarget_selector_(self, ObjC.selector('dispatchTouch:')); + const displayLink = api.CADisplayLink.displayLinkWithTarget_selector_(self, ObjC.selector("dispatchTouch:")); ObjC.bind(self, { displayLink: displayLink, window: null, @@ -37,13 +38,13 @@ const Injector = ObjC.registerClass({ } return self; }, - '- dealloc': function () { + "- dealloc": function () { ObjC.unbind(this.self); this.super.dealloc(); }, - '- dispatchTouch:': { - retType: 'void', - argTypes: ['object'], + "- dispatchTouch:": { + retType: "void", + argTypes: ["object"], implementation: function (sender) { const priv = this.data; @@ -68,18 +69,18 @@ const Injector = ObjC.registerClass({ phase = UITouchPhaseBegan; touch.setPhase_(phase); touch.setWindow_(priv.window); - touch['- _setLocationInWindow:resetPrevious:'].call(touch, point, true); + touch["- _setLocationInWindow:resetPrevious:"].call(touch, point, true); touch.setView_(priv.window.hitTest_withEvent_(point, NULL)); setIsFirstTouchForView(touch, true); } else { touch = priv.touch; if (isLastTouch) { - touch['- _setLocationInWindow:resetPrevious:'].call(touch, priv.previous, false); + touch["- _setLocationInWindow:resetPrevious:"].call(touch, priv.previous, false); phase = UITouchPhaseEnded; point = priv.previous; } else { - touch['- _setLocationInWindow:resetPrevious:'].call(touch, point, false); + touch["- _setLocationInWindow:resetPrevious:"].call(touch, point, false); phase = api.CGPointEqualToPoint(point, priv.previous) ? UITouchPhaseStationary : UITouchPhaseMoved; } @@ -87,8 +88,8 @@ const Injector = ObjC.registerClass({ } const app = api.UIApplication.sharedApplication(); - const event = app['- _touchesEvent'].call(app); - event['- _clearTouches'].call(event); + const event = app["- _touchesEvent"].call(app); + event["- _clearTouches"].call(event); const absoluteTime = api.mach_absolute_time(); @@ -119,12 +120,12 @@ const Injector = ObjC.registerClass({ isRangeAndTouch, 0); - if ('- _setHidEvent:' in touch) { - touch['- _setHidEvent:'].call(touch, hidEvent); + if ("- _setHidEvent:" in touch) { + touch["- _setHidEvent:"].call(touch, hidEvent); } - event['- _setHIDEvent:'].call(event, hidEvent); - event['- _addTouch:forDelayedDelivery:'].call(event, touch, false); + event["- _setHIDEvent:"].call(event, hidEvent); + event["- _addTouch:forDelayedDelivery:"].call(event, touch, false); const pool = api.NSAutoreleasePool.alloc().init(); try { @@ -145,11 +146,11 @@ const Injector = ObjC.registerClass({ } }); -export function tap(view, x, y) { +export function tap(view: ObjC.Object, x: number, y: number): Promise { return new Promise(function (resolve, reject) { const window = view.window(); if (window === null) { - reject(new Error('Cannot tap on NULL window')); + reject(new Error("Cannot tap on NULL window")); return; } const { CGPointZero } = getApi(); @@ -166,36 +167,36 @@ export function tap(view, x, y) { }); } -function setIsTap (touch, isTap) { - const flags = touch.$ivars['_touchFlags']; +function setIsTap (touch: ObjC.Object, isTap : boolean) { + const flags = touch.$ivars["_touchFlags"]; const newFlags = [...flags]; if (isTap) { newFlags[0] |= UITOUCH_FLAG_IS_TAP; } else { newFlags[0] &= ~UITOUCH_FLAG_IS_TAP; } - touch.$ivars['_touchFlags'] = newFlags; + touch.$ivars["_touchFlags"] = newFlags; } -function setIsFirstTouchForView (touch, isFirst) { - const flags = touch.$ivars['_touchFlags']; +function setIsFirstTouchForView (touch: ObjC.Object, isFirst: boolean) { + const flags = touch.$ivars["_touchFlags"]; const newFlags = [...flags]; if (isFirst) { newFlags[0] |= UITOUCH_FLAG_IS_FIRST_TOUCH_FOR_VIEW; } else { newFlags[0] &= ~UITOUCH_FLAG_IS_FIRST_TOUCH_FOR_VIEW; } - touch.$ivars['_touchFlags'] = newFlags; + touch.$ivars["_touchFlags"] = newFlags; } -let _api = null; +let _api: any = null; function getApi () { if (_api === null) { - const coreGraphics = Process.getModuleByName('CoreGraphics'); - const coreFoundation = Process.getModuleByName('CoreFoundation'); - const foundation = Process.getModuleByName('Foundation'); - const libSystem = Process.getModuleByName('libSystem.B.dylib'); + const coreGraphics = Process.getModuleByName("CoreGraphics"); + const coreFoundation = Process.getModuleByName("CoreFoundation"); + const foundation = Process.getModuleByName("Foundation"); + const libSystem = Process.getModuleByName("libSystem.B.dylib"); _api = { CADisplayLink: ObjC.classes.CADisplayLink, @@ -204,45 +205,45 @@ function getApi () { UIApplication: ObjC.classes.UIApplication, UITouch: ObjC.classes.UITouch, CGPointEqualToPoint: new NativeFunction( - coreGraphics.getExportByName('CGPointEqualToPoint'), - 'uint8', + coreGraphics.getExportByName("CGPointEqualToPoint"), + "uint8", [CGPoint, CGPoint] ), - CGPointZero: coreGraphics.getExportByName('CGPointZero').readPointer(), + CGPointZero: coreGraphics.getExportByName("CGPointZero").readPointer(), IOHIDEventCreateDigitizerFingerEvent: new NativeFunction( - Module.getGlobalExportByName('IOHIDEventCreateDigitizerFingerEvent'), - 'pointer', + Module.getGlobalExportByName("IOHIDEventCreateDigitizerFingerEvent"), + "pointer", [ - 'pointer', // allocator - ['uint32', 'uint32'], // timestamp - 'uint32', // index - 'uint32', // identity - 'uint32', // eventMask + "pointer", // allocator + ["uint32", "uint32"], // timestamp + "uint32", // index + "uint32", // identity + "uint32", // eventMask CGFloat, // x CGFloat, // y CGFloat, // z CGFloat, // tipPressure CGFloat, // twist - 'uint8', // range - 'uint8', // touch - 'uint32' // options + "uint8", // range + "uint8", // touch + "uint32" // options ] ), - kCFAllocatorDefault: coreFoundation.getExportByName('kCFAllocatorDefault').readPointer(), - NSRunLoopCommonModes: foundation.getExportByName('NSRunLoopCommonModes').readPointer(), + kCFAllocatorDefault: coreFoundation.getExportByName("kCFAllocatorDefault").readPointer(), + NSRunLoopCommonModes: foundation.getExportByName("NSRunLoopCommonModes").readPointer(), CFGetSystemUptime: new NativeFunction( - coreFoundation.getExportByName('CFGetSystemUptime'), - 'double', + coreFoundation.getExportByName("CFGetSystemUptime"), + "double", [] ), CFRelease: new NativeFunction( - coreFoundation.getExportByName('CFRelease'), - 'void', - ['pointer'] + coreFoundation.getExportByName("CFRelease"), + "void", + ["pointer"] ), mach_absolute_time: new NativeFunction( - libSystem.getExportByName('mach_absolute_time'), - 'uint64', + libSystem.getExportByName("mach_absolute_time"), + "uint64", [] ), }; diff --git a/package-lock.json b/package-lock.json index ad9ceb5..66c4d57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,15 +8,57 @@ "name": "frida-uikit", "version": "6.0.0", "license": "MIT", - "devDependencies": { + "dependencies": { "frida-objc-bridge": "^8.0.5" + }, + "devDependencies": { + "@types/frida-gum": "^19.0.0", + "@types/node": "^24.0.8", + "typescript": "^5.8.3" + } + }, + "node_modules/@types/frida-gum": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.0.0.tgz", + "integrity": "sha512-qViK1KwvPEAkajwSUv0wpZvZLiX8xO4JFFCh8dfYZfzSyOEfoE0KliCP/nJSKIKG6N3hXnPowrvPufjs5fLKAA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.0.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.8.tgz", + "integrity": "sha512-WytNrFSgWO/esSH9NbpWUfTMGQwCGIKfCmNlmFDNiI5gGhgMmEA+V1AEvKLeBNvvtBnailJtkrEa2OIISwrVAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" } }, "node_modules/frida-objc-bridge": { "version": "8.0.5", "resolved": "https://registry.npmjs.org/frida-objc-bridge/-/frida-objc-bridge-8.0.5.tgz", - "integrity": "sha512-0xnL0VgxSQXgLj3S33S0kdAvLrCHSCIZb9HVraEgEyKnf9wOcu0KZ9fI5xzx0e0y+ry/g2Vl3yw9j3dsn92Ffg==", - "dev": true + "integrity": "sha512-0xnL0VgxSQXgLj3S33S0kdAvLrCHSCIZb9HVraEgEyKnf9wOcu0KZ9fI5xzx0e0y+ry/g2Vl3yw9j3dsn92Ffg==" + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" } } } diff --git a/package.json b/package.json index 7d23192..c836784 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,16 @@ "keywords": [ "frida-gum" ], - "main": "index.js", + "main": "dist/index.js", "type": "module", + "types": "dist/**/*.d.ts", "files": [ - "/index.js", - "/lib/**/*.js" + "dist/index.js", + "dist/lib/**/*.js" ], + "scripts": { + "build": "tsc" + }, "repository": { "type": "git", "url": "git+https://github.com/nowsecure/frida-uikit.git" @@ -22,5 +26,10 @@ "homepage": "https://github.com/nowsecure/frida-uikit#readme", "dependencies": { "frida-objc-bridge": "^8.0.5" + }, + "devDependencies": { + "@types/frida-gum": "^19.0.0", + "@types/node": "^24.0.8", + "typescript": "^5.8.3" } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..dc859ef --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "target": "ES2020", + "lib": ["ES2020"], + "module": "Node16", + "moduleResolution": "Node16", + "esModuleInterop": true, + "resolveJsonModule": true, + "declaration": true, + "strict": true, + "skipLibCheck": true, + "outDir": "./dist" + }, + "include": [ + "index.ts", + "lib/*.ts", + ] +}