mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-09 21:25:45 +08:00
fix(core): selfhosted auth handling (#15295)
fix #15284 fix #15266 fix #15268 fix #15267 #### PR Dependency Tree * **PR #15295** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal)
This commit is contained in:
@@ -5,8 +5,7 @@ query getCurrentUser {
|
||||
email
|
||||
emailVerified
|
||||
avatarUrl
|
||||
token {
|
||||
sessionToken
|
||||
}
|
||||
hasPassword
|
||||
features
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1967,12 +1967,10 @@ export const getCurrentUserQuery = {
|
||||
email
|
||||
emailVerified
|
||||
avatarUrl
|
||||
token {
|
||||
sessionToken
|
||||
}
|
||||
hasPassword
|
||||
features
|
||||
}
|
||||
}`,
|
||||
deprecations: ["'token' is deprecated: use auth session exchange instead"],
|
||||
};
|
||||
|
||||
export const getDocCreatedByUpdatedByListQuery = {
|
||||
|
||||
@@ -6519,7 +6519,8 @@ export type GetCurrentUserQuery = {
|
||||
email: string;
|
||||
emailVerified: boolean;
|
||||
avatarUrl: string | null;
|
||||
token: { __typename?: 'tokenType'; sessionToken: string | null };
|
||||
hasPassword: boolean | null;
|
||||
features: Array<FeatureType>;
|
||||
} | null;
|
||||
};
|
||||
|
||||
|
||||
@@ -186,17 +186,27 @@ export abstract class AutoReconnectConnection<
|
||||
return;
|
||||
}
|
||||
|
||||
if (signal?.aborted) {
|
||||
reject(signal.reason);
|
||||
return;
|
||||
}
|
||||
|
||||
const off = this.onStatusChanged(status => {
|
||||
if (status === 'connected') {
|
||||
resolve();
|
||||
off();
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
signal?.addEventListener('abort', reason => {
|
||||
reject(reason);
|
||||
const onAbort = () => {
|
||||
reject(signal?.reason);
|
||||
cleanup();
|
||||
};
|
||||
const cleanup = () => {
|
||||
off();
|
||||
});
|
||||
signal?.removeEventListener('abort', onAbort);
|
||||
};
|
||||
signal?.addEventListener('abort', onAbort, { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +41,9 @@ class FakeSocket {
|
||||
}
|
||||
}
|
||||
|
||||
const { resetSharedConnection } = vi.hoisted(() => ({
|
||||
const { resetSharedConnection, waitForConnected } = vi.hoisted(() => ({
|
||||
resetSharedConnection: vi.fn(),
|
||||
waitForConnected: vi.fn(async () => {}),
|
||||
}));
|
||||
const socket = new FakeSocket();
|
||||
|
||||
@@ -56,7 +57,7 @@ vi.mock('../../impls/cloud/socket', () => ({
|
||||
|
||||
connect() {}
|
||||
|
||||
async waitForConnected() {}
|
||||
waitForConnected = waitForConnected;
|
||||
|
||||
disconnect() {
|
||||
socket.disconnected = true;
|
||||
@@ -74,6 +75,8 @@ beforeEach(() => {
|
||||
socket.connected = true;
|
||||
socket.disconnected = false;
|
||||
resetSharedConnection.mockClear();
|
||||
waitForConnected.mockReset();
|
||||
waitForConnected.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
test('getRealtimeInputKey is deterministic for realtime subscription inputs', () => {
|
||||
@@ -157,7 +160,7 @@ test('non-bootstrap request still requires authenticated context', async () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('request rejects when aborted', async () => {
|
||||
test('request rejects when connection times out or is aborted', async () => {
|
||||
const manager = new RealtimeManager();
|
||||
manager.setContext({
|
||||
endpoint: 'http://server',
|
||||
@@ -175,6 +178,14 @@ test('request rejects when aborted', async () => {
|
||||
controller.abort();
|
||||
|
||||
await expect(request).rejects.toThrow('Realtime request aborted');
|
||||
|
||||
waitForConnected.mockImplementationOnce(() => new Promise(() => {}));
|
||||
await expect(
|
||||
manager.request('notification.count.get', {}, { timeoutMs: 1 })
|
||||
).rejects.toMatchObject({
|
||||
name: 'RealtimeRequestTimeout',
|
||||
message: 'Realtime request timed out: notification.count.get',
|
||||
});
|
||||
});
|
||||
|
||||
test('subscribe routes events by topic and stable input key', async () => {
|
||||
|
||||
@@ -79,8 +79,8 @@ export class RealtimeManager {
|
||||
input: RealtimeRequestInputOf<Op>,
|
||||
options?: { timeoutMs?: number; signal?: AbortSignal }
|
||||
): Promise<RealtimeRequestOutputOf<Op>> {
|
||||
const socket = await this.connect(op === 'user.profile.get');
|
||||
const timeoutMs = options?.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT;
|
||||
const connectAbort = new AbortController();
|
||||
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
||||
let abortHandler: (() => void) | undefined;
|
||||
const abort = () => {
|
||||
@@ -104,28 +104,36 @@ export class RealtimeManager {
|
||||
options?.signal?.addEventListener('abort', abortHandler, { once: true });
|
||||
});
|
||||
|
||||
const ack = await Promise.race([
|
||||
socket.emitWithAck('realtime:request', {
|
||||
op,
|
||||
input,
|
||||
clientVersion: BUILD_CONFIG.appVersion,
|
||||
}),
|
||||
timeout,
|
||||
aborted,
|
||||
]).finally(() => {
|
||||
try {
|
||||
const socket = await Promise.race([
|
||||
this.connect(op === 'user.profile.get', connectAbort.signal),
|
||||
timeout,
|
||||
aborted,
|
||||
]);
|
||||
const ack = await Promise.race([
|
||||
socket.emitWithAck('realtime:request', {
|
||||
op,
|
||||
input,
|
||||
clientVersion: BUILD_CONFIG.appVersion,
|
||||
}),
|
||||
timeout,
|
||||
aborted,
|
||||
]);
|
||||
|
||||
if ('error' in ack) {
|
||||
throw rejectAck(ack.error);
|
||||
}
|
||||
|
||||
return ack.data as unknown as RealtimeRequestOutputOf<Op>;
|
||||
} finally {
|
||||
connectAbort.abort();
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
if (abortHandler) {
|
||||
options?.signal?.removeEventListener('abort', abortHandler);
|
||||
}
|
||||
});
|
||||
|
||||
if ('error' in ack) {
|
||||
throw rejectAck(ack.error);
|
||||
}
|
||||
|
||||
return ack.data as unknown as RealtimeRequestOutputOf<Op>;
|
||||
}
|
||||
|
||||
subscribe<Topic extends RealtimeTopicName>(
|
||||
@@ -224,7 +232,7 @@ export class RealtimeManager {
|
||||
};
|
||||
}
|
||||
|
||||
private async connect(allowUnauthenticated = false) {
|
||||
private async connect(allowUnauthenticated = false, signal?: AbortSignal) {
|
||||
if (
|
||||
!this.context?.endpoint ||
|
||||
(!this.context.authenticated && !allowUnauthenticated)
|
||||
@@ -245,7 +253,7 @@ export class RealtimeManager {
|
||||
this.socketConnection.connect();
|
||||
}
|
||||
|
||||
await this.socketConnection.waitForConnected();
|
||||
await this.socketConnection.waitForConnected(signal);
|
||||
this.socketConnection.inner.socket.off('realtime:event', this.handleEvent);
|
||||
this.socketConnection.inner.socket.on('realtime:event', this.handleEvent);
|
||||
this.socketConnection.inner.socket.off('connect', this.handleReconnect);
|
||||
|
||||
Reference in New Issue
Block a user