();
+
+ for (const memberServerIds of this.memberServerIdsBySignalUrl.values()) {
+ memberServerIds.forEach((serverId) => joinedServerIds.add(serverId));
+ }
+
+ return joinedServerIds;
}
/**
@@ -942,11 +1236,15 @@ export class WebRTCService implements OnDestroy {
/** Disconnect from the signaling server and clean up all state. */
disconnect(): void {
+ this.leaveRoom();
this.voiceServerId = null;
this.peerServerMap.clear();
- this.leaveRoom();
+ this.peerSignalingUrlMap.clear();
+ this.lastJoinedServerBySignalUrl.clear();
+ this.memberServerIdsBySignalUrl.clear();
+ this.serverSignalingUrlMap.clear();
this.mediaManager.stopVoiceHeartbeat();
- this.signalingManager.close();
+ this.destroyAllSignalingManagers();
this._isSignalingConnected.set(false);
this._hasEverConnected.set(false);
this._hasConnectionError.set(false);
@@ -962,6 +1260,7 @@ export class WebRTCService implements OnDestroy {
private fullCleanup(): void {
this.voiceServerId = null;
this.peerServerMap.clear();
+ this.peerSignalingUrlMap.clear();
this.remoteScreenShareRequestsEnabled = false;
this.desiredRemoteScreenSharePeers.clear();
this.activeRemoteScreenSharePeers.clear();
@@ -1040,10 +1339,25 @@ export class WebRTCService implements OnDestroy {
}
}
+ private destroyAllSignalingManagers(): void {
+ for (const subscriptions of this.signalingSubscriptions.values()) {
+ for (const subscription of subscriptions) {
+ subscription.unsubscribe();
+ }
+ }
+
+ for (const manager of this.signalingManagers.values()) {
+ manager.destroy();
+ }
+
+ this.signalingSubscriptions.clear();
+ this.signalingManagers.clear();
+ this.signalingConnectionStates.clear();
+ }
+
ngOnDestroy(): void {
this.disconnect();
this.serviceDestroyed$.complete();
- this.signalingManager.destroy();
this.peerManager.destroy();
this.mediaManager.destroy();
this.screenShareManager.destroy();
diff --git a/src/app/features/server-search/server-search.component.html b/src/app/features/server-search/server-search.component.html
index 67bd6ce..75cf8f0 100644
--- a/src/app/features/server-search/server-search.component.html
+++ b/src/app/features/server-search/server-search.component.html
@@ -298,6 +298,24 @@
/>
+
+
+
+
This endpoint handles all signaling for this chat server.
+
+
-
+
+ @if (hasMissingDefaultServers()) {
+
+ }
+
+
- Server directories to search for rooms. The active server is used for creating new rooms.
+
+ Active server endpoints stay enabled at the same time. You pick the endpoint when creating a new server.
+
@@ -41,6 +55,7 @@
[class.bg-red-500]="server.status === 'offline'"
[class.bg-yellow-500]="server.status === 'checking'"
[class.bg-muted]="server.status === 'unknown'"
+ [class.bg-orange-500]="server.status === 'incompatible'"
>
@@ -53,13 +68,17 @@
@if (server.latency !== undefined && server.status === 'online') {
{{ server.latency }}ms
}
+ @if (server.status === 'incompatible') {
+
Update the client in order to connect to other users
+ }
- @if (!server.isActive) {
+ @if (!server.isActive && server.status !== 'incompatible') {
}
- @if (!server.isDefault) {
+ @if (server.isActive && hasMultipleActiveServers()) {
+ }
+ @if (hasMultipleServers()) {
+
@@ -78,15 +91,19 @@
@if (server.latency !== undefined && server.status === 'online') {
{{ server.latency }}ms
}
+ @if (server.status === 'incompatible') {
+ Update the client in order to connect to other users
+ }
- @if (!server.isActive) {
+ @if (!server.isActive && server.status !== 'incompatible') {
}
- @if (!server.isDefault) {
+ @if (server.isActive && hasMultipleActiveServers()) {
+
+
+ }
+ @if (hasMultipleServers()) {
+
this.servers().length > 1);
+ hasMultipleActiveServers = computed(() => this.activeServers().length > 1);
isTesting = signal(false);
addError = signal(null);
@@ -122,6 +127,14 @@ export class SettingsComponent implements OnInit {
this.serverDirectory.setActiveServer(id);
}
+ deactivateServer(id: string): void {
+ this.serverDirectory.deactivateServer(id);
+ }
+
+ restoreDefaultServers(): void {
+ this.serverDirectory.restoreDefaultServers();
+ }
+
/** Test connectivity to all configured servers. */
async testAllServers(): Promise {
this.isTesting.set(true);
diff --git a/src/app/features/shell/title-bar.component.html b/src/app/features/shell/title-bar.component.html
index 5910263..286eb11 100644
--- a/src/app/features/shell/title-bar.component.html
+++ b/src/app/features/shell/title-bar.component.html
@@ -13,6 +13,12 @@
/>
{{ roomName() }}
+ @if (showRoomCompatibilityNotice()) {
+
+ {{ signalServerCompatibilityError() }}
+
+ }
+
@if (showRoomReconnectNotice()) {
{{ username() }} | {{ serverName() }}
- @if (isReconnecting()) {
- Reconnecting…
- }
+ Reconnecting…
}
@@ -41,16 +49,15 @@
class="flex items-center gap-2"
style="-webkit-app-region: no-drag"
>
- @if (!isAuthed()) {
-
- Login
-
- }
+
+ Login
+
}
- @if (inviteStatus()) {
-
- {{ inviteStatus() }}
-
- }
+
+ {{ inviteStatus() }}
+
!!this.currentUser());
currentRoom = this.store.selectSignal(selectCurrentRoom);
isSignalServerReconnecting = this.store.selectSignal(selectIsSignalServerReconnecting);
+ signalServerCompatibilityError = this.store.selectSignal(selectSignalServerCompatibilityError);
inRoom = computed(() => !!this.currentRoom());
roomName = computed(() => this.currentRoom()?.name || '');
roomDescription = computed(() => this.currentRoom()?.description || '');
+ showRoomCompatibilityNotice = computed(() =>
+ this.inRoom() && !!this.signalServerCompatibilityError()
+ );
showRoomReconnectNotice = computed(() =>
- this.inRoom() && (
+ this.inRoom()
+ && !this.signalServerCompatibilityError()
+ && (
this.isSignalServerReconnecting()
|| this.webrtc.shouldShowConnectionError()
|| this.isReconnecting()
diff --git a/src/app/store/rooms/rooms.actions.ts b/src/app/store/rooms/rooms.actions.ts
index 2b465b0..3009760 100644
--- a/src/app/store/rooms/rooms.actions.ts
+++ b/src/app/store/rooms/rooms.actions.ts
@@ -25,7 +25,15 @@ export const RoomsActions = createActionGroup({
'Search Servers Success': props<{ servers: ServerInfo[] }>(),
'Search Servers Failure': props<{ error: string }>(),
- 'Create Room': props<{ name: string; description?: string; topic?: string; isPrivate?: boolean; password?: string }>(),
+ 'Create Room': props<{
+ name: string;
+ description?: string;
+ topic?: string;
+ isPrivate?: boolean;
+ password?: string;
+ sourceId?: string;
+ sourceUrl?: string;
+ }>(),
'Create Room Success': props<{ room: Room }>(),
'Create Room Failure': props<{ error: string }>(),
@@ -36,7 +44,7 @@ export const RoomsActions = createActionGroup({
'Leave Room': emptyProps(),
'Leave Room Success': emptyProps(),
- 'View Server': props<{ room: Room }>(),
+ 'View Server': props<{ room: Room; skipBanCheck?: boolean }>(),
'View Server Success': props<{ room: Room }>(),
'Delete Room': props<{ roomId: string }>(),
@@ -68,6 +76,7 @@ export const RoomsActions = createActionGroup({
'Clear Search Results': emptyProps(),
'Set Connecting': props<{ isConnecting: boolean }>(),
- 'Set Signal Server Reconnecting': props<{ isReconnecting: boolean }>()
+ 'Set Signal Server Reconnecting': props<{ isReconnecting: boolean }>(),
+ 'Set Signal Server Compatibility Error': props<{ message: string | null }>()
}
});
diff --git a/src/app/store/rooms/rooms.effects.ts b/src/app/store/rooms/rooms.effects.ts
index 176327a..a114452 100644
--- a/src/app/store/rooms/rooms.effects.ts
+++ b/src/app/store/rooms/rooms.effects.ts
@@ -32,7 +32,11 @@ import { selectCurrentUser, selectAllUsers } from '../users/users.selectors';
import { selectCurrentRoom, selectSavedRooms } from './rooms.selectors';
import { DatabaseService } from '../../core/services/database.service';
import { WebRTCService } from '../../core/services/webrtc.service';
-import { ServerDirectoryService } from '../../core/services/server-directory.service';
+import {
+ CLIENT_UPDATE_REQUIRED_MESSAGE,
+ ServerDirectoryService,
+ ServerSourceSelector
+} from '../../core/services/server-directory.service';
import {
ChatEvent,
Room,
@@ -182,12 +186,21 @@ export class RoomsEffects {
this.actions$.pipe(
ofType(RoomsActions.createRoom),
withLatestFrom(this.store.select(selectCurrentUser)),
- switchMap(([{ name, description, topic, isPrivate, password }, currentUser]) => {
+ switchMap(([{ name, description, topic, isPrivate, password, sourceId, sourceUrl }, currentUser]) => {
if (!currentUser) {
return of(RoomsActions.createRoomFailure({ error: 'Not logged in' }));
}
- const activeEndpoint = this.serverDirectory.activeServer();
+ const allEndpoints = this.serverDirectory.servers();
+ const activeEndpoints = this.serverDirectory.activeServers();
+ const selectedEndpoint = allEndpoints.find((endpoint) =>
+ (sourceId && endpoint.id === sourceId)
+ || (!!sourceUrl && endpoint.url === sourceUrl)
+ );
+ const endpoint = selectedEndpoint
+ ?? activeEndpoints[0]
+ ?? allEndpoints[0]
+ ?? null;
const normalizedPassword = typeof password === 'string' ? password.trim() : '';
const room: Room = {
id: uuidv4(),
@@ -201,9 +214,9 @@ export class RoomsEffects {
createdAt: Date.now(),
userCount: 1,
maxUsers: 50,
- sourceId: activeEndpoint?.id,
- sourceName: activeEndpoint?.name,
- sourceUrl: activeEndpoint?.url
+ sourceId: endpoint?.id,
+ sourceName: endpoint?.name,
+ sourceUrl: endpoint?.url
};
// Save to local DB
@@ -224,7 +237,11 @@ export class RoomsEffects {
userCount: 1,
maxUsers: room.maxUsers || 50,
tags: []
- })
+ }, endpoint ? {
+ sourceId: endpoint.id,
+ sourceUrl: endpoint.url
+ } : undefined
+ )
.subscribe();
return of(RoomsActions.createRoomSuccess({ room }));
@@ -353,7 +370,9 @@ export class RoomsEffects {
user,
savedRooms
]) => {
- this.connectToRoomSignaling(room, user ?? null, undefined, savedRooms);
+ void this.connectToRoomSignaling(room, user ?? null, undefined, savedRooms, {
+ showCompatibilityError: true
+ });
this.router.navigate(['/room', room.id]);
})
@@ -367,7 +386,7 @@ export class RoomsEffects {
ofType(RoomsActions.viewServer),
withLatestFrom(this.store.select(selectCurrentUser), this.store.select(selectSavedRooms)),
switchMap(([
- { room },
+ { room, skipBanCheck },
user,
savedRooms
]) => {
@@ -375,18 +394,28 @@ export class RoomsEffects {
return of(RoomsActions.joinRoomFailure({ error: 'Not logged in' }));
}
+ const activateViewedRoom = () => {
+ const oderId = user.oderId || this.webrtc.peerId();
+
+ void this.connectToRoomSignaling(room, user, oderId, savedRooms, {
+ showCompatibilityError: true
+ });
+
+ this.router.navigate(['/room', room.id]);
+ return of(RoomsActions.viewServerSuccess({ room }));
+ };
+
+ if (skipBanCheck) {
+ return activateViewedRoom();
+ }
+
return from(this.getBlockedRoomAccessActions(room.id, user)).pipe(
switchMap((blockedActions) => {
if (blockedActions.length > 0) {
return from(blockedActions);
}
- const oderId = user.oderId || this.webrtc.peerId();
-
- this.connectToRoomSignaling(room, user, oderId, savedRooms);
-
- this.router.navigate(['/room', room.id]);
- return of(RoomsActions.viewServerSuccess({ room }));
+ return activateViewedRoom();
}),
catchError((error) => of(RoomsActions.joinRoomFailure({ error: error.message })))
);
@@ -1317,48 +1346,64 @@ export class RoomsEffects {
{ dispatch: false }
);
- private connectToRoomSignaling(
+ private async connectToRoomSignaling(
room: Room,
user: User | null,
resolvedOderId?: string,
- savedRooms: Room[] = []
- ): void {
+ savedRooms: Room[] = [],
+ options: { showCompatibilityError?: boolean } = {}
+ ): Promise {
+ const shouldShowCompatibilityError = options.showCompatibilityError ?? false;
+ const compatibilitySelector = this.resolveCompatibilitySelector(room);
+ const isCompatible = compatibilitySelector === null
+ ? true
+ : await this.serverDirectory.ensureEndpointVersionCompatibility(compatibilitySelector);
+
+ if (!isCompatible) {
+ if (shouldShowCompatibilityError) {
+ this.store.dispatch(
+ RoomsActions.setSignalServerCompatibilityError({ message: CLIENT_UPDATE_REQUIRED_MESSAGE })
+ );
+ }
+
+ this.store.dispatch(RoomsActions.setSignalServerReconnecting({ isReconnecting: false }));
+ return;
+ }
+
+ if (shouldShowCompatibilityError) {
+ this.store.dispatch(RoomsActions.setSignalServerCompatibilityError({ message: null }));
+ }
+
const wsUrl = this.serverDirectory.getWebSocketUrl({
sourceId: room.sourceId,
sourceUrl: room.sourceUrl
});
- const currentWsUrl = this.webrtc.getCurrentSignalingUrl();
const oderId = resolvedOderId || user?.oderId || this.webrtc.peerId();
const displayName = user?.displayName || 'Anonymous';
- const sameSignalServer = currentWsUrl === wsUrl;
const sameSignalRooms = this.getRoomsForSignalingUrl(this.includeRoom(savedRooms, room), wsUrl);
const backgroundRooms = sameSignalRooms.filter((candidate) => candidate.id !== room.id);
const joinCurrentEndpointRooms = () => {
this.webrtc.setCurrentServer(room.id);
- this.webrtc.identify(oderId, displayName);
+ this.webrtc.identify(oderId, displayName, wsUrl);
for (const backgroundRoom of backgroundRooms) {
if (!this.webrtc.hasJoinedServer(backgroundRoom.id)) {
- this.webrtc.joinRoom(backgroundRoom.id, oderId);
+ this.webrtc.joinRoom(backgroundRoom.id, oderId, wsUrl);
}
}
if (this.webrtc.hasJoinedServer(room.id)) {
- this.webrtc.switchServer(room.id, oderId);
+ this.webrtc.switchServer(room.id, oderId, wsUrl);
} else {
- this.webrtc.joinRoom(room.id, oderId);
+ this.webrtc.joinRoom(room.id, oderId, wsUrl);
}
};
- if (this.webrtc.isConnected() && sameSignalServer) {
+ if (this.webrtc.isSignalingConnectedTo(wsUrl)) {
joinCurrentEndpointRooms();
return;
}
- if (currentWsUrl && currentWsUrl !== wsUrl) {
- this.webrtc.disconnectAll();
- }
-
this.webrtc.connectToSignalingServer(wsUrl).subscribe({
next: (connected) => {
if (!connected)
@@ -1376,20 +1421,66 @@ export class RoomsEffects {
}
const watchedRoomId = this.extractRoomIdFromUrl(this.router.url);
- const currentWsUrl = this.webrtc.getCurrentSignalingUrl();
- const targetRoom = (watchedRoomId
- ? savedRooms.find((room) => room.id === watchedRoomId) ?? null
- : null)
- ?? (currentWsUrl ? this.findRoomBySignalingUrl(savedRooms, currentWsUrl) : null)
- ?? currentRoom
- ?? savedRooms[0]
- ?? null;
+ const roomsToSync = currentRoom ? this.includeRoom(savedRooms, currentRoom) : savedRooms;
+ const roomsBySignalingUrl = new Map();
- if (!targetRoom) {
- return;
+ for (const room of roomsToSync) {
+ const wsUrl = this.serverDirectory.getWebSocketUrl({
+ sourceId: room.sourceId,
+ sourceUrl: room.sourceUrl
+ });
+ const groupedRooms = roomsBySignalingUrl.get(wsUrl) ?? [];
+
+ if (!groupedRooms.some((groupedRoom) => groupedRoom.id === room.id)) {
+ groupedRooms.push(room);
+ }
+
+ roomsBySignalingUrl.set(wsUrl, groupedRooms);
}
- this.connectToRoomSignaling(targetRoom, user, user.oderId || this.webrtc.peerId(), savedRooms);
+ for (const groupedRooms of roomsBySignalingUrl.values()) {
+ const preferredRoom = groupedRooms.find((room) => room.id === watchedRoomId)
+ ?? (currentRoom && groupedRooms.some((room) => room.id === currentRoom.id)
+ ? currentRoom
+ : null)
+ ?? groupedRooms[0]
+ ?? null;
+
+ if (!preferredRoom) {
+ continue;
+ }
+
+ const shouldShowCompatibilityError = preferredRoom.id === watchedRoomId
+ || (!!currentRoom && preferredRoom.id === currentRoom.id);
+
+ void this.connectToRoomSignaling(preferredRoom, user, user.oderId || this.webrtc.peerId(), roomsToSync, {
+ showCompatibilityError: shouldShowCompatibilityError
+ });
+ }
+ }
+
+ private resolveCompatibilitySelector(room: Room): ServerSourceSelector | undefined | null {
+ if (room.sourceId) {
+ const endpointById = this.serverDirectory.servers().find((entry) => entry.id === room.sourceId);
+
+ if (endpointById) {
+ return { sourceId: room.sourceId };
+ }
+
+ if (room.sourceUrl && this.serverDirectory.findServerByUrl(room.sourceUrl)) {
+ return { sourceUrl: room.sourceUrl };
+ }
+
+ return null;
+ }
+
+ if (room.sourceUrl) {
+ return this.serverDirectory.findServerByUrl(room.sourceUrl)
+ ? { sourceUrl: room.sourceUrl }
+ : null;
+ }
+
+ return undefined;
}
private includeRoom(rooms: Room[], room: Room): Room[] {
@@ -1421,10 +1512,6 @@ export class RoomsEffects {
return matchingRooms;
}
- private findRoomBySignalingUrl(rooms: Room[], wsUrl: string): Room | null {
- return this.getRoomsForSignalingUrl(rooms, wsUrl)[0] ?? null;
- }
-
private extractRoomIdFromUrl(url: string): string | null {
const roomMatch = url.match(ROOM_URL_PATTERN);
diff --git a/src/app/store/rooms/rooms.reducer.ts b/src/app/store/rooms/rooms.reducer.ts
index b62f1cb..1e7ac0a 100644
--- a/src/app/store/rooms/rooms.reducer.ts
+++ b/src/app/store/rooms/rooms.reducer.ts
@@ -84,6 +84,8 @@ export interface RoomsState {
isConnected: boolean;
/** Whether the current room is using locally cached data while reconnecting. */
isSignalServerReconnecting: boolean;
+ /** Banner message when the viewed room's signaling endpoint is incompatible. */
+ signalServerCompatibilityError: string | null;
/** Whether rooms are being loaded from local storage. */
loading: boolean;
/** Most recent error message, if any. */
@@ -101,6 +103,7 @@ export const initialState: RoomsState = {
isConnecting: false,
isConnected: false,
isSignalServerReconnecting: false,
+ signalServerCompatibilityError: null,
loading: false,
error: null,
activeChannelId: 'general'
@@ -151,6 +154,7 @@ export const roomsReducer = createReducer(
on(RoomsActions.createRoom, (state) => ({
...state,
isConnecting: true,
+ signalServerCompatibilityError: null,
error: null
})),
@@ -163,6 +167,7 @@ export const roomsReducer = createReducer(
savedRooms: upsertRoom(state.savedRooms, enriched),
isConnecting: false,
isSignalServerReconnecting: false,
+ signalServerCompatibilityError: null,
isConnected: true,
activeChannelId: 'general'
};
@@ -178,6 +183,7 @@ export const roomsReducer = createReducer(
on(RoomsActions.joinRoom, (state) => ({
...state,
isConnecting: true,
+ signalServerCompatibilityError: null,
error: null
})),
@@ -190,6 +196,7 @@ export const roomsReducer = createReducer(
savedRooms: upsertRoom(state.savedRooms, enriched),
isConnecting: false,
isSignalServerReconnecting: false,
+ signalServerCompatibilityError: null,
isConnected: true,
activeChannelId: 'general'
};
@@ -212,6 +219,7 @@ export const roomsReducer = createReducer(
currentRoom: null,
roomSettings: null,
isSignalServerReconnecting: false,
+ signalServerCompatibilityError: null,
isConnecting: false,
isConnected: false
})),
@@ -220,6 +228,7 @@ export const roomsReducer = createReducer(
on(RoomsActions.viewServer, (state) => ({
...state,
isConnecting: true,
+ signalServerCompatibilityError: null,
error: null
})),
@@ -231,6 +240,7 @@ export const roomsReducer = createReducer(
currentRoom: enriched,
savedRooms: upsertRoom(state.savedRooms, enriched),
isConnecting: false,
+ signalServerCompatibilityError: null,
isConnected: true,
activeChannelId: 'general'
};
@@ -286,6 +296,7 @@ export const roomsReducer = createReducer(
on(RoomsActions.deleteRoomSuccess, (state, { roomId }) => ({
...state,
isSignalServerReconnecting: state.currentRoom?.id === roomId ? false : state.isSignalServerReconnecting,
+ signalServerCompatibilityError: state.currentRoom?.id === roomId ? null : state.signalServerCompatibilityError,
savedRooms: state.savedRooms.filter((room) => room.id !== roomId),
currentRoom: state.currentRoom?.id === roomId ? null : state.currentRoom
})),
@@ -294,6 +305,7 @@ export const roomsReducer = createReducer(
on(RoomsActions.forgetRoomSuccess, (state, { roomId }) => ({
...state,
isSignalServerReconnecting: state.currentRoom?.id === roomId ? false : state.isSignalServerReconnecting,
+ signalServerCompatibilityError: state.currentRoom?.id === roomId ? null : state.signalServerCompatibilityError,
savedRooms: state.savedRooms.filter((room) => room.id !== roomId),
currentRoom: state.currentRoom?.id === roomId ? null : state.currentRoom
})),
@@ -304,6 +316,7 @@ export const roomsReducer = createReducer(
currentRoom: enrichRoom(room),
savedRooms: upsertRoom(state.savedRooms, room),
isSignalServerReconnecting: false,
+ signalServerCompatibilityError: null,
isConnected: true
})),
@@ -313,6 +326,7 @@ export const roomsReducer = createReducer(
currentRoom: null,
roomSettings: null,
isSignalServerReconnecting: false,
+ signalServerCompatibilityError: null,
isConnected: false
})),
@@ -382,6 +396,11 @@ export const roomsReducer = createReducer(
isSignalServerReconnecting: isReconnecting
})),
+ on(RoomsActions.setSignalServerCompatibilityError, (state, { message }) => ({
+ ...state,
+ signalServerCompatibilityError: message
+ })),
+
// Channel management
on(RoomsActions.selectChannel, (state, { channelId }) => ({
...state,
diff --git a/src/app/store/rooms/rooms.selectors.ts b/src/app/store/rooms/rooms.selectors.ts
index c1267d2..7dd9dd9 100644
--- a/src/app/store/rooms/rooms.selectors.ts
+++ b/src/app/store/rooms/rooms.selectors.ts
@@ -30,6 +30,10 @@ export const selectIsSignalServerReconnecting = createSelector(
selectRoomsState,
(state) => state.isSignalServerReconnecting
);
+export const selectSignalServerCompatibilityError = createSelector(
+ selectRoomsState,
+ (state) => state.signalServerCompatibilityError
+);
export const selectRoomsError = createSelector(
selectRoomsState,
(state) => state.error
diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts
index a95d37e..5e0ffcb 100644
--- a/src/environments/environment.prod.ts
+++ b/src/environments/environment.prod.ts
@@ -1,4 +1,16 @@
export const environment = {
production: true,
+ defaultServers: [
+ {
+ key: 'toju-primary',
+ name: 'Toju Signal',
+ url: 'https://signal.toju.app'
+ },
+ {
+ key: 'toju-sweden',
+ name: 'Toju Signal Sweden',
+ url: 'https://signal-sweden.toju.app'
+ }
+ ],
defaultServerUrl: 'https://signal.toju.app'
};
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
index e150095..7093d8a 100644
--- a/src/environments/environment.ts
+++ b/src/environments/environment.ts
@@ -1,4 +1,21 @@
export const environment = {
production: false,
+ defaultServers: [
+ {
+ key: 'default',
+ name: 'Default Server',
+ url: 'https://46.59.68.77:3001'
+ },
+ {
+ key: 'toju-primary',
+ name: 'Toju Signal',
+ url: 'https://signal.toju.app'
+ },
+ {
+ key: 'toju-sweden',
+ name: 'Toju Signal Sweden',
+ url: 'https://signal-sweden.toju.app'
+ }
+ ],
defaultServerUrl: 'https://46.59.68.77:3001'
};