Skip to content
Merged
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: 7 additions & 1 deletion src/discord-rpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ export function createDiscordRpcTransport({ clientId, createClient } = {}) {
if (!next || typeof next.login !== "function" || typeof next.setActivity !== "function" || typeof next.clearActivity !== "function") {
throw new TypeError("RPC client: expected login, setActivity and clearActivity functions");
}
await next.login({ clientId });
try { await next.login({ clientId }); }
catch (error) {
// A failed client was never adopted, so close() cannot release it.
// Cleanup must not delay or replace the original login failure.
try { Promise.resolve(next.destroy?.()).catch(() => {}); } catch {}
throw error;
}
// A close during createClient/login must not be undone by this late
// arrival: destroy the new client instead of adopting it.
if (epoch !== generation) {
Expand Down
22 changes: 22 additions & 0 deletions test/discord-rpc-login-cleanup.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {createDiscordRpcTransport} from '../src/discord-rpc.js';
for(const mode of ['sync','async','throwing','rejecting','pending']){
test(`failed RPC login destroys discarded client (${mode}) and preserves error`,async()=>{
let destroyed=0,created=0,timer;
const error=new Error('fixture login failed');
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(()=>{});}});
const transport=createDiscordRpcTransport({clientId:'123456789012345678',createClient:async()=>{created++;return factory();}});
try{
for(let i=1;i<=2;i++){
await assert.rejects(Promise.race([transport.connect(),new Promise((_,reject)=>{timer=setTimeout(()=>reject(new Error('cleanup blocked')),100);})]),e=>e===error);
clearTimeout(timer);assert.equal(destroyed,i);assert.equal(created,i);assert.equal(transport.connected,false);
}
await transport.close();assert.equal(destroyed,2);
}finally{clearTimeout(timer);}
});
}
test('failed RPC login without destroy preserves error',async()=>{
const error=new Error('fixture');const transport=createDiscordRpcTransport({clientId:'123456789012345678',createClient:async()=>({login:async()=>{throw error;},setActivity:async()=>{},clearActivity:async()=>{}})});
await assert.rejects(transport.connect(),e=>e===error);
});
Loading