Skip to content

Commit 01f693f

Browse files
shdwmtrclaude
andcommitted
fix: remove Promise constructor anti-pattern that caused infinite hangs
Any error thrown inside a `new Promise(async (resolve, reject) => ...)` async executor leaves the outer Promise permanently pending — there is no automatic link between the async function's rejection and the outer Promise's reject callback. In GetPluginData this meant a bad GraphQL response (e.g. responseJson.data undefined) would silently freeze inflightFetch forever, blocking all subsequent /plugins requests. Rewrote all four files as plain async functions so errors propagate normally. Also added an explicit data-presence check in GetPluginData so GitHub GraphQL error responses throw immediately. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent fa7fd76 commit 01f693f

4 files changed

Lines changed: 63 additions & 102 deletions

File tree

apps/www/src/app/api/Firebase.ts

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,17 @@ if (global.firebaseAdmin && global.Database && global.StorageBucket) {
2727
export { firebaseAdmin, Database, StorageBucket };
2828

2929
export const Firebase = {
30-
Get: () => {
31-
return new Promise<FirebaseFirestore.QuerySnapshot>((resolve) => {
32-
Database.collection('V2')
33-
.get()
34-
.then((snap) => {
35-
resolve(snap);
36-
});
37-
});
30+
Get: async (): Promise<FirebaseFirestore.QuerySnapshot> => {
31+
return Database.collection('V2').get();
3832
},
39-
FromID: (id) => {
40-
return new Promise<FirebaseFirestore.DocumentSnapshot>((resolve, reject) => {
41-
Database.collection('V2')
42-
.doc(id)
43-
.get()
44-
.then((snap) => {
45-
if (!snap.exists) {
46-
reject("document wasn't found.");
47-
}
48-
resolve(snap!);
49-
});
50-
});
33+
FromID: async (id): Promise<FirebaseFirestore.DocumentSnapshot> => {
34+
const snap = await Database.collection('V2').doc(id).get();
35+
if (!snap.exists) {
36+
throw new Error("document wasn't found.");
37+
}
38+
return snap;
5139
},
52-
FromRepository: (owner, repo) => {
53-
return new Promise<FirebaseFirestore.QuerySnapshot>(async (resolve, reject) => {
54-
resolve(await Database.collection('V2').where('github.owner', '==', owner).where('github.repo', '==', repo).get());
55-
});
40+
FromRepository: async (owner, repo): Promise<FirebaseFirestore.QuerySnapshot> => {
41+
return Database.collection('V2').where('github.owner', '==', owner).where('github.repo', '==', repo).get();
5642
},
5743
};

apps/www/src/app/api/v1/plugins/GetPluginData.ts

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Firebase } from '../../Firebase';
21
import { GithubGraphQL } from '../../v2/GraphQLInterop';
32

43
const FormatSize = (kilobytes) => {
@@ -41,9 +40,8 @@ export interface PluginDataTable {
4140
metadata: { id: string; commitId: string }[];
4241
}
4342

44-
const GetPluginData = (pluginList) => {
45-
return new Promise<PluginDataProps[]>(async (resolve, reject) => {
46-
const query = `
43+
const GetPluginData = async (pluginList): Promise<PluginDataProps[]> => {
44+
const query = `
4745
query {
4846
${pluginList
4947
.map(
@@ -88,33 +86,33 @@ const GetPluginData = (pluginList) => {
8886
}
8987
`;
9088

91-
const responseJson = await GithubGraphQL.Post(query);
89+
const responseJson = await GithubGraphQL.Post(query);
9290

93-
const jsonResponse = Object.values(responseJson.data)
94-
.map((repository) => repository)
95-
.map((repo: any): PluginDataProps | null => {
96-
try {
97-
const pluginJson = JSON.parse(repo.pluginJson.text);
98-
return {
99-
pluginJson: pluginJson,
100-
usesBackend: pluginJson?.useBackend === true || pluginJson?.useBackend === undefined,
101-
readme: repo?.pluginReadme?.text || repo.readme.text,
102-
stargazerCount: repo.stargazerCount,
103-
diskUsage: FormatSize(repo.diskUsage),
104-
commitDate: repo.commit.committedDate,
105-
commitMessage: repo.commit.message,
106-
repoName: repo.repoName,
107-
repoOwner: repo.repoOwner.login,
108-
id: repo.commitId.oid,
109-
};
110-
} catch {
111-
return null;
112-
}
113-
})
114-
.filter((item): item is PluginDataProps => item !== null);
91+
if (!responseJson.data) {
92+
throw new Error(`GitHub GraphQL error: ${JSON.stringify(responseJson)}`);
93+
}
11594

116-
resolve(jsonResponse);
117-
});
95+
return Object.values(responseJson.data)
96+
.map((repo: any): PluginDataProps | null => {
97+
try {
98+
const pluginJson = JSON.parse(repo.pluginJson.text);
99+
return {
100+
pluginJson: pluginJson,
101+
usesBackend: pluginJson?.useBackend === true || pluginJson?.useBackend === undefined,
102+
readme: repo?.pluginReadme?.text || repo.readme.text,
103+
stargazerCount: repo.stargazerCount,
104+
diskUsage: FormatSize(repo.diskUsage),
105+
commitDate: repo.commit.committedDate,
106+
commitMessage: repo.commit.message,
107+
repoName: repo.repoName,
108+
repoOwner: repo.repoOwner.login,
109+
id: repo.commitId.oid,
110+
};
111+
} catch {
112+
return null;
113+
}
114+
})
115+
.filter((item): item is PluginDataProps => item !== null);
118116
};
119117

120118
export { GetPluginData };

apps/www/src/app/api/v1/plugins/GetPluginList.ts

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,26 @@
11
import querystring from 'querystring';
22

33
const RetrievePluginList = async () => {
4-
return new Promise((resolve, reject) => {
5-
fetch('https://api.github.com/repos/SteamClientHomebrew/PluginDatabase/contents/plugins', {
6-
headers: {
7-
Authorization: process.env.GITHUB_TOKEN ? `Bearer ${process.env.GITHUB_TOKEN}` : process.env.BEARER!,
8-
'Content-Type': 'application/json',
9-
},
10-
next: { revalidate: 300 },
11-
})
12-
.then((text) => text.json())
13-
.then((data) => {
14-
if (!Array.isArray(data)) {
15-
reject(new Error(`GitHub API error: ${JSON.stringify(data)}`));
16-
return;
17-
}
18-
resolve(
19-
data.map((plugin) => {
20-
const pluginLinks = plugin._links;
21-
const branch = querystring.parse(pluginLinks.self.split('?')[1]).ref;
4+
const res = await fetch('https://api.github.com/repos/SteamClientHomebrew/PluginDatabase/contents/plugins', {
5+
headers: {
6+
Authorization: process.env.GITHUB_TOKEN ? `Bearer ${process.env.GITHUB_TOKEN}` : process.env.BEARER!,
7+
'Content-Type': 'application/json',
8+
},
9+
next: { revalidate: 300 },
10+
});
11+
const data = await res.json();
2212

23-
const owner = pluginLinks.html.split('/')[3];
24-
const repo = pluginLinks.html.split('/')[4];
25-
const commit = pluginLinks.html.split('/')[6];
13+
if (!Array.isArray(data)) {
14+
throw new Error(`GitHub API error: ${JSON.stringify(data)}`);
15+
}
2616

27-
return {
28-
owner: owner,
29-
repo: repo,
30-
branch: branch,
31-
commit: commit,
32-
};
33-
}),
34-
);
35-
})
36-
.catch((err) => {
37-
reject(err);
38-
});
17+
return data.map((plugin) => {
18+
const pluginLinks = plugin._links;
19+
const branch = querystring.parse(pluginLinks.self.split('?')[1]).ref;
20+
const owner = pluginLinks.html.split('/')[3];
21+
const repo = pluginLinks.html.split('/')[4];
22+
const commit = pluginLinks.html.split('/')[6];
23+
return { owner, repo, branch, commit };
3924
});
4025
};
4126

apps/www/src/app/api/v1/plugins/GetPluginMetadata.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,15 @@ interface PluginMetadata {
33
id: string;
44
}
55

6-
const GetPluginMetadata = async () => {
7-
return new Promise<PluginMetadata[]>((resolve, reject) => {
8-
fetch('https://raw.githubusercontent.com/shdwmtr/plugdb/refs/heads/main/metadata.json', {
9-
headers: {
10-
Authorization: process.env.BEARER!,
11-
'Content-Type': 'application/json',
12-
},
13-
next: { revalidate: 300 },
14-
})
15-
.then((text) => text.json())
16-
.then((data) => {
17-
resolve(data);
18-
})
19-
.catch((err) => {
20-
reject(err);
21-
});
6+
const GetPluginMetadata = async (): Promise<PluginMetadata[]> => {
7+
const res = await fetch('https://raw.githubusercontent.com/shdwmtr/plugdb/refs/heads/main/metadata.json', {
8+
headers: {
9+
Authorization: process.env.BEARER!,
10+
'Content-Type': 'application/json',
11+
},
12+
next: { revalidate: 300 },
2213
});
14+
return res.json();
2315
};
2416

2517
export { GetPluginMetadata };

0 commit comments

Comments
 (0)