Skip to content

Better type signatures for callable and patchers #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions typescript-packages/client/src/millennium-api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** Returnable IPC types */
type IPC_types = string | number | boolean;
type IPCType = string | number | boolean | void;

/*
Global Millennium API for developers.
Expand Down Expand Up @@ -40,7 +40,11 @@ type Millennium = {
* pass
* ```
*/
declare const callable: <Args extends any[] = [], T = IPC_types>(route: string) => (...args: Args) => Promise<T>;
declare const callable: <
// Ideally this would be `Params extends Record<...>` but for backwards compatibility we keep a tuple type
Params extends [params: Record<string, IPCType>] | [] = [],
Return extends IPCType = IPCType
>(route: string) => (...params: Params) => Promise<Return>;

const m_private_context: any = undefined;
export const pluginSelf = m_private_context;
Expand Down
83 changes: 63 additions & 20 deletions typescript-packages/client/src/utils/patcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,52 +6,85 @@ export interface PatchOptions {
singleShot?: boolean;
}

// Unused at this point, exported for backwards compatibility
export type GenericPatchHandler = (args: any[], ret?: any) => any;

export interface Patch {
original: Function;
property: string;
object: any;
patchedFunction: any;
export interface Patch<
Object extends Record<PropertyKey, any> = Record<PropertyKey, any>,
Property extends keyof Object = keyof Object,
Target extends Object[Property] = Object[Property]
> {
object: Object;
property: Property;
original: Target;

patchedFunction: Target;
hasUnpatched: boolean;
handler: GenericPatchHandler;
handler: ((args: Parameters<Target>, ret: ReturnType<Target>) => any) | ((args: Parameters<Target>) => any);

unpatch: () => void;
unpatch(): void;
}

// let patches = new Set<Patch>();

export function beforePatch(object: any, property: string, handler: (args: any[]) => any, options: PatchOptions = {}): Patch {
export function beforePatch<
Object extends Record<PropertyKey, any>,
Property extends keyof Object,
Target extends Object[Property],
>(
object: Object,
property: Property,
handler: (args: Parameters<Target>) => void,
options: PatchOptions = {},
): Patch<Object, Property, Target> {
const orig = object[property];
object[property] = function (...args: any[]) {
object[property] = (function (this: any, ...args: Parameters<Target>) {
handler.call(this, args);
const ret = patch.original.call(this, ...args);
if (options.singleShot) {
patch.unpatch();
}
return ret;
};
}) as any;
const patch = processPatch(object, property, handler, object[property], orig);
return patch;
}

export function afterPatch(object: any, property: string, handler: (args: any[], ret: any) => any, options: PatchOptions = {}): Patch {
export function afterPatch<
Object extends Record<PropertyKey, any>,
Property extends keyof Object,
Target extends Object[Property]
>(
object: Object,
property: Property,
handler: (args: Parameters<Target>, ret: ReturnType<Target>) => ReturnType<Target>,
options: PatchOptions = {},
): Patch<Object, Property, Target> {
const orig = object[property];
object[property] = function (...args: any[]) {
object[property] = (function (this: any, ...args: Parameters<Target>) {
let ret = patch.original.call(this, ...args);
ret = handler.call(this, args, ret);
if (options.singleShot) {
patch.unpatch();
}
return ret;
};
return ret
}) as any;
const patch = processPatch(object, property, handler, object[property], orig);
return patch;
}

export function replacePatch(object: any, property: string, handler: (args: any[]) => any, options: PatchOptions = {}): Patch {
export function replacePatch<
Object extends Record<PropertyKey, any>,
Property extends keyof Object,
Target extends Object[Property]
>(
object: Object,
property: Property,
handler: (args: Parameters<Target>) => ReturnType<Target>,
options: PatchOptions = {},
): Patch<Object, Property, Target> {
const orig = object[property];
object[property] = function (...args: any[]) {
object[property] = (function (this: any, ...args: Parameters<Target>) {
const ret = handler.call(this, args);
// console.debug('[Patcher] replacePatch', patch);

Expand All @@ -60,12 +93,22 @@ export function replacePatch(object: any, property: string, handler: (args: any[
patch.unpatch();
}
return ret;
};
}) as any;
const patch = processPatch(object, property, handler, object[property], orig);
return patch;
}

function processPatch(object: any, property: any, handler: GenericPatchHandler, patchedFunction: any, original: any): Patch {
function processPatch<
Object extends Record<PropertyKey, any>,
Property extends keyof Object,
Target extends Object[Property],
>(
object: Object,
property: Property,
handler: ((args: Parameters<Target>, ret: ReturnType<Target>) => any) | ((args: Parameters<Target>) => any),
patchedFunction: Target,
original: Target,
): Patch<Object, Property, Target> {
// Assign all props of original function to new one
Object.assign(object[property], original);
// Allow toString webpack filters to continue to work
Expand All @@ -78,14 +121,14 @@ function processPatch(object: any, property: any, handler: GenericPatchHandler,
});

// Build a Patch object of this patch
const patch: Patch = {
const patch = {
object,
property,
handler,
patchedFunction,
original,
hasUnpatched: false,
unpatch: () => unpatch(patch),
unpatch: () => unpatch(patch as any),
};

object[property].__millenniumPatch = patch;
Expand Down