frontend: move room and recording parameter extraction guards to its corresponding domain folder and refactor code
This commit is contained in:
parent
5bb9a2f3e1
commit
9f46d03646
@ -0,0 +1,47 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivateFn } from '@angular/router';
|
||||
import { NavigationService } from '../../../shared/services/navigation.service';
|
||||
import { SessionStorageService } from '../../../shared/services/session-storage.service';
|
||||
import { extractParams, handleLeaveRedirectUrl } from '../../../shared/utils/url-params.utils';
|
||||
import { RoomMemberContextService } from '../../room-members/services/room-member-context.service';
|
||||
import { MeetingContextService } from '../services/meeting-context.service';
|
||||
|
||||
export const extractRoomParamsGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => {
|
||||
const navigationService = inject(NavigationService);
|
||||
const meetingContextService = inject(MeetingContextService);
|
||||
const roomMemberContextService = inject(RoomMemberContextService);
|
||||
const sessionStorageService = inject(SessionStorageService);
|
||||
|
||||
const {
|
||||
roomId,
|
||||
secret: querySecret,
|
||||
participantName,
|
||||
leaveRedirectUrl,
|
||||
showOnlyRecordings,
|
||||
e2eeKey: queryE2eeKey
|
||||
} = extractParams(route);
|
||||
const secret = querySecret || sessionStorageService.getRoomSecret();
|
||||
const e2eeKey = queryE2eeKey || sessionStorageService.getE2EEKey();
|
||||
|
||||
// Handle leave redirect URL logic
|
||||
handleLeaveRedirectUrl(leaveRedirectUrl);
|
||||
|
||||
// Save parameters in the meeting context and room member context services
|
||||
meetingContextService.setRoomId(roomId);
|
||||
if (secret) {
|
||||
meetingContextService.setRoomSecret(secret, true);
|
||||
}
|
||||
if (e2eeKey) {
|
||||
meetingContextService.setE2eeKey(e2eeKey);
|
||||
}
|
||||
if (participantName) {
|
||||
roomMemberContextService.setParticipantName(participantName);
|
||||
}
|
||||
|
||||
// If the showOnlyRecordings flag is set, redirect to the recordings page for the room
|
||||
if (showOnlyRecordings === 'true') {
|
||||
return navigationService.createRedirectionTo(`room/${roomId}/recordings`, { secret });
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
@ -0,0 +1,2 @@
|
||||
export * from './extract-params.guard';
|
||||
export * from './validate-room-access.guard';
|
||||
@ -1,10 +1,9 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from '@angular/router';
|
||||
import { NavigationErrorReason } from '../../../shared/models/navigation.model';
|
||||
import { RoomMemberContextService } from '../../room-members/services/room-member-context.service';
|
||||
import { NavigationService } from '../../../shared/services/navigation.service';
|
||||
import { MeetingContextService } from '../../meeting/services/meeting-context.service';
|
||||
|
||||
import { RoomMemberContextService } from '../../room-members/services/room-member-context.service';
|
||||
import { MeetingContextService } from '../services/meeting-context.service';
|
||||
|
||||
/**
|
||||
* Guard to validate access to a room by generating a room member token.
|
||||
@ -38,16 +37,12 @@ const validateRoomAccessInternal = async (pageUrl: string, validateRecordingPerm
|
||||
const navigationService = inject(NavigationService);
|
||||
const meetingContextService = inject(MeetingContextService);
|
||||
|
||||
const secret = meetingContextService.roomSecret();
|
||||
const roomId = meetingContextService.roomId();
|
||||
if (!roomId) {
|
||||
console.error('Cannot validate room access: room ID is undefined');
|
||||
return navigationService.redirectToErrorPage(NavigationErrorReason.INVALID_ROOM);
|
||||
}
|
||||
const secret = meetingContextService.roomSecret();
|
||||
if (!secret) {
|
||||
console.error('Cannot validate room access: room secret is undefined');
|
||||
return navigationService.redirectToErrorPage(NavigationErrorReason.MISSING_ROOM_SECRET);
|
||||
}
|
||||
|
||||
try {
|
||||
await roomMemberService.generateToken(roomId, {
|
||||
@ -59,13 +54,14 @@ const validateRoomAccessInternal = async (pageUrl: string, validateRecordingPerm
|
||||
if (validateRecordingPermissions) {
|
||||
if (!roomMemberService.hasPermission('canRetrieveRecordings')) {
|
||||
// If the user does not have permission to retrieve recordings, redirect to the error page
|
||||
return navigationService.redirectToErrorPage(NavigationErrorReason.UNAUTHORIZED_RECORDING_ACCESS);
|
||||
return navigationService.redirectToErrorPage(NavigationErrorReason.FORBIDDEN_RECORDING_ACCESS);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
console.error('Error generating room member token:', error);
|
||||
const message = error?.error?.message || error.message || 'Unknown error';
|
||||
switch (error.status) {
|
||||
case 400:
|
||||
// Invalid secret
|
||||
@ -74,8 +70,17 @@ const validateRoomAccessInternal = async (pageUrl: string, validateRecordingPerm
|
||||
// Unauthorized access
|
||||
// Redirect to the login page with query param to redirect back to the page
|
||||
return navigationService.redirectToLoginPage(pageUrl);
|
||||
case 403:
|
||||
// Insufficient permissions or anonymous access disabled
|
||||
if (message.includes('Anonymous access')) {
|
||||
return navigationService.redirectToErrorPage(NavigationErrorReason.ANONYMOUS_ACCESS_DISABLED);
|
||||
}
|
||||
return navigationService.redirectToErrorPage(NavigationErrorReason.FORBIDDEN_ROOM_ACCESS);
|
||||
case 404:
|
||||
// Room not found
|
||||
// Room or member not found
|
||||
if (message.includes('Room member')) {
|
||||
return navigationService.redirectToErrorPage(NavigationErrorReason.INVALID_MEMBER);
|
||||
}
|
||||
return navigationService.redirectToErrorPage(NavigationErrorReason.INVALID_ROOM);
|
||||
default:
|
||||
return navigationService.redirectToErrorPage(NavigationErrorReason.INTERNAL_ERROR);
|
||||
@ -1,6 +1,6 @@
|
||||
export * from './components';
|
||||
export * from './customization';
|
||||
export * from './guards';
|
||||
export * from './models';
|
||||
export * from './pages';
|
||||
export * from './providers';
|
||||
export * from './services';
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export * from './meeting-context-adapter.provider';
|
||||
@ -1,12 +0,0 @@
|
||||
import { Provider } from '@angular/core';
|
||||
import { MEETING_CONTEXT_ADAPTER } from '../../../shared/adapters';
|
||||
import { MeetingContextService } from '../services/meeting-context.service';
|
||||
|
||||
/**
|
||||
* Provides the MeetingContextAdapter using the existing MeetingContextService.
|
||||
* This allows shared guards to use the adapter interface without depending on domain services.
|
||||
*/
|
||||
export const MEETING_CONTEXT_ADAPTER_PROVIDER: Provider = {
|
||||
provide: MEETING_CONTEXT_ADAPTER,
|
||||
useExisting: MeetingContextService
|
||||
};
|
||||
@ -1,9 +1,9 @@
|
||||
import { WebComponentProperty } from '@openvidu-meet/typings';
|
||||
import { extractRoomQueryParamsGuard } from '../../../shared/guards/extract-query-params.guard';
|
||||
import { removeQueryParamsGuard } from '../../../shared/guards/remove-query-params.guard';
|
||||
import { runGuardsSerially } from '../../../shared/guards/run-serially.guard';
|
||||
import { DomainRouteConfig } from '../../../shared/models/domain-routes.model';
|
||||
import { validateRoomAccessGuard } from '../../rooms/guards/room-validate-access.guard';
|
||||
import { extractRoomParamsGuard } from '../guards/extract-params.guard';
|
||||
import { validateRoomAccessGuard } from '../guards/validate-room-access.guard';
|
||||
|
||||
/**
|
||||
* Meeting domain route configurations
|
||||
@ -15,7 +15,7 @@ export const meetingDomainRoutes: DomainRouteConfig[] = [
|
||||
loadComponent: () => import('../pages/meeting/meeting.component').then((m) => m.MeetingComponent),
|
||||
canActivate: [
|
||||
runGuardsSerially(
|
||||
extractRoomQueryParamsGuard,
|
||||
extractRoomParamsGuard,
|
||||
validateRoomAccessGuard,
|
||||
removeQueryParamsGuard(['secret', WebComponentProperty.E2EE_KEY])
|
||||
)
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivateFn } from '@angular/router';
|
||||
import { SessionStorageService } from '../../../shared/services/session-storage.service';
|
||||
import { extractParams } from '../../../shared/utils/url-params.utils';
|
||||
import { MeetingContextService } from '../../meeting/services/meeting-context.service';
|
||||
|
||||
export const extractRoomRecordingsParamsGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => {
|
||||
const meetingContextService = inject(MeetingContextService);
|
||||
const sessionStorageService = inject(SessionStorageService);
|
||||
|
||||
const { roomId, secret: querySecret } = extractParams(route);
|
||||
const secret = querySecret || sessionStorageService.getRoomSecret();
|
||||
|
||||
// Save parameters in the meeting context service
|
||||
meetingContextService.setRoomId(roomId);
|
||||
if (secret) {
|
||||
meetingContextService.setRoomSecret(secret, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
@ -1 +1,2 @@
|
||||
export * from './extract-params.guard';
|
||||
export * from './recording-validate-access.guard';
|
||||
|
||||
@ -17,11 +17,6 @@ export const validateRecordingAccessGuard: CanActivateFn = async (
|
||||
const recordingId = route.params['recording-id'];
|
||||
const secret = route.queryParams['secret'];
|
||||
|
||||
if (!secret) {
|
||||
// If no secret is provided, redirect to the error page
|
||||
return navigationService.redirectToErrorPage(NavigationErrorReason.MISSING_RECORDING_SECRET);
|
||||
}
|
||||
|
||||
try {
|
||||
// Attempt to access the recording to check if the secret is valid
|
||||
await recordingService.getRecording(recordingId, secret);
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { WebComponentProperty } from '@openvidu-meet/typings';
|
||||
import { extractRecordingQueryParamsGuard } from '../../../shared/guards/extract-query-params.guard';
|
||||
import { removeQueryParamsGuard } from '../../../shared/guards/remove-query-params.guard';
|
||||
import { runGuardsSerially } from '../../../shared/guards/run-serially.guard';
|
||||
import { DomainRouteConfig } from '../../../shared/models/domain-routes.model';
|
||||
import { validateRoomRecordingsAccessGuard } from '../../rooms/guards/room-validate-access.guard';
|
||||
import { validateRoomRecordingsAccessGuard } from '../../meeting/guards/validate-room-access.guard';
|
||||
import { extractRoomRecordingsParamsGuard } from '../guards/extract-params.guard';
|
||||
import { validateRecordingAccessGuard } from '../guards/recording-validate-access.guard';
|
||||
|
||||
/**
|
||||
@ -19,9 +18,9 @@ export const recordingsDomainRoutes: DomainRouteConfig[] = [
|
||||
),
|
||||
canActivate: [
|
||||
runGuardsSerially(
|
||||
extractRecordingQueryParamsGuard,
|
||||
extractRoomRecordingsParamsGuard,
|
||||
validateRoomRecordingsAccessGuard,
|
||||
removeQueryParamsGuard(['secret', WebComponentProperty.E2EE_KEY])
|
||||
removeQueryParamsGuard(['secret'])
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
export * from './interceptor-handlers';
|
||||
export * from './providers';
|
||||
export * from './services';
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export * from './room-member-adapter.provider';
|
||||
@ -1,12 +0,0 @@
|
||||
import { Provider } from '@angular/core';
|
||||
import { ROOM_MEMBER_ADAPTER } from '../../../shared/adapters';
|
||||
import { RoomMemberContextService } from '../../room-members/services/room-member-context.service';
|
||||
|
||||
/**
|
||||
* Provides the RoomMemberAdapter using the existing RoomMemberService.
|
||||
* This allows shared guards to use the adapter interface without depending on domain services.
|
||||
*/
|
||||
export const ROOM_MEMBER_ADAPTER_PROVIDER: Provider = {
|
||||
provide: ROOM_MEMBER_ADAPTER,
|
||||
useExisting: RoomMemberContextService
|
||||
};
|
||||
@ -1,2 +1 @@
|
||||
export * from './room-edit-check.guard';
|
||||
export * from './room-validate-access.guard';
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
export * from './meeting-context.adapter';
|
||||
export * from './room-member.adapter';
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import { InjectionToken } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Adapter interface for meeting context operations.
|
||||
* This allows shared guards to interact with meeting context without directly depending on domain services.
|
||||
*/
|
||||
export interface MeetingContextAdapter {
|
||||
/**
|
||||
* Sets the room ID for the current meeting context
|
||||
*/
|
||||
setRoomId(roomId: string): void;
|
||||
|
||||
/**
|
||||
* Sets the room secret for the current meeting context
|
||||
*/
|
||||
setRoomSecret(secret: string, persistInStorage?: boolean): void;
|
||||
|
||||
/**
|
||||
* Sets the E2EE encryption key for the current meeting
|
||||
*/
|
||||
setE2eeKey(key: string): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection token for the MeetingContextAdapter
|
||||
*/
|
||||
export const MEETING_CONTEXT_ADAPTER = new InjectionToken<MeetingContextAdapter>('MEETING_CONTEXT_ADAPTER');
|
||||
@ -1,17 +0,0 @@
|
||||
import { InjectionToken } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Adapter interface for room member operations.
|
||||
* This allows shared guards to interact with room member context without directly depending on domain services.
|
||||
*/
|
||||
export interface RoomMemberAdapter {
|
||||
/**
|
||||
* Sets the participant name for the current room member
|
||||
*/
|
||||
setParticipantName(name: string): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection token for the RoomMemberAdapter
|
||||
*/
|
||||
export const ROOM_MEMBER_ADAPTER = new InjectionToken<RoomMemberAdapter>('ROOM_MEMBER_ADAPTER');
|
||||
@ -1,148 +0,0 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivateFn } from '@angular/router';
|
||||
import { WebComponentProperty } from '@openvidu-meet/typings';
|
||||
import { MEETING_CONTEXT_ADAPTER, ROOM_MEMBER_ADAPTER } from '../adapters';
|
||||
import { NavigationErrorReason } from '../models/navigation.model';
|
||||
import { AppDataService } from '../services/app-data.service';
|
||||
import { NavigationService } from '../services/navigation.service';
|
||||
import { SessionStorageService } from '../services/session-storage.service';
|
||||
|
||||
export const extractRoomQueryParamsGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => {
|
||||
const navigationService = inject(NavigationService);
|
||||
const meetingContextAdapter = inject(MEETING_CONTEXT_ADAPTER);
|
||||
const roomMemberAdapter = inject(ROOM_MEMBER_ADAPTER);
|
||||
const sessionStorageService = inject(SessionStorageService);
|
||||
|
||||
const {
|
||||
roomId,
|
||||
secret: querySecret,
|
||||
participantName,
|
||||
leaveRedirectUrl,
|
||||
showOnlyRecordings,
|
||||
e2eeKey: queryE2eeKey
|
||||
} = extractParams(route);
|
||||
const secret = querySecret || sessionStorageService.getRoomSecret();
|
||||
const e2eeKey = queryE2eeKey || sessionStorageService.getE2EEKey();
|
||||
|
||||
// Handle leave redirect URL logic
|
||||
handleLeaveRedirectUrl(leaveRedirectUrl);
|
||||
|
||||
if (!secret) {
|
||||
// If no secret is provided, redirect to the error page
|
||||
return navigationService.redirectToErrorPage(NavigationErrorReason.MISSING_ROOM_SECRET);
|
||||
}
|
||||
|
||||
meetingContextAdapter.setRoomId(roomId);
|
||||
meetingContextAdapter.setRoomSecret(secret, true);
|
||||
|
||||
if (e2eeKey) {
|
||||
meetingContextAdapter.setE2eeKey(e2eeKey);
|
||||
}
|
||||
|
||||
if (participantName) {
|
||||
roomMemberAdapter.setParticipantName(participantName);
|
||||
}
|
||||
|
||||
if (showOnlyRecordings === 'true') {
|
||||
// Redirect to the room recordings page
|
||||
return navigationService.createRedirectionTo(`room/${roomId}/recordings`, { secret });
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const extractRecordingQueryParamsGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => {
|
||||
const navigationService = inject(NavigationService);
|
||||
const meetingContextAdapter = inject(MEETING_CONTEXT_ADAPTER);
|
||||
const sessionStorageService = inject(SessionStorageService);
|
||||
|
||||
const { roomId, secret: querySecret } = extractParams(route);
|
||||
const secret = querySecret || sessionStorageService.getRoomSecret();
|
||||
|
||||
if (!secret) {
|
||||
// If no secret is provided, redirect to the error page
|
||||
return navigationService.redirectToErrorPage(NavigationErrorReason.MISSING_ROOM_SECRET);
|
||||
}
|
||||
|
||||
meetingContextAdapter.setRoomId(roomId);
|
||||
meetingContextAdapter.setRoomSecret(secret, true);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const extractParams = ({ params, queryParams }: ActivatedRouteSnapshot) => ({
|
||||
roomId: params['room-id'] as string,
|
||||
secret: queryParams['secret'] as string,
|
||||
participantName: queryParams[WebComponentProperty.PARTICIPANT_NAME] as string,
|
||||
leaveRedirectUrl: queryParams[WebComponentProperty.LEAVE_REDIRECT_URL] as string,
|
||||
showOnlyRecordings: (queryParams[WebComponentProperty.SHOW_ONLY_RECORDINGS] as string) || 'false',
|
||||
e2eeKey: queryParams[WebComponentProperty.E2EE_KEY] as string
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles the leave redirect URL logic with automatic referrer detection
|
||||
*/
|
||||
const handleLeaveRedirectUrl = (leaveRedirectUrl: string | undefined) => {
|
||||
const navigationService = inject(NavigationService);
|
||||
const appDataService = inject(AppDataService);
|
||||
const isEmbeddedMode = appDataService.isEmbeddedMode();
|
||||
|
||||
// Explicit valid URL provided - use as is
|
||||
if (leaveRedirectUrl && isValidUrl(leaveRedirectUrl)) {
|
||||
navigationService.setLeaveRedirectUrl(leaveRedirectUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
// Absolute path provided in embedded mode - construct full URL based on parent origin
|
||||
if (isEmbeddedMode && leaveRedirectUrl?.startsWith('/')) {
|
||||
const parentUrl = document.referrer;
|
||||
const parentOrigin = new URL(parentUrl).origin;
|
||||
navigationService.setLeaveRedirectUrl(parentOrigin + leaveRedirectUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-detect from referrer (only if no explicit URL provided and not embedded)
|
||||
if (!leaveRedirectUrl && !isEmbeddedMode) {
|
||||
const autoRedirectUrl = getAutoRedirectUrl();
|
||||
if (autoRedirectUrl) {
|
||||
navigationService.setLeaveRedirectUrl(autoRedirectUrl);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Automatically detects if user came from another domain and returns appropriate redirect URL
|
||||
*/
|
||||
const getAutoRedirectUrl = (): string | null => {
|
||||
try {
|
||||
const referrer = document.referrer;
|
||||
|
||||
// No referrer means user typed URL directly or came from bookmark
|
||||
if (!referrer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const referrerUrl = new URL(referrer);
|
||||
const currentUrl = new URL(window.location.href);
|
||||
|
||||
// Check if referrer is from a different domain
|
||||
if (referrerUrl.origin !== currentUrl.origin) {
|
||||
console.log(`Auto-configuring leave redirect to referrer: ${referrer}`);
|
||||
return referrer;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.warn('Error detecting auto redirect URL:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const isValidUrl = (url: string) => {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@ -1,4 +1,2 @@
|
||||
export * from './extract-query-params.guard';
|
||||
export * from './remove-query-params.guard';
|
||||
export * from './run-serially.guard';
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
export * from './adapters';
|
||||
export * from './components';
|
||||
export * from './guards';
|
||||
export * from './interceptors';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export * from './array.utils';
|
||||
export * from './format.utils';
|
||||
export * from './token.utils';
|
||||
|
||||
export * from './url-params.utils';
|
||||
|
||||
@ -0,0 +1,82 @@
|
||||
import { ActivatedRouteSnapshot } from "@angular/router";
|
||||
import { WebComponentProperty } from "@openvidu-meet/typings";
|
||||
import { NavigationService } from "../services/navigation.service";
|
||||
import { AppDataService } from "../services/app-data.service";
|
||||
import { inject } from "@angular/core";
|
||||
|
||||
export const extractParams = ({ params, queryParams }: ActivatedRouteSnapshot) => ({
|
||||
roomId: params['room-id'] as string,
|
||||
secret: queryParams['secret'] as string | undefined,
|
||||
participantName: queryParams[WebComponentProperty.PARTICIPANT_NAME] as string | undefined,
|
||||
leaveRedirectUrl: queryParams[WebComponentProperty.LEAVE_REDIRECT_URL] as string | undefined,
|
||||
showOnlyRecordings: (queryParams[WebComponentProperty.SHOW_ONLY_RECORDINGS] as string) || 'false',
|
||||
e2eeKey: queryParams[WebComponentProperty.E2EE_KEY] as string | undefined
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles the leave redirect URL logic with automatic referrer detection
|
||||
*/
|
||||
export const handleLeaveRedirectUrl = (leaveRedirectUrl: string | undefined) => {
|
||||
const navigationService = inject(NavigationService);
|
||||
const appDataService = inject(AppDataService);
|
||||
const isEmbeddedMode = appDataService.isEmbeddedMode();
|
||||
|
||||
// Explicit valid URL provided - use as is
|
||||
if (leaveRedirectUrl && isValidUrl(leaveRedirectUrl)) {
|
||||
navigationService.setLeaveRedirectUrl(leaveRedirectUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
// Absolute path provided in embedded mode - construct full URL based on parent origin
|
||||
if (isEmbeddedMode && leaveRedirectUrl?.startsWith('/')) {
|
||||
const parentUrl = document.referrer;
|
||||
const parentOrigin = new URL(parentUrl).origin;
|
||||
navigationService.setLeaveRedirectUrl(parentOrigin + leaveRedirectUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-detect from referrer (only if no explicit URL provided and not embedded)
|
||||
if (!leaveRedirectUrl && !isEmbeddedMode) {
|
||||
const autoRedirectUrl = getAutoRedirectUrl();
|
||||
if (autoRedirectUrl) {
|
||||
navigationService.setLeaveRedirectUrl(autoRedirectUrl);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Automatically detects if user came from another domain and returns appropriate redirect URL
|
||||
*/
|
||||
const getAutoRedirectUrl = (): string | null => {
|
||||
try {
|
||||
const referrer = document.referrer;
|
||||
|
||||
// No referrer means user typed URL directly or came from bookmark
|
||||
if (!referrer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const referrerUrl = new URL(referrer);
|
||||
const currentUrl = new URL(window.location.href);
|
||||
|
||||
// Check if referrer is from a different domain
|
||||
if (referrerUrl.origin !== currentUrl.origin) {
|
||||
console.log(`Auto-configuring leave redirect to referrer: ${referrer}`);
|
||||
return referrer;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.warn('Error detecting auto redirect URL:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const isValidUrl = (url: string) => {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@ -15,9 +15,7 @@ import {
|
||||
AuthInterceptorErrorHandlerService,
|
||||
CustomParticipantModel,
|
||||
httpInterceptor,
|
||||
MEETING_CONTEXT_ADAPTER_PROVIDER,
|
||||
MeetingLayoutService,
|
||||
ROOM_MEMBER_ADAPTER_PROVIDER,
|
||||
RoomMemberInterceptorErrorHandlerService,
|
||||
ThemeService
|
||||
} from '@openvidu-meet/shared-components';
|
||||
@ -40,8 +38,6 @@ export const appConfig: ApplicationConfig = {
|
||||
provideAppInitializer(() => inject(RoomMemberInterceptorErrorHandlerService).init()),
|
||||
importProvidersFrom(OpenViduComponentsModule.forRoot(ovComponentsconfig)),
|
||||
{ provide: LayoutService, useClass: MeetingLayoutService },
|
||||
MEETING_CONTEXT_ADAPTER_PROVIDER,
|
||||
ROOM_MEMBER_ADAPTER_PROVIDER,
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideRouter(ceRoutes),
|
||||
provideAnimationsAsync(),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user