@decky/ui 4.3.1 → 4.5.0
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/dist/class-mapper.d.ts +4 -2
- package/dist/class-mapper.js +7 -6
- package/dist/utils/index.d.ts +3 -2
- package/dist/utils/index.js +3 -2
- package/dist/utils/react/fc.d.ts +6 -0
- package/dist/utils/react/fc.js +74 -0
- package/dist/utils/{react.d.ts → react/react.d.ts} +2 -0
- package/dist/utils/{react.js → react/react.js} +14 -3
- package/dist/utils/{react-patching.d.ts → react/treepatcher.d.ts} +1 -1
- package/dist/utils/{react-patching.js → react/treepatcher.js} +2 -2
- package/dist/webpack.d.ts +4 -2
- package/dist/webpack.js +23 -13
- package/package.json +1 -1
- package/src/class-mapper.ts +9 -6
- package/src/utils/index.ts +3 -2
- package/src/utils/react/fc.ts +109 -0
- package/src/utils/{react.ts → react/react.ts} +18 -3
- package/src/utils/{react-patching.ts → react/treepatcher.ts} +2 -2
- package/src/webpack.ts +29 -14
package/dist/class-mapper.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { ModuleID } from './webpack';
|
|
1
2
|
export interface ClassModule {
|
|
2
3
|
[name: string]: string;
|
|
3
4
|
}
|
|
4
|
-
export declare const
|
|
5
|
-
export declare
|
|
5
|
+
export declare const classModuleMap: Map<ModuleID, ClassModule>;
|
|
6
|
+
export declare const classMap: IterableIterator<ClassModule>;
|
|
7
|
+
export declare function findClass(id: string, name: string): string | void;
|
|
6
8
|
export declare function findClassModule(filter: (module: any) => boolean): ClassModule | void;
|
|
7
9
|
export declare function unminifyClass(minifiedClass: string): string | void;
|
package/dist/class-mapper.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export const
|
|
1
|
+
import { createModuleMapping } from './webpack';
|
|
2
|
+
export const classModuleMap = createModuleMapping((m) => {
|
|
3
3
|
if (typeof m == 'object' && !m.__esModule) {
|
|
4
4
|
const keys = Object.keys(m);
|
|
5
5
|
if (keys.length == 1 && m.version)
|
|
@@ -10,14 +10,15 @@ export const classMap = findAllModules((m) => {
|
|
|
10
10
|
}
|
|
11
11
|
return false;
|
|
12
12
|
});
|
|
13
|
-
export
|
|
14
|
-
|
|
13
|
+
export const classMap = classModuleMap.values();
|
|
14
|
+
export function findClass(id, name) {
|
|
15
|
+
return classModuleMap.get(id)?.[name];
|
|
15
16
|
}
|
|
16
17
|
export function findClassModule(filter) {
|
|
17
|
-
return
|
|
18
|
+
return [...classModuleMap.values()].find((m) => filter(m));
|
|
18
19
|
}
|
|
19
20
|
export function unminifyClass(minifiedClass) {
|
|
20
|
-
for (let m of
|
|
21
|
+
for (let m of classModuleMap.values()) {
|
|
21
22
|
for (let className of Object.keys(m)) {
|
|
22
23
|
if (m[className] == minifiedClass)
|
|
23
24
|
return className;
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export * from './patcher';
|
|
2
|
-
export * from './react';
|
|
3
|
-
export * from './react-patching';
|
|
4
2
|
export * from './static-classes';
|
|
3
|
+
export * from './react/react';
|
|
4
|
+
export * from './react/fc';
|
|
5
|
+
export * from './react/treepatcher';
|
|
5
6
|
declare global {
|
|
6
7
|
var FocusNavController: any;
|
|
7
8
|
var GamepadNavTree: any;
|
package/dist/utils/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export * from './patcher';
|
|
2
|
-
export * from './react';
|
|
3
|
-
export * from './react-patching';
|
|
4
2
|
export * from './static-classes';
|
|
3
|
+
export * from './react/react';
|
|
4
|
+
export * from './react/fc';
|
|
5
|
+
export * from './react/treepatcher';
|
|
5
6
|
export function joinClassNames(...classes) {
|
|
6
7
|
return classes.join(' ');
|
|
7
8
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createElement } from 'react';
|
|
2
|
+
import { applyHookStubs, removeHookStubs } from './react';
|
|
3
|
+
import Logger from '../../logger';
|
|
4
|
+
let loggingEnabled = false;
|
|
5
|
+
export function setFCTrampolineLoggingEnabled(value = true) { loggingEnabled = value; }
|
|
6
|
+
;
|
|
7
|
+
let logger = new Logger('FCTrampoline');
|
|
8
|
+
export function injectFCTrampoline(component, customHooks) {
|
|
9
|
+
const newComponent = function (...args) {
|
|
10
|
+
loggingEnabled && logger.debug("new component rendering with props", args);
|
|
11
|
+
return component.apply(this, args);
|
|
12
|
+
};
|
|
13
|
+
const userComponent = { component: newComponent };
|
|
14
|
+
component.prototype.render = function (...args) {
|
|
15
|
+
loggingEnabled && logger.debug("rendering trampoline", args, this);
|
|
16
|
+
return createElement(userComponent.component, this.props, this.props.children);
|
|
17
|
+
};
|
|
18
|
+
component.prototype.isReactComponent = true;
|
|
19
|
+
let stubsApplied = false;
|
|
20
|
+
let oldCreateElement = window.SP_REACT.createElement;
|
|
21
|
+
const applyStubsIfNeeded = () => {
|
|
22
|
+
if (!stubsApplied) {
|
|
23
|
+
loggingEnabled && logger.debug("applied stubs");
|
|
24
|
+
stubsApplied = true;
|
|
25
|
+
applyHookStubs(customHooks);
|
|
26
|
+
window.SP_REACT.createElement = () => {
|
|
27
|
+
loggingEnabled && logger.debug("createElement hook called");
|
|
28
|
+
return Object.create(component.prototype);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const removeStubsIfNeeded = () => {
|
|
33
|
+
if (stubsApplied) {
|
|
34
|
+
loggingEnabled && logger.debug("removed stubs");
|
|
35
|
+
stubsApplied = false;
|
|
36
|
+
removeHookStubs();
|
|
37
|
+
window.SP_REACT.createElement = oldCreateElement;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
Object.defineProperty(component, "contextType", {
|
|
41
|
+
configurable: true,
|
|
42
|
+
get: function () {
|
|
43
|
+
loggingEnabled && logger.debug("get contexttype", this, stubsApplied);
|
|
44
|
+
applyStubsIfNeeded();
|
|
45
|
+
return this._contextType;
|
|
46
|
+
},
|
|
47
|
+
set: function (value) {
|
|
48
|
+
this._contextType = value;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
Object.defineProperty(component, "getDerivedStateFromProps", {
|
|
52
|
+
configurable: true,
|
|
53
|
+
get: function () {
|
|
54
|
+
loggingEnabled && logger.debug("get getDerivedStateFromProps", this, stubsApplied);
|
|
55
|
+
removeStubsIfNeeded();
|
|
56
|
+
return this._getDerivedStateFromProps;
|
|
57
|
+
},
|
|
58
|
+
set: function (value) {
|
|
59
|
+
this._getDerivedStateFromProps = value;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
Object.defineProperty(component.prototype, "updater", {
|
|
63
|
+
configurable: true,
|
|
64
|
+
get: function () {
|
|
65
|
+
return this._updater;
|
|
66
|
+
},
|
|
67
|
+
set: function (value) {
|
|
68
|
+
loggingEnabled && logger.debug("set updater", this, value, stubsApplied);
|
|
69
|
+
removeStubsIfNeeded();
|
|
70
|
+
return this._updater = value;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
return userComponent;
|
|
74
|
+
}
|
|
@@ -5,6 +5,8 @@ declare global {
|
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
7
|
export declare function createPropListRegex(propList: string[], fromStart?: boolean): RegExp;
|
|
8
|
+
export declare function applyHookStubs(customHooks?: any): any;
|
|
9
|
+
export declare function removeHookStubs(): void;
|
|
8
10
|
export declare function fakeRenderComponent(fun: Function, customHooks?: any): any;
|
|
9
11
|
export declare function wrapReactType(node: any, prop?: any): any;
|
|
10
12
|
export declare function wrapReactClass(node: any, prop?: any): any;
|
|
@@ -12,10 +12,11 @@ export function createPropListRegex(propList, fromStart = true) {
|
|
|
12
12
|
});
|
|
13
13
|
return new RegExp(regexString);
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
let oldHooks = {};
|
|
16
|
+
export function applyHookStubs(customHooks = {}) {
|
|
16
17
|
const hooks = window.SP_REACT.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher
|
|
17
18
|
.current;
|
|
18
|
-
|
|
19
|
+
oldHooks = {
|
|
19
20
|
useContext: hooks.useContext,
|
|
20
21
|
useCallback: hooks.useCallback,
|
|
21
22
|
useLayoutEffect: hooks.useLayoutEffect,
|
|
@@ -35,8 +36,18 @@ export function fakeRenderComponent(fun, customHooks = {}) {
|
|
|
35
36
|
return [val, (n) => (val = n)];
|
|
36
37
|
};
|
|
37
38
|
Object.assign(hooks, customHooks);
|
|
38
|
-
|
|
39
|
+
return hooks;
|
|
40
|
+
}
|
|
41
|
+
export function removeHookStubs() {
|
|
42
|
+
const hooks = window.SP_REACT.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher
|
|
43
|
+
.current;
|
|
39
44
|
Object.assign(hooks, oldHooks);
|
|
45
|
+
oldHooks = {};
|
|
46
|
+
}
|
|
47
|
+
export function fakeRenderComponent(fun, customHooks) {
|
|
48
|
+
const hooks = applyHookStubs(customHooks);
|
|
49
|
+
const res = fun(hooks);
|
|
50
|
+
removeHookStubs();
|
|
40
51
|
return res;
|
|
41
52
|
}
|
|
42
53
|
export function wrapReactType(node, prop = 'type') {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import Logger from '
|
|
2
|
-
import { afterPatch } from '
|
|
1
|
+
import Logger from '../../logger';
|
|
2
|
+
import { afterPatch } from '../patcher';
|
|
3
3
|
import { wrapReactClass, wrapReactType } from './react';
|
|
4
4
|
let loggingEnabled = false;
|
|
5
5
|
let perfLoggingEnabled = false;
|
package/dist/webpack.d.ts
CHANGED
|
@@ -3,18 +3,20 @@ declare global {
|
|
|
3
3
|
webpackChunksteamui: any;
|
|
4
4
|
}
|
|
5
5
|
}
|
|
6
|
+
export type ModuleID = string;
|
|
6
7
|
export type Module = any;
|
|
7
8
|
export type Export = any;
|
|
8
9
|
type FilterFn = (module: any) => boolean;
|
|
9
10
|
type ExportFilterFn = (moduleExport: any, exportName?: any) => boolean;
|
|
10
11
|
type FindFn = (module: any) => any;
|
|
11
|
-
export declare let modules: any
|
|
12
|
+
export declare let modules: Map<string, any>;
|
|
12
13
|
export declare const findModule: (filter: FilterFn) => any;
|
|
13
|
-
export declare const findModuleDetailsByExport: (filter: ExportFilterFn, minExports?: number) => [module: Module | undefined, moduleExport: any, exportName: any];
|
|
14
|
+
export declare const findModuleDetailsByExport: (filter: ExportFilterFn, minExports?: number) => [module: Module | undefined, moduleExport: any, exportName: any, moduleID: string | undefined];
|
|
14
15
|
export declare const findModuleByExport: (filter: ExportFilterFn, minExports?: number) => any;
|
|
15
16
|
export declare const findModuleExport: (filter: ExportFilterFn, minExports?: number) => any;
|
|
16
17
|
export declare const findModuleChild: (filter: FindFn) => any;
|
|
17
18
|
export declare const findAllModules: (filter: FilterFn) => any[];
|
|
19
|
+
export declare const createModuleMapping: (filter: FilterFn) => Map<string, any>;
|
|
18
20
|
export declare const CommonUIModule: any;
|
|
19
21
|
export declare const IconsModule: any;
|
|
20
22
|
export declare const ReactRouter: any;
|
package/dist/webpack.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import Logger from './logger';
|
|
2
2
|
const logger = new Logger('Webpack');
|
|
3
|
-
export let modules =
|
|
3
|
+
export let modules = new Map();
|
|
4
4
|
function initModuleCache() {
|
|
5
5
|
const startTime = performance.now();
|
|
6
6
|
logger.group('Webpack Module Init');
|
|
7
|
-
const id =
|
|
7
|
+
const id = Symbol("@decky/ui");
|
|
8
8
|
let webpackRequire;
|
|
9
9
|
window.webpackChunksteamui.push([
|
|
10
10
|
[id],
|
|
@@ -14,22 +14,22 @@ function initModuleCache() {
|
|
|
14
14
|
},
|
|
15
15
|
]);
|
|
16
16
|
logger.log('Initializing all modules. Errors here likely do not matter, as they are usually just failing module side effects.');
|
|
17
|
-
for (let
|
|
17
|
+
for (let id of Object.keys(webpackRequire.m)) {
|
|
18
18
|
try {
|
|
19
|
-
const module = webpackRequire(
|
|
19
|
+
const module = webpackRequire(id);
|
|
20
20
|
if (module) {
|
|
21
|
-
modules.
|
|
21
|
+
modules.set(id, module);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
catch (e) {
|
|
25
|
-
logger.debug('Ignoring require error for module',
|
|
25
|
+
logger.debug('Ignoring require error for module', id, e);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
logger.groupEnd(`Modules initialized in ${performance.now() - startTime}ms...`);
|
|
29
29
|
}
|
|
30
30
|
initModuleCache();
|
|
31
31
|
export const findModule = (filter) => {
|
|
32
|
-
for (const m of modules) {
|
|
32
|
+
for (const m of modules.values()) {
|
|
33
33
|
if (m.default && filter(m.default))
|
|
34
34
|
return m.default;
|
|
35
35
|
if (filter(m))
|
|
@@ -37,7 +37,7 @@ export const findModule = (filter) => {
|
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
export const findModuleDetailsByExport = (filter, minExports) => {
|
|
40
|
-
for (const m of modules) {
|
|
40
|
+
for (const [id, m] of modules) {
|
|
41
41
|
if (!m)
|
|
42
42
|
continue;
|
|
43
43
|
for (const mod of [m.default, m]) {
|
|
@@ -49,7 +49,7 @@ export const findModuleDetailsByExport = (filter, minExports) => {
|
|
|
49
49
|
if (mod?.[exportName]) {
|
|
50
50
|
const filterRes = filter(mod[exportName], exportName);
|
|
51
51
|
if (filterRes) {
|
|
52
|
-
return [mod, mod[exportName], exportName];
|
|
52
|
+
return [mod, mod[exportName], exportName, id];
|
|
53
53
|
}
|
|
54
54
|
else {
|
|
55
55
|
continue;
|
|
@@ -58,7 +58,7 @@ export const findModuleDetailsByExport = (filter, minExports) => {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
return [undefined, undefined, undefined];
|
|
61
|
+
return [undefined, undefined, undefined, undefined];
|
|
62
62
|
};
|
|
63
63
|
export const findModuleByExport = (filter, minExports) => {
|
|
64
64
|
return findModuleDetailsByExport(filter, minExports)?.[0];
|
|
@@ -67,7 +67,7 @@ export const findModuleExport = (filter, minExports) => {
|
|
|
67
67
|
return findModuleDetailsByExport(filter, minExports)?.[1];
|
|
68
68
|
};
|
|
69
69
|
export const findModuleChild = (filter) => {
|
|
70
|
-
for (const m of modules) {
|
|
70
|
+
for (const m of modules.values()) {
|
|
71
71
|
for (const mod of [m.default, m]) {
|
|
72
72
|
const filterRes = filter(mod);
|
|
73
73
|
if (filterRes) {
|
|
@@ -81,7 +81,7 @@ export const findModuleChild = (filter) => {
|
|
|
81
81
|
};
|
|
82
82
|
export const findAllModules = (filter) => {
|
|
83
83
|
const out = [];
|
|
84
|
-
for (const m of modules) {
|
|
84
|
+
for (const m of modules.values()) {
|
|
85
85
|
if (m.default && filter(m.default))
|
|
86
86
|
out.push(m.default);
|
|
87
87
|
if (filter(m))
|
|
@@ -89,7 +89,17 @@ export const findAllModules = (filter) => {
|
|
|
89
89
|
}
|
|
90
90
|
return out;
|
|
91
91
|
};
|
|
92
|
-
export const
|
|
92
|
+
export const createModuleMapping = (filter) => {
|
|
93
|
+
const mapping = new Map();
|
|
94
|
+
for (const [id, m] of modules) {
|
|
95
|
+
if (m.default && filter(m.default))
|
|
96
|
+
mapping.set(id, m.default);
|
|
97
|
+
if (filter(m))
|
|
98
|
+
mapping.set(id, m);
|
|
99
|
+
}
|
|
100
|
+
return mapping;
|
|
101
|
+
};
|
|
102
|
+
export const CommonUIModule = findModule((m) => {
|
|
93
103
|
if (typeof m !== 'object')
|
|
94
104
|
return false;
|
|
95
105
|
for (let prop in m) {
|
package/package.json
CHANGED
package/src/class-mapper.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Module,
|
|
1
|
+
import { Module, ModuleID, createModuleMapping } from './webpack';
|
|
2
2
|
|
|
3
3
|
export interface ClassModule {
|
|
4
4
|
[name: string]: string;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
export const
|
|
7
|
+
export const classModuleMap: Map<ModuleID, ClassModule> = createModuleMapping((m: Module) => {
|
|
8
8
|
if (typeof m == 'object' && !m.__esModule) {
|
|
9
9
|
const keys = Object.keys(m);
|
|
10
10
|
// special case some libraries
|
|
@@ -17,16 +17,19 @@ export const classMap: ClassModule[] = findAllModules((m: Module) => {
|
|
|
17
17
|
return false;
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
export
|
|
21
|
-
|
|
20
|
+
export const classMap = classModuleMap.values();
|
|
21
|
+
|
|
22
|
+
export function findClass(id: string, name: string): string | void {
|
|
23
|
+
return classModuleMap.get(id)?.[name];
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export function findClassModule(filter: (module: any) => boolean): ClassModule | void {
|
|
25
|
-
|
|
27
|
+
// TODO optimize
|
|
28
|
+
return [...classModuleMap.values()].find((m) => filter(m));
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export function unminifyClass(minifiedClass: string): string | void {
|
|
29
|
-
for (let m of
|
|
32
|
+
for (let m of classModuleMap.values()) {
|
|
30
33
|
for (let className of Object.keys(m)) {
|
|
31
34
|
if (m[className] == minifiedClass) return className;
|
|
32
35
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export * from './patcher';
|
|
2
|
-
export * from './react';
|
|
3
|
-
export * from './react-patching';
|
|
4
2
|
export * from './static-classes';
|
|
3
|
+
export * from './react/react';
|
|
4
|
+
export * from './react/fc';
|
|
5
|
+
export * from './react/treepatcher';
|
|
5
6
|
|
|
6
7
|
declare global {
|
|
7
8
|
var FocusNavController: any;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Utilities for patching function components
|
|
2
|
+
import { createElement, type FC } from 'react';
|
|
3
|
+
import { applyHookStubs, removeHookStubs } from './react';
|
|
4
|
+
import Logger from '../../logger';
|
|
5
|
+
|
|
6
|
+
export interface FCTrampoline {
|
|
7
|
+
component: FC
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let loggingEnabled = false;
|
|
11
|
+
|
|
12
|
+
export function setFCTrampolineLoggingEnabled(value: boolean = true) { loggingEnabled = value };
|
|
13
|
+
|
|
14
|
+
let logger = new Logger('FCTrampoline');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Directly hooks a function component from its reference, redirecting it to a user-patchable wrapper in its returned object.
|
|
18
|
+
* This only works if the original component when called directly returns either nothing, null, or another child element.
|
|
19
|
+
*
|
|
20
|
+
* This works by tricking react into thinking it's a class component by cleverly working around its class component checks,
|
|
21
|
+
* keeping the unmodified function component intact as a mostly working constructor (as it is impossible to direcly modify a function),
|
|
22
|
+
* stubbing out hooks to prevent errors by detecting setter/getter triggers that occur direcly before/after the class component is instantiated by react,
|
|
23
|
+
* and creating a fake class component render method to trampoline out into your own handler.
|
|
24
|
+
*
|
|
25
|
+
* Due to the nature of this method of hooking a component, please only use this where it is *absolutely necessary.*
|
|
26
|
+
* Incorrect hook stubs can cause major instability, be careful when writing them. Refer to fakeRenderComponent for the hook stub implementation.
|
|
27
|
+
* Make sure your hook stubs can handle all the cases they could possibly need to within the component you are injecting into.
|
|
28
|
+
* You do not need to worry about its children, as they are never called due to the first instance never actually rendering.
|
|
29
|
+
*/
|
|
30
|
+
export function injectFCTrampoline(component: FC, customHooks?: any): FCTrampoline {
|
|
31
|
+
// It needs to be wrapped so React doesn't infinitely call the fake class render method.
|
|
32
|
+
const newComponent = function (this: any, ...args: any) {
|
|
33
|
+
loggingEnabled && logger.debug("new component rendering with props", args);
|
|
34
|
+
return component.apply(this, args);
|
|
35
|
+
}
|
|
36
|
+
const userComponent = { component: newComponent };
|
|
37
|
+
// Create a fake class component render method
|
|
38
|
+
component.prototype.render = function (...args: any[]) {
|
|
39
|
+
loggingEnabled && logger.debug("rendering trampoline", args, this);
|
|
40
|
+
// Pass through rendering via creating the component as a child so React can use function component logic instead of class component logic (setting up the hooks)
|
|
41
|
+
return createElement(userComponent.component, this.props, this.props.children);
|
|
42
|
+
};
|
|
43
|
+
component.prototype.isReactComponent = true;
|
|
44
|
+
let stubsApplied = false;
|
|
45
|
+
let oldCreateElement = window.SP_REACT.createElement;
|
|
46
|
+
|
|
47
|
+
const applyStubsIfNeeded = () => {
|
|
48
|
+
if (!stubsApplied) {
|
|
49
|
+
loggingEnabled && logger.debug("applied stubs");
|
|
50
|
+
stubsApplied = true;
|
|
51
|
+
applyHookStubs(customHooks)
|
|
52
|
+
// we have to redirect this to return an object with component's prototype as a constructor returning a value overrides its prototype
|
|
53
|
+
window.SP_REACT.createElement = () => {
|
|
54
|
+
loggingEnabled && logger.debug("createElement hook called");
|
|
55
|
+
return Object.create(component.prototype);
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const removeStubsIfNeeded = () => {
|
|
61
|
+
if (stubsApplied) {
|
|
62
|
+
loggingEnabled && logger.debug("removed stubs");
|
|
63
|
+
stubsApplied = false;
|
|
64
|
+
removeHookStubs();
|
|
65
|
+
window.SP_REACT.createElement = oldCreateElement;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Accessed two times, once directly before class instantiation, and again in some extra logic we don't need to worry about that we hanlde below just in case.
|
|
70
|
+
Object.defineProperty(component, "contextType", {
|
|
71
|
+
configurable: true,
|
|
72
|
+
get: function () {
|
|
73
|
+
loggingEnabled && logger.debug("get contexttype", this, stubsApplied);
|
|
74
|
+
applyStubsIfNeeded();
|
|
75
|
+
return this._contextType;
|
|
76
|
+
},
|
|
77
|
+
set: function (value) {
|
|
78
|
+
this._contextType = value;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Undoes the second contextType access we can't detect shortly before render before it's able to cause any damage
|
|
83
|
+
Object.defineProperty(component, "getDerivedStateFromProps", {
|
|
84
|
+
configurable: true,
|
|
85
|
+
get: function () {
|
|
86
|
+
loggingEnabled && logger.debug("get getDerivedStateFromProps", this, stubsApplied);
|
|
87
|
+
removeStubsIfNeeded();
|
|
88
|
+
return this._getDerivedStateFromProps;
|
|
89
|
+
},
|
|
90
|
+
set: function (value) {
|
|
91
|
+
this._getDerivedStateFromProps = value;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Set directly after class is instantiated
|
|
96
|
+
Object.defineProperty(component.prototype, "updater", {
|
|
97
|
+
configurable: true,
|
|
98
|
+
get: function () {
|
|
99
|
+
return this._updater;
|
|
100
|
+
},
|
|
101
|
+
set: function (value) {
|
|
102
|
+
loggingEnabled && logger.debug("set updater", this, value, stubsApplied);
|
|
103
|
+
removeStubsIfNeeded();
|
|
104
|
+
return this._updater = value;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return userComponent;
|
|
109
|
+
}
|
|
@@ -30,13 +30,15 @@ export function createPropListRegex(propList: string[], fromStart: boolean = tru
|
|
|
30
30
|
return new RegExp(regexString);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
let oldHooks = {};
|
|
34
|
+
|
|
35
|
+
export function applyHookStubs(customHooks: any = {}): any {
|
|
34
36
|
const hooks = (window.SP_REACT as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher
|
|
35
37
|
.current;
|
|
36
38
|
|
|
37
39
|
// TODO: add more hooks
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
oldHooks = {
|
|
40
42
|
useContext: hooks.useContext,
|
|
41
43
|
useCallback: hooks.useCallback,
|
|
42
44
|
useLayoutEffect: hooks.useLayoutEffect,
|
|
@@ -60,9 +62,22 @@ export function fakeRenderComponent(fun: Function, customHooks: any = {}): any {
|
|
|
60
62
|
|
|
61
63
|
Object.assign(hooks, customHooks);
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
return hooks;
|
|
66
|
+
}
|
|
64
67
|
|
|
68
|
+
export function removeHookStubs() {
|
|
69
|
+
const hooks = (window.SP_REACT as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher
|
|
70
|
+
.current;
|
|
65
71
|
Object.assign(hooks, oldHooks);
|
|
72
|
+
oldHooks = {};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function fakeRenderComponent(fun: Function, customHooks?: any): any {
|
|
76
|
+
const hooks = applyHookStubs(customHooks);
|
|
77
|
+
|
|
78
|
+
const res = fun(hooks); // TODO why'd we do this?
|
|
79
|
+
|
|
80
|
+
removeHookStubs();
|
|
66
81
|
|
|
67
82
|
return res;
|
|
68
83
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import Logger from '
|
|
2
|
-
import { GenericPatchHandler, afterPatch } from '
|
|
1
|
+
import Logger from '../../logger';
|
|
2
|
+
import { GenericPatchHandler, afterPatch } from '../patcher';
|
|
3
3
|
import { wrapReactClass, wrapReactType } from './react';
|
|
4
4
|
|
|
5
5
|
// TODO max size limit? could implement as a class extending map perhaps
|
package/src/webpack.ts
CHANGED
|
@@ -9,20 +9,21 @@ declare global {
|
|
|
9
9
|
const logger = new Logger('Webpack');
|
|
10
10
|
|
|
11
11
|
// In most case an object with getters for each property. Look for the first call to r.d in the module, usually near or at the top.
|
|
12
|
+
export type ModuleID = string; // number string
|
|
12
13
|
export type Module = any;
|
|
13
14
|
export type Export = any;
|
|
14
15
|
type FilterFn = (module: any) => boolean;
|
|
15
16
|
type ExportFilterFn = (moduleExport: any, exportName?: any) => boolean;
|
|
16
17
|
type FindFn = (module: any) => any;
|
|
17
18
|
|
|
18
|
-
export let modules
|
|
19
|
+
export let modules = new Map<ModuleID, Module>();
|
|
19
20
|
|
|
20
21
|
function initModuleCache() {
|
|
21
22
|
const startTime = performance.now();
|
|
22
23
|
logger.group('Webpack Module Init');
|
|
23
24
|
// Webpack 5, currently on beta
|
|
24
25
|
// Generate a fake module ID
|
|
25
|
-
const id =
|
|
26
|
+
const id = Symbol("@decky/ui");
|
|
26
27
|
let webpackRequire!: ((id: any) => Module) & { m: object };
|
|
27
28
|
// Insert our module in a new chunk.
|
|
28
29
|
// The module will then be called with webpack's internal require function as its first argument
|
|
@@ -39,14 +40,14 @@ function initModuleCache() {
|
|
|
39
40
|
);
|
|
40
41
|
|
|
41
42
|
// Loop over every module ID
|
|
42
|
-
for (let
|
|
43
|
+
for (let id of Object.keys(webpackRequire.m)) {
|
|
43
44
|
try {
|
|
44
|
-
const module = webpackRequire(
|
|
45
|
+
const module = webpackRequire(id);
|
|
45
46
|
if (module) {
|
|
46
|
-
modules.
|
|
47
|
+
modules.set(id, module);
|
|
47
48
|
}
|
|
48
49
|
} catch (e) {
|
|
49
|
-
logger.debug('Ignoring require error for module',
|
|
50
|
+
logger.debug('Ignoring require error for module', id, e);
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -56,7 +57,7 @@ function initModuleCache() {
|
|
|
56
57
|
initModuleCache();
|
|
57
58
|
|
|
58
59
|
export const findModule = (filter: FilterFn) => {
|
|
59
|
-
for (const m of modules) {
|
|
60
|
+
for (const m of modules.values()) {
|
|
60
61
|
if (m.default && filter(m.default)) return m.default;
|
|
61
62
|
if (filter(m)) return m;
|
|
62
63
|
}
|
|
@@ -65,8 +66,8 @@ export const findModule = (filter: FilterFn) => {
|
|
|
65
66
|
export const findModuleDetailsByExport = (
|
|
66
67
|
filter: ExportFilterFn,
|
|
67
68
|
minExports?: number,
|
|
68
|
-
): [module: Module | undefined, moduleExport: any, exportName: any] => {
|
|
69
|
-
for (const m of modules) {
|
|
69
|
+
): [module: Module | undefined, moduleExport: any, exportName: any, moduleID: string | undefined] => {
|
|
70
|
+
for (const [id, m] of modules) {
|
|
70
71
|
if (!m) continue;
|
|
71
72
|
for (const mod of [m.default, m]) {
|
|
72
73
|
if (typeof mod !== 'object') continue;
|
|
@@ -75,7 +76,7 @@ export const findModuleDetailsByExport = (
|
|
|
75
76
|
if (mod?.[exportName]) {
|
|
76
77
|
const filterRes = filter(mod[exportName], exportName);
|
|
77
78
|
if (filterRes) {
|
|
78
|
-
return [mod, mod[exportName], exportName];
|
|
79
|
+
return [mod, mod[exportName], exportName, id];
|
|
79
80
|
} else {
|
|
80
81
|
continue;
|
|
81
82
|
}
|
|
@@ -83,7 +84,7 @@ export const findModuleDetailsByExport = (
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
|
-
return [undefined, undefined, undefined];
|
|
87
|
+
return [undefined, undefined, undefined, undefined];
|
|
87
88
|
};
|
|
88
89
|
|
|
89
90
|
export const findModuleByExport = (filter: ExportFilterFn, minExports?: number) => {
|
|
@@ -98,7 +99,7 @@ export const findModuleExport = (filter: ExportFilterFn, minExports?: number) =>
|
|
|
98
99
|
* @deprecated use findModuleExport instead
|
|
99
100
|
*/
|
|
100
101
|
export const findModuleChild = (filter: FindFn) => {
|
|
101
|
-
for (const m of modules) {
|
|
102
|
+
for (const m of modules.values()) {
|
|
102
103
|
for (const mod of [m.default, m]) {
|
|
103
104
|
const filterRes = filter(mod);
|
|
104
105
|
if (filterRes) {
|
|
@@ -110,10 +111,13 @@ export const findModuleChild = (filter: FindFn) => {
|
|
|
110
111
|
}
|
|
111
112
|
};
|
|
112
113
|
|
|
114
|
+
/**
|
|
115
|
+
* @deprecated use createModuleMapping instead
|
|
116
|
+
*/
|
|
113
117
|
export const findAllModules = (filter: FilterFn) => {
|
|
114
118
|
const out = [];
|
|
115
119
|
|
|
116
|
-
for (const m of modules) {
|
|
120
|
+
for (const m of modules.values()) {
|
|
117
121
|
if (m.default && filter(m.default)) out.push(m.default);
|
|
118
122
|
if (filter(m)) out.push(m);
|
|
119
123
|
}
|
|
@@ -121,7 +125,18 @@ export const findAllModules = (filter: FilterFn) => {
|
|
|
121
125
|
return out;
|
|
122
126
|
};
|
|
123
127
|
|
|
124
|
-
export const
|
|
128
|
+
export const createModuleMapping = (filter: FilterFn) => {
|
|
129
|
+
const mapping = new Map<ModuleID, Module>();
|
|
130
|
+
|
|
131
|
+
for (const [id, m] of modules) {
|
|
132
|
+
if (m.default && filter(m.default)) mapping.set(id, m.default);
|
|
133
|
+
if (filter(m)) mapping.set(id, m);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return mapping;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const CommonUIModule = findModule((m: Module) => {
|
|
125
140
|
if (typeof m !== 'object') return false;
|
|
126
141
|
for (let prop in m) {
|
|
127
142
|
if (m[prop]?.contextType?._currentValue && Object.keys(m).length > 60) return true;
|