Skip to content

Commit 30977b5

Browse files
committed
fix: destroy the discord client when login fails
A client whose login failed was never adopted, so close() could not release it. It is now destroyed on the failure path; cleanup errors are swallowed so they cannot delay or replace the original login error.
1 parent e7b7541 commit 30977b5

2 files changed

Lines changed: 29 additions & 1 deletion

File tree

‎src/discord-rpc.js‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,13 @@ export function createDiscordRpcTransport({ clientId, createClient } = {}) {
2525
if (!next || typeof next.login !== "function" || typeof next.setActivity !== "function" || typeof next.clearActivity !== "function") {
2626
throw new TypeError("RPC client: expected login, setActivity and clearActivity functions");
2727
}
28-
await next.login({ clientId });
28+
try { await next.login({ clientId }); }
29+
catch (error) {
30+
// A failed client was never adopted, so close() cannot release it.
31+
// Cleanup must not delay or replace the original login failure.
32+
try { Promise.resolve(next.destroy?.()).catch(() => {}); } catch {}
33+
throw error;
34+
}
2935
// A close during createClient/login must not be undone by this late
3036
// arrival: destroy the new client instead of adopting it.
3137
if (epoch !== generation) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import test from 'node:test';
2+
import assert from 'node:assert/strict';
3+
import {createDiscordRpcTransport} from '../src/discord-rpc.js';
4+
for(const mode of ['sync','async','throwing','rejecting','pending']){
5+
test(`failed RPC login destroys discarded client (${mode}) and preserves error`,async()=>{
6+
let destroyed=0,created=0,timer;
7+
const error=new Error('fixture login failed');
8+
const factory=()=>({login:async()=>{throw error;},setActivity:async()=>{},clearActivity:async()=>{},destroy:()=>{destroyed++;if(mode==='async')return Promise.resolve();if(mode==='throwing')throw new Error('cleanup');if(mode==='rejecting')return Promise.reject(new Error('cleanup'));if(mode==='pending')return new Promise(()=>{});}});
9+
const transport=createDiscordRpcTransport({clientId:'123456789012345678',createClient:async()=>{created++;return factory();}});
10+
try{
11+
for(let i=1;i<=2;i++){
12+
await assert.rejects(Promise.race([transport.connect(),new Promise((_,reject)=>{timer=setTimeout(()=>reject(new Error('cleanup blocked')),100);})]),e=>e===error);
13+
clearTimeout(timer);assert.equal(destroyed,i);assert.equal(created,i);assert.equal(transport.connected,false);
14+
}
15+
await transport.close();assert.equal(destroyed,2);
16+
}finally{clearTimeout(timer);}
17+
});
18+
}
19+
test('failed RPC login without destroy preserves error',async()=>{
20+
const error=new Error('fixture');const transport=createDiscordRpcTransport({clientId:'123456789012345678',createClient:async()=>({login:async()=>{throw error;},setActivity:async()=>{},clearActivity:async()=>{}})});
21+
await assert.rejects(transport.connect(),e=>e===error);
22+
});

0 commit comments

Comments
 (0)