Stop Matrix refresh loop when login is unavailable
This commit is contained in:
1 parent
12a853fa42
commit
92287c0a9a
2 files changed
+73
-3
No files matched your search
@@ -25,6 +25,13 @@ export interface TokenRefresher {
|
|||||||
refresh(): Promise<void>;
|
refresh(): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MatrixAuthenticationUnavailableError extends Error {
|
||||||
|
constructor(cause: unknown) {
|
||||||
|
super('Matrix authentication could not be restored', { cause });
|
||||||
|
this.name = 'MatrixAuthenticationUnavailableError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional Reddit-API hook for resolving t2 IDs to real usernames. Without
|
* Optional Reddit-API hook for resolving t2 IDs to real usernames. Without
|
||||||
* it, conversations show up as `t2_abc123` (stable but ugly). With it, the
|
* it, conversations show up as `t2_abc123` (stable but ugly). With it, the
|
||||||
@@ -174,7 +181,11 @@ export class ChatService {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof MatrixAuthError && this.tokenRefresher) {
|
if (err instanceof MatrixAuthError && this.tokenRefresher) {
|
||||||
this.logger.warn('[chat] Matrix token rejected — refreshing via browser session');
|
this.logger.warn('[chat] Matrix token rejected — refreshing via browser session');
|
||||||
await this.tokenRefresher.refresh();
|
try {
|
||||||
|
await this.tokenRefresher.refresh();
|
||||||
|
} catch (refreshError) {
|
||||||
|
throw new MatrixAuthenticationUnavailableError(refreshError);
|
||||||
|
}
|
||||||
return await fn();
|
return await fn();
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
@@ -331,6 +342,7 @@ export class ChatService {
|
|||||||
try {
|
try {
|
||||||
await this.loadDetails(chat);
|
await this.loadDetails(chat);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (err instanceof MatrixAuthenticationUnavailableError) throw err;
|
||||||
this.logger.error(`[chat] backfill: failed details for ${chat.roomName}`, err);
|
this.logger.error(`[chat] backfill: failed details for ${chat.roomName}`, err);
|
||||||
}
|
}
|
||||||
done++;
|
done++;
|
||||||
@@ -1062,6 +1074,10 @@ export class ChatService {
|
|||||||
}) as { state_key?: string } | undefined;
|
}) as { state_key?: string } | undefined;
|
||||||
t2 = otherMember?.state_key?.match(/^@(t2_[^:]+):/)?.[1];
|
t2 = otherMember?.state_key?.match(/^@(t2_[^:]+):/)?.[1];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (err instanceof MatrixAuthenticationUnavailableError) {
|
||||||
|
this.logger.warn('[chat] username enrichment paused until Reddit login is restored');
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.logger.debug(`[chat] /state lookup failed for ${chat.roomId}:`, err);
|
this.logger.debug(`[chat] /state lookup failed for ${chat.roomId}:`, err);
|
||||||
}
|
}
|
||||||
if (!t2) {
|
if (!t2) {
|
||||||
@@ -1074,6 +1090,10 @@ export class ChatService {
|
|||||||
)?.sender;
|
)?.sender;
|
||||||
t2 = otherSender?.match(/^@(t2_[^:]+):/)?.[1];
|
t2 = otherSender?.match(/^@(t2_[^:]+):/)?.[1];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (err instanceof MatrixAuthenticationUnavailableError) {
|
||||||
|
this.logger.warn('[chat] username enrichment paused until Reddit login is restored');
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.logger.debug(`[chat] /messages lookup failed for ${chat.roomId}:`, err);
|
this.logger.debug(`[chat] /messages lookup failed for ${chat.roomId}:`, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import Database from 'better-sqlite3';
|
import Database from 'better-sqlite3';
|
||||||
@@ -6,7 +6,7 @@ import { runMigrations } from '../src/database/client.js';
|
|||||||
import { RedditUsersRepository } from '../src/database/repositories.js';
|
import { RedditUsersRepository } from '../src/database/repositories.js';
|
||||||
import { ChatService } from '../src/services/chat/service.js';
|
import { ChatService } from '../src/services/chat/service.js';
|
||||||
import { ChatCache } from '../src/services/chat/cache.js';
|
import { ChatCache } from '../src/services/chat/cache.js';
|
||||||
import { MatrixClient } from '../src/services/chat/matrix/client.js';
|
import { MatrixAuthError, MatrixClient } from '../src/services/chat/matrix/client.js';
|
||||||
import { createMockLogger } from './mockLogger.js';
|
import { createMockLogger } from './mockLogger.js';
|
||||||
|
|
||||||
const creds = {
|
const creds = {
|
||||||
@@ -103,4 +103,54 @@ describe('ChatService username persistence', () => {
|
|||||||
expect(chats[0].roomName).toBe('alice');
|
expect(chats[0].roomName).toBe('alice');
|
||||||
db.close();
|
db.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('aborts username enrichment after one failed token refresh', async () => {
|
||||||
|
const logger = createMockLogger();
|
||||||
|
const cache = tmpCache(logger);
|
||||||
|
cache.set([
|
||||||
|
{
|
||||||
|
username: 'unknown',
|
||||||
|
roomName: 'unknown',
|
||||||
|
lastMessageTime: Date.now(),
|
||||||
|
lastMessageSnippet: '',
|
||||||
|
isFromUs: false,
|
||||||
|
unread: false,
|
||||||
|
roomId: '!first:reddit.com',
|
||||||
|
messages: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
username: 'unknown',
|
||||||
|
roomName: 'unknown',
|
||||||
|
lastMessageTime: Date.now(),
|
||||||
|
lastMessageSnippet: '',
|
||||||
|
isFromUs: false,
|
||||||
|
unread: false,
|
||||||
|
roomId: '!second:reddit.com',
|
||||||
|
messages: [],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const matrix = buildMatrix();
|
||||||
|
matrix.getRoomFullState = vi.fn().mockRejectedValue(new MatrixAuthError('expired'));
|
||||||
|
matrix.getRoomMessages = vi.fn().mockRejectedValue(new MatrixAuthError('expired'));
|
||||||
|
const tokenRefresher = {
|
||||||
|
refresh: vi.fn().mockRejectedValue(new Error('interactive login required')),
|
||||||
|
};
|
||||||
|
const usernameResolver = { resolveT2Ids: vi.fn().mockResolvedValue({}) };
|
||||||
|
const svc = new ChatService(
|
||||||
|
matrix,
|
||||||
|
logger,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
tokenRefresher,
|
||||||
|
usernameResolver,
|
||||||
|
cache,
|
||||||
|
);
|
||||||
|
|
||||||
|
await svc.enrichUsernames();
|
||||||
|
|
||||||
|
expect(tokenRefresher.refresh).toHaveBeenCalledOnce();
|
||||||
|
expect(matrix.getRoomFullState).toHaveBeenCalledOnce();
|
||||||
|
expect(matrix.getRoomMessages).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user