[skip ci] feat: prepare for public release
This commit is contained in:
@@ -19,7 +19,6 @@
|
||||
|
||||
import {makeAutoObservable, observable, reaction, runInAction} from 'mobx';
|
||||
import {ChannelTypes, ME, Permissions} from '~/Constants';
|
||||
import {Logger} from '~/lib/Logger';
|
||||
import ChannelStore from './ChannelStore';
|
||||
import GuildStore from './GuildStore';
|
||||
import PermissionStore from './PermissionStore';
|
||||
@@ -32,8 +31,6 @@ type ChannelId = string;
|
||||
const PRIVATE_CHANNEL_SENTINEL = ME;
|
||||
const CAN_READ_PERMISSIONS = Permissions.VIEW_CHANNEL | Permissions.READ_MESSAGE_HISTORY;
|
||||
|
||||
const _logger = new Logger('GuildReadStateStore');
|
||||
|
||||
class GuildReadState {
|
||||
unread = observable.box(false);
|
||||
unreadChannelId = observable.box<ChannelId | null>(null);
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
*/
|
||||
|
||||
import {action, makeAutoObservable, reaction, runInAction} from 'mobx';
|
||||
import * as ModalActionCreators from '~/actions/ModalActionCreators';
|
||||
import {modal} from '~/actions/ModalActionCreators';
|
||||
import {ClaimAccountModal} from '~/components/modals/ClaimAccountModal';
|
||||
import {openClaimAccountModal} from '~/components/modals/ClaimAccountModal';
|
||||
import {type User, type UserPrivate, UserRecord} from '~/records/UserRecord';
|
||||
import AuthenticationStore from '~/stores/AuthenticationStore';
|
||||
|
||||
@@ -73,7 +71,7 @@ class UserStore {
|
||||
|
||||
if (!userRecord.isClaimed()) {
|
||||
setTimeout(async () => {
|
||||
ModalActionCreators.push(modal(() => <ClaimAccountModal />));
|
||||
openClaimAccountModal();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ class ConnectionStore {
|
||||
handleGatewayDispatch: action.bound,
|
||||
ensureGuildActiveAndSynced: action.bound,
|
||||
flushPendingGuildSync: action.bound,
|
||||
syncGuildIfNeeded: action.bound,
|
||||
},
|
||||
{autoBind: true},
|
||||
);
|
||||
@@ -324,6 +325,10 @@ class ConnectionStore {
|
||||
}
|
||||
}
|
||||
|
||||
syncGuildIfNeeded(guildId: string, reason?: string): void {
|
||||
this.ensureGuildActiveAndSynced(guildId, {reason});
|
||||
}
|
||||
|
||||
private flushPendingGuildSync(): void {
|
||||
const guildId = this.pendingGuildSyncId ?? SelectedGuildStore.selectedGuildId;
|
||||
if (!guildId || guildId === FAVORITES_GUILD_ID) {
|
||||
|
||||
@@ -23,19 +23,22 @@ import type {Participant, Room, ScreenShareCaptureOptions, TrackPublishOptions}
|
||||
import {computed, makeObservable} from 'mobx';
|
||||
import * as SoundActionCreators from '~/actions/SoundActionCreators';
|
||||
import * as ToastActionCreators from '~/actions/ToastActionCreators';
|
||||
import {type GatewayErrorCode, GatewayErrorCodes} from '~/Constants';
|
||||
import {ChannelTypes, type GatewayErrorCode, GatewayErrorCodes} from '~/Constants';
|
||||
import type {GatewayErrorData} from '~/lib/GatewaySocket';
|
||||
import {Logger} from '~/lib/Logger';
|
||||
import {voiceStatsDB} from '~/lib/VoiceStatsDB';
|
||||
import type {GuildReadyData} from '~/records/GuildRecord';
|
||||
import AuthenticationStore from '~/stores/AuthenticationStore';
|
||||
import CallMediaPrefsStore from '~/stores/CallMediaPrefsStore';
|
||||
import ChannelStore from '~/stores/ChannelStore';
|
||||
import ConnectionStore from '~/stores/ConnectionStore';
|
||||
import GuildMemberStore from '~/stores/GuildMemberStore';
|
||||
import GuildStore from '~/stores/GuildStore';
|
||||
import IdleStore from '~/stores/IdleStore';
|
||||
import LocalVoiceStateStore from '~/stores/LocalVoiceStateStore';
|
||||
import MediaPermissionStore from '~/stores/MediaPermissionStore';
|
||||
import UserStore from '~/stores/UserStore';
|
||||
import VoiceDevicePermissionStore from '~/stores/voice/VoiceDevicePermissionStore';
|
||||
import {SoundType} from '~/utils/SoundUtils';
|
||||
import {
|
||||
checkChannelLimit,
|
||||
@@ -152,6 +155,33 @@ class MediaEngineFacade {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const currentUser = UserStore.getCurrentUser();
|
||||
const isUnclaimed = !(currentUser?.isClaimed() ?? false);
|
||||
if (isUnclaimed) {
|
||||
if (!this.i18n) {
|
||||
throw new Error('MediaEngineFacade: i18n not initialized');
|
||||
}
|
||||
if (guildId) {
|
||||
const guild = GuildStore.getGuild(guildId);
|
||||
const isOwner = guild?.isOwner(currentUserId) ?? false;
|
||||
if (!isOwner) {
|
||||
ToastActionCreators.createToast({
|
||||
type: 'error',
|
||||
children: this.i18n._(msg`Claim your account to join voice channels you don't own.`),
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const channel = ChannelStore.getChannel(channelId);
|
||||
if (channel?.type === ChannelTypes.DM) {
|
||||
ToastActionCreators.createToast({
|
||||
type: 'error',
|
||||
children: this.i18n._(msg`Claim your account to start or join 1:1 calls.`),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ConnectionStore.socket) {
|
||||
logger.warn('[connectToVoiceChannel] No socket');
|
||||
return;
|
||||
@@ -159,6 +189,8 @@ class MediaEngineFacade {
|
||||
|
||||
if (!checkChannelLimit(guildId, channelId)) return;
|
||||
|
||||
this.voiceStateSync.reset();
|
||||
|
||||
const shouldProceed = checkMultipleConnections(
|
||||
guildId,
|
||||
channelId,
|
||||
@@ -185,6 +217,7 @@ class MediaEngineFacade {
|
||||
await this.disconnectFromVoiceChannel('user');
|
||||
}
|
||||
}
|
||||
this.voiceStateSync.reset();
|
||||
VoiceConnectionManager.startConnection(guildId, channelId);
|
||||
sendVoiceStateConnect(guildId, channelId);
|
||||
}
|
||||
@@ -298,17 +331,29 @@ class MediaEngineFacade {
|
||||
const {guildId, channelId, connectionId} = VoiceConnectionManager.connectionState;
|
||||
if (!channelId || !connectionId) return;
|
||||
|
||||
const devicePermission = VoiceDevicePermissionStore.getState().permissionStatus;
|
||||
const micGranted = MediaPermissionStore.isMicrophoneGranted() || devicePermission === 'granted';
|
||||
|
||||
const payload: VoiceStateSyncPayload = {
|
||||
guild_id: guildId,
|
||||
channel_id: channelId,
|
||||
connection_id: connectionId,
|
||||
self_mute: partial?.self_mute ?? LocalVoiceStateStore.getSelfMute(),
|
||||
self_mute:
|
||||
micGranted && partial?.self_mute !== undefined
|
||||
? partial.self_mute
|
||||
: micGranted
|
||||
? LocalVoiceStateStore.getSelfMute()
|
||||
: true,
|
||||
self_deaf: partial?.self_deaf ?? LocalVoiceStateStore.getSelfDeaf(),
|
||||
self_video: partial?.self_video ?? LocalVoiceStateStore.getSelfVideo(),
|
||||
self_stream: partial?.self_stream ?? LocalVoiceStateStore.getSelfStream(),
|
||||
viewer_stream_key: partial?.viewer_stream_key ?? LocalVoiceStateStore.getViewerStreamKey(),
|
||||
};
|
||||
|
||||
if (!micGranted && !LocalVoiceStateStore.getSelfMute()) {
|
||||
LocalVoiceStateStore.updateSelfMute(true);
|
||||
}
|
||||
|
||||
this.voiceStateSync.requestState(payload);
|
||||
}
|
||||
|
||||
@@ -451,6 +496,7 @@ class MediaEngineFacade {
|
||||
GatewayErrorCodes.VOICE_CHANNEL_FULL,
|
||||
GatewayErrorCodes.VOICE_MISSING_CONNECTION_ID,
|
||||
GatewayErrorCodes.VOICE_TOKEN_FAILED,
|
||||
GatewayErrorCodes.VOICE_UNCLAIMED_ACCOUNT,
|
||||
]);
|
||||
|
||||
if (!voiceErrorCodes.has(error.code)) {
|
||||
@@ -467,7 +513,8 @@ class MediaEngineFacade {
|
||||
} else if (
|
||||
error.code === GatewayErrorCodes.VOICE_PERMISSION_DENIED ||
|
||||
error.code === GatewayErrorCodes.VOICE_CHANNEL_FULL ||
|
||||
error.code === GatewayErrorCodes.VOICE_MEMBER_TIMED_OUT
|
||||
error.code === GatewayErrorCodes.VOICE_MEMBER_TIMED_OUT ||
|
||||
error.code === GatewayErrorCodes.VOICE_UNCLAIMED_ACCOUNT
|
||||
) {
|
||||
if (VoiceConnectionManager.connecting && !VoiceConnectionManager.connected) {
|
||||
logger.info('[handleGatewayError] Permission denied, channel full, or timeout while connecting, aborting');
|
||||
@@ -481,6 +528,14 @@ class MediaEngineFacade {
|
||||
type: 'error',
|
||||
children: this.i18n._(msg`You can't join while you're on timeout.`),
|
||||
});
|
||||
} else if (error.code === GatewayErrorCodes.VOICE_UNCLAIMED_ACCOUNT) {
|
||||
if (!this.i18n) {
|
||||
throw new Error('MediaEngineFacade: i18n not initialized');
|
||||
}
|
||||
ToastActionCreators.createToast({
|
||||
type: 'error',
|
||||
children: this.i18n._(msg`Claim your account to join this voice channel.`),
|
||||
});
|
||||
}
|
||||
} else if (error.code === GatewayErrorCodes.VOICE_TOKEN_FAILED) {
|
||||
if (VoiceConnectionManager.connecting) {
|
||||
|
||||
@@ -36,18 +36,22 @@ const logger = new Logger('VoiceStateSyncManager');
|
||||
export class VoiceStateSyncManager {
|
||||
private pending: VoiceStateSyncPayload | null = null;
|
||||
private lastSent: VoiceStateSyncPayload | null = null;
|
||||
private inFlight: VoiceStateSyncPayload | null = null;
|
||||
private serverState: VoiceStateSyncPayload | null = null;
|
||||
private flushScheduled = false;
|
||||
|
||||
reset(): void {
|
||||
this.pending = null;
|
||||
this.lastSent = null;
|
||||
this.inFlight = null;
|
||||
this.serverState = null;
|
||||
this.flushScheduled = false;
|
||||
logger.debug('[reset] Voice state sync cache cleared');
|
||||
}
|
||||
|
||||
requestState(payload: VoiceStateSyncPayload): void {
|
||||
this.pending = payload;
|
||||
this.flush();
|
||||
this.scheduleFlush();
|
||||
}
|
||||
|
||||
confirmServerState(payload: VoiceStateSyncPayload | null): void {
|
||||
@@ -57,14 +61,46 @@ export class VoiceStateSyncManager {
|
||||
}
|
||||
|
||||
this.serverState = payload;
|
||||
if (this.inFlight && this.areEqual(this.inFlight, payload)) {
|
||||
this.inFlight = null;
|
||||
this.lastSent = payload;
|
||||
}
|
||||
|
||||
if (this.pending && this.areEqual(this.pending, payload)) {
|
||||
this.pending = null;
|
||||
}
|
||||
|
||||
this.scheduleFlush();
|
||||
}
|
||||
|
||||
private scheduleFlush(): void {
|
||||
if (this.flushScheduled) return;
|
||||
this.flushScheduled = true;
|
||||
|
||||
Promise.resolve().then(() => {
|
||||
this.flushScheduled = false;
|
||||
this.flush();
|
||||
});
|
||||
}
|
||||
|
||||
private flush(): void {
|
||||
if (!this.pending) return;
|
||||
|
||||
if (
|
||||
this.inFlight &&
|
||||
(this.inFlight.connection_id !== this.pending.connection_id ||
|
||||
this.inFlight.channel_id !== this.pending.channel_id ||
|
||||
this.inFlight.guild_id !== this.pending.guild_id)
|
||||
) {
|
||||
logger.debug('[flush] Dropping stale in-flight state after context change', {
|
||||
inFlight: this.inFlight,
|
||||
pending: this.pending,
|
||||
});
|
||||
this.inFlight = null;
|
||||
}
|
||||
|
||||
if (this.inFlight) return;
|
||||
|
||||
if (this.lastSent && this.areEqual(this.lastSent, this.pending)) {
|
||||
this.pending = null;
|
||||
return;
|
||||
@@ -86,6 +122,7 @@ export class VoiceStateSyncManager {
|
||||
});
|
||||
|
||||
this.lastSent = this.pending;
|
||||
this.inFlight = this.pending;
|
||||
this.pending = null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user