Compare commits

..

6 Commits

Author SHA1 Message Date
semantic-release-bot
8c6043b5a7 chore(release): 4.7.2 [CI SKIP] 2024-09-16 23:30:08 +00:00
AAGaming
3aa07dc9ce fix(utils): fix potential race condition in findSP 2024-09-16 19:29:38 -04:00
semantic-release-bot
3f335b3583 chore(release): 4.7.1 [CI SKIP] 2024-08-08 18:18:52 +00:00
AAGaming
4c97097757 fix(utils/react): fix potential race condition in injectFCTrampoline 2024-08-08 14:18:19 -04:00
AAGaming
1ee43e86d6 chore(utils/react): get rid of ts-expect-error 2024-08-03 13:50:33 -04:00
AAGaming
db6ab9c448 chore(docs): add note to CloseSideMenus 2024-07-28 18:51:08 -04:00
7 changed files with 63 additions and 22 deletions

View File

@@ -4,7 +4,7 @@ on:
pull_request: pull_request:
push: push:
branches: branches:
- v4-dev - main
jobs: jobs:
release: release:

View File

@@ -1,3 +1,17 @@
## [4.7.2](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v4.7.1...v4.7.2) (2024-09-16)
### Bug Fixes
* **utils:** fix potential race condition in findSP ([3aa07dc](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/3aa07dc9ce798ff8d1148424ee9e8a8bf2ba78c6))
## [4.7.1](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v4.7.0...v4.7.1) (2024-08-08)
### Bug Fixes
* **utils/react:** fix potential race condition in injectFCTrampoline ([4c97097](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/4c97097757919580a380b70785e6c161de6b03cc))
# [4.7.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v4.6.0...v4.7.0) (2024-07-28) # [4.7.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v4.6.0...v4.7.0) (2024-07-28)

View File

@@ -1,6 +1,6 @@
{ {
"name": "@decky/ui", "name": "@decky/ui",
"version": "4.7.0", "version": "4.7.2",
"description": "A library for interacting with the Steam frontend in Decky plugins and elsewhere.", "description": "A library for interacting with the Steam frontend in Decky plugins and elsewhere.",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",

View File

@@ -126,6 +126,7 @@ export interface Navigation {
OpenQuickAccessMenu(quickAccessTab?: QuickAccessTab): void; OpenQuickAccessMenu(quickAccessTab?: QuickAccessTab): void;
OpenMainMenu(): void; OpenMainMenu(): void;
OpenPowerMenu(unknown?: any): void; OpenPowerMenu(unknown?: any): void;
/** if calling this to perform navigation, call it after Navigate to prevent a race condition in desktop Big Picture mode that hides the overlay unintentionally */
CloseSideMenus(): void; CloseSideMenus(): void;
} }

View File

@@ -25,7 +25,7 @@ export function findSP(): Window {
if (document.title == 'SP') return window; if (document.title == 'SP') return window;
// new (SP as popup) // new (SP as popup)
const navTrees = getGamepadNavigationTrees(); const navTrees = getGamepadNavigationTrees();
return navTrees?.find((x: any) => x.m_ID == 'root_1_').Root.Element.ownerDocument.defaultView; return navTrees?.find((x: any) => x.m_ID == 'root_1_')?.Root?.Element?.ownerDocument?.defaultView;
} }
/** /**
@@ -40,6 +40,6 @@ export function getFocusNavController(): any {
*/ */
export function getGamepadNavigationTrees(): any { export function getGamepadNavigationTrees(): any {
const focusNav = getFocusNavController(); const focusNav = getFocusNavController();
const context = focusNav.m_ActiveContext || focusNav.m_LastActiveContext; const context = focusNav?.m_ActiveContext || focusNav?.m_LastActiveContext;
return context?.m_rgGamepadNavigationTrees; return context?.m_rgGamepadNavigationTrees;
} }

View File

@@ -52,6 +52,7 @@ export function injectFCTrampoline(component: FC, customHooks?: any): FCTrampoli
// we have to redirect this to return an object with component's prototype as a constructor returning a value overrides its prototype // we have to redirect this to return an object with component's prototype as a constructor returning a value overrides its prototype
window.SP_REACT.createElement = () => { window.SP_REACT.createElement = () => {
loggingEnabled && logger.debug("createElement hook called"); loggingEnabled && logger.debug("createElement hook called");
loggingEnabled && console.trace("createElement trace");
return Object.create(component.prototype); return Object.create(component.prototype);
}; };
} }
@@ -66,12 +67,16 @@ export function injectFCTrampoline(component: FC, customHooks?: any): FCTrampoli
} }
} }
let renderHookStep = 0;
// 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. // 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.
Object.defineProperty(component, "contextType", { Object.defineProperty(component, "contextType", {
configurable: true, configurable: true,
get: function () { get: function () {
loggingEnabled && logger.debug("get contexttype", this, stubsApplied); loggingEnabled && logger.debug("get contexttype", this, stubsApplied, renderHookStep);
applyStubsIfNeeded(); loggingEnabled && console.trace("contextType trace");
if (renderHookStep == 0) renderHookStep = 1;
else if (renderHookStep == 3) renderHookStep = 4;
return this._contextType; return this._contextType;
}, },
set: function (value) { set: function (value) {
@@ -79,16 +84,20 @@ export function injectFCTrampoline(component: FC, customHooks?: any): FCTrampoli
} }
}); });
// Undoes the second contextType access we can't detect shortly before render before it's able to cause any damage // Always accessed directly after contextType for the path we want to catch.
Object.defineProperty(component, "getDerivedStateFromProps", { Object.defineProperty(component, "contextTypes", {
configurable: true, configurable: true,
get: function () { get: function () {
loggingEnabled && logger.debug("get getDerivedStateFromProps", this, stubsApplied); loggingEnabled && logger.debug("get contexttypes", this, stubsApplied, renderHookStep);
removeStubsIfNeeded(); loggingEnabled && console.trace("contextTypes trace");
return this._getDerivedStateFromProps; if (renderHookStep == 1) {
renderHookStep = 2;
applyStubsIfNeeded();
};
return this._contextTypes;
}, },
set: function (value) { set: function (value) {
this._getDerivedStateFromProps = value; this._contextTypes = value;
} }
}); });
@@ -99,11 +108,32 @@ export function injectFCTrampoline(component: FC, customHooks?: any): FCTrampoli
return this._updater; return this._updater;
}, },
set: function (value) { set: function (value) {
loggingEnabled && logger.debug("set updater", this, value, stubsApplied); loggingEnabled && logger.debug("set updater", this, value, stubsApplied, renderHookStep);
removeStubsIfNeeded(); loggingEnabled && console.trace("updater trace");
if (renderHookStep == 2) {
renderHookStep = 0;
removeStubsIfNeeded();
}
return this._updater = value; return this._updater = value;
} }
}); });
// Prevents the second contextType+contextTypes access from leaving its hooks around
Object.defineProperty(component, "getDerivedStateFromProps", {
configurable: true,
get: function () {
loggingEnabled && logger.debug("get getDerivedStateFromProps", this, stubsApplied, renderHookStep);
loggingEnabled && console.trace("getDerivedStateFromProps trace");
if (renderHookStep == 2) {
renderHookStep = 0;
removeStubsIfNeeded();
}
return this._getDerivedStateFromProps;
},
set: function (value) {
this._getDerivedStateFromProps = value;
}
});
return userComponent; return userComponent;
} }

View File

@@ -104,19 +104,15 @@ export function wrapReactClass(node: any, prop: any = 'type') {
export function getReactRoot(o: HTMLElement | Element | Node) { export function getReactRoot(o: HTMLElement | Element | Node) {
return ( return (
// @ts-expect-error 7053 (o as any)[Object.keys(o).find((k) => k.startsWith('__reactContainer$')) as string] ||
o[Object.keys(o).find((k) => k.startsWith('__reactContainer$')) as string] || (o as any)['_reactRootContainer']?._internalRoot?.current
// @ts-expect-error 7053
o['_reactRootContainer']?._internalRoot?.current
); );
} }
export function getReactInstance(o: HTMLElement | Element | Node) { export function getReactInstance(o: HTMLElement | Element | Node) {
return ( return (
// @ts-expect-error 7053 (o as any)[Object.keys(o).find((k) => k.startsWith('__reactFiber')) as string] ||
o[Object.keys(o).find((k) => k.startsWith('__reactFiber')) as string] || (o as any)[Object.keys(o).find((k) => k.startsWith('__reactInternalInstance')) as string]
// @ts-expect-error 7053
o[Object.keys(o).find((k) => k.startsWith('__reactInternalInstance')) as string]
); );
} }