Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a2f18e3d5 |
@@ -17,6 +17,8 @@ import {
|
|||||||
CALL_SESSION_HEARTBEAT_MS,
|
CALL_SESSION_HEARTBEAT_MS,
|
||||||
clearCallSession,
|
clearCallSession,
|
||||||
decideRejoin,
|
decideRejoin,
|
||||||
|
othersInCall,
|
||||||
|
rejoinToastBody,
|
||||||
readCallSession,
|
readCallSession,
|
||||||
writeCallSession,
|
writeCallSession,
|
||||||
} from '../utils/callRejoin';
|
} from '../utils/callRejoin';
|
||||||
@@ -112,6 +114,27 @@ export function useCallRejoin(embed: CallEmbed | undefined, joined: boolean): vo
|
|||||||
};
|
};
|
||||||
dismissToast(TOAST_ID);
|
dismissToast(TOAST_ID);
|
||||||
clearCallSession();
|
clearCallSession();
|
||||||
|
// [Gitea #302] A DM call the other person has since left is over;
|
||||||
|
// starting it again would ring them, so just say so.
|
||||||
|
const dm = l.directs.has(room.roomId);
|
||||||
|
if (
|
||||||
|
dm &&
|
||||||
|
!othersInCall(
|
||||||
|
session.memberships.map((m) => m.userId ?? ''),
|
||||||
|
mx.getUserId() ?? '',
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Own id: clicking a toast removes it by id after its onClick runs.
|
||||||
|
setToast({
|
||||||
|
id: `${TOAST_ID}-ended`,
|
||||||
|
iconSrc: Icons.Phone,
|
||||||
|
displayName: 'Call ended',
|
||||||
|
body: `${room.name ?? 'They'} already left the call.`,
|
||||||
|
roomName: room.name ?? '',
|
||||||
|
roomId: room.roomId,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
(l.directs.has(room.roomId) ? l.startDmCall : l.startRoomCall)(room, pref);
|
(l.directs.has(room.roomId) ? l.startDmCall : l.startRoomCall)(room, pref);
|
||||||
} catch {
|
} catch {
|
||||||
@@ -133,6 +156,7 @@ export function useCallRejoin(embed: CallEmbed | undefined, joined: boolean): vo
|
|||||||
.filter((m) => m.userId === mx.getUserId())
|
.filter((m) => m.userId === mx.getUserId())
|
||||||
.map((m) => m.deviceId),
|
.map((m) => m.deviceId),
|
||||||
memberCount: memberships.length,
|
memberCount: memberships.length,
|
||||||
|
dm: latest.current.directs.has(room.roomId),
|
||||||
});
|
});
|
||||||
// The RTC session fills in a moment after sync; keep waiting while it is
|
// The RTC session fills in a moment after sync; keep waiting while it is
|
||||||
// empty unless this is the final (timed-out) look.
|
// empty unless this is the final (timed-out) look.
|
||||||
@@ -152,7 +176,7 @@ export function useCallRejoin(embed: CallEmbed | undefined, joined: boolean): vo
|
|||||||
id: TOAST_ID,
|
id: TOAST_ID,
|
||||||
iconSrc: Icons.Phone,
|
iconSrc: Icons.Phone,
|
||||||
displayName: 'Rejoin voice?',
|
displayName: 'Rejoin voice?',
|
||||||
body: `You were in the call in ${room.name ?? 'a room'} before the restart. Tap to rejoin.`,
|
body: rejoinToastBody(room.name, latest.current.directs.has(room.roomId)),
|
||||||
roomName: room.name ?? '',
|
roomName: room.name ?? '',
|
||||||
roomId: room.roomId,
|
roomId: room.roomId,
|
||||||
sticky: true,
|
sticky: true,
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { describe, it } from 'node:test';
|
import { describe, it } from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { CALL_REJOIN_MAX_AGE_MS, decideRejoin, RejoinContext } from './callRejoin';
|
import {
|
||||||
|
CALL_REJOIN_MAX_AGE_MS,
|
||||||
|
decideRejoin,
|
||||||
|
othersInCall,
|
||||||
|
rejoinToastBody,
|
||||||
|
RejoinContext,
|
||||||
|
} from './callRejoin';
|
||||||
|
|
||||||
const base: RejoinContext = {
|
const base: RejoinContext = {
|
||||||
record: {
|
record: {
|
||||||
@@ -54,3 +60,21 @@ describe('decideRejoin', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('decideRejoin: DM calls are never auto-rejoined, only offered (#302)', () => {
|
||||||
|
assert.deepEqual(decideRejoin({ ...base, mode: 'auto', dm: true }), {
|
||||||
|
action: 'ask',
|
||||||
|
roomId: '!r',
|
||||||
|
});
|
||||||
|
assert.deepEqual(decideRejoin({ ...base, mode: 'auto' }), { action: 'auto', roomId: '!r' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejoinToastBody and othersInCall (#302)', () => {
|
||||||
|
assert.equal(
|
||||||
|
rejoinToastBody('Bob Builder', true),
|
||||||
|
'You were in a call with Bob Builder before the restart. Tap to rejoin.',
|
||||||
|
);
|
||||||
|
assert.match(rejoinToastBody('Voice Lounge', false), /in the call in Voice Lounge/);
|
||||||
|
assert.equal(othersInCall(['@me:x'], '@me:x'), false);
|
||||||
|
assert.equal(othersInCall(['@me:x', '@bob:x'], '@me:x'), true);
|
||||||
|
});
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ export type RejoinContext = {
|
|||||||
ownMemberDevices: string[];
|
ownMemberDevices: string[];
|
||||||
/** Number of call memberships in the room, ours included. */
|
/** Number of call memberships in the room, ours included. */
|
||||||
memberCount: number;
|
memberCount: number;
|
||||||
|
/** A DM call: rejoining one may ring the other person. */
|
||||||
|
dm?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RejoinDecision = { action: 'none' } | { action: 'ask' | 'auto'; roomId: string };
|
export type RejoinDecision = { action: 'none' } | { action: 'ask' | 'auto'; roomId: string };
|
||||||
@@ -83,5 +85,18 @@ export function decideRejoin(ctx: RejoinContext): RejoinDecision {
|
|||||||
// Nobody there any more (our own stale membership doesn't count).
|
// Nobody there any more (our own stale membership doesn't count).
|
||||||
const others = ctx.memberCount - ctx.ownMemberDevices.length;
|
const others = ctx.memberCount - ctx.ownMemberDevices.length;
|
||||||
if (others <= 0) return { action: 'none' };
|
if (others <= 0) return { action: 'none' };
|
||||||
|
// [Gitea #302] Never rejoin a DM call on our own: if the other person has
|
||||||
|
// just left, starting it again would ring them. Ask instead.
|
||||||
|
if (ctx.dm && mode === 'auto') return { action: 'ask', roomId: record.roomId };
|
||||||
return { action: mode, roomId: record.roomId };
|
return { action: mode, roomId: record.roomId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Pure: whether anyone but us is still in the call (by call membership). */
|
||||||
|
export const othersInCall = (memberUserIds: string[], myUserId: string): boolean =>
|
||||||
|
memberUserIds.some((u) => u !== myUserId);
|
||||||
|
|
||||||
|
/** The rejoin toast's text; DMs name the person rather than "the call in <room>". */
|
||||||
|
export const rejoinToastBody = (roomName: string | undefined, dm: boolean): string =>
|
||||||
|
dm
|
||||||
|
? `You were in a call with ${roomName ?? 'them'} before the restart. Tap to rejoin.`
|
||||||
|
: `You were in the call in ${roomName ?? 'a room'} before the restart. Tap to rejoin.`;
|
||||||
|
|||||||
Reference in New Issue
Block a user