diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/guards/extract-params.guard.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/guards/extract-params.guard.ts new file mode 100644 index 00000000..37d1121a --- /dev/null +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/guards/extract-params.guard.ts @@ -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; +}; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/guards/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/guards/index.ts new file mode 100644 index 00000000..1d6bccbc --- /dev/null +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/guards/index.ts @@ -0,0 +1,2 @@ +export * from './extract-params.guard'; +export * from './validate-room-access.guard'; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/guards/room-validate-access.guard.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/guards/validate-room-access.guard.ts similarity index 80% rename from meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/guards/room-validate-access.guard.ts rename to meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/guards/validate-room-access.guard.ts index 1151ab36..40bd6001 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/guards/room-validate-access.guard.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/guards/validate-room-access.guard.ts @@ -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); diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/index.ts index 9f8a0c90..9e34c4ab 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/index.ts @@ -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'; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/providers/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/providers/index.ts deleted file mode 100644 index 31149417..00000000 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/providers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './meeting-context-adapter.provider'; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/providers/meeting-context-adapter.provider.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/providers/meeting-context-adapter.provider.ts deleted file mode 100644 index a20be7cd..00000000 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/providers/meeting-context-adapter.provider.ts +++ /dev/null @@ -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 -}; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/routes/meeting.routes.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/routes/meeting.routes.ts index e8fd20fa..81a9b49a 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/routes/meeting.routes.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/routes/meeting.routes.ts @@ -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]) ) diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/guards/extract-params.guard.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/guards/extract-params.guard.ts new file mode 100644 index 00000000..a4cc8c8e --- /dev/null +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/guards/extract-params.guard.ts @@ -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; +}; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/guards/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/guards/index.ts index db69384f..0ea52a60 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/guards/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/guards/index.ts @@ -1 +1,2 @@ +export * from './extract-params.guard'; export * from './recording-validate-access.guard'; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/guards/recording-validate-access.guard.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/guards/recording-validate-access.guard.ts index c80e00dd..881fe7ee 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/guards/recording-validate-access.guard.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/guards/recording-validate-access.guard.ts @@ -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); diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/routes/recordings.routes.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/routes/recordings.routes.ts index 7c22c405..32c9ca2e 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/routes/recordings.routes.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/routes/recordings.routes.ts @@ -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']) ) ] } diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/index.ts index c287d65a..45732831 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/index.ts @@ -1,3 +1,2 @@ export * from './interceptor-handlers'; -export * from './providers'; export * from './services'; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/providers/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/providers/index.ts deleted file mode 100644 index 8d78d59a..00000000 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/providers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './room-member-adapter.provider'; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/providers/room-member-adapter.provider.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/providers/room-member-adapter.provider.ts deleted file mode 100644 index 9f530e85..00000000 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/providers/room-member-adapter.provider.ts +++ /dev/null @@ -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 -}; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/guards/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/guards/index.ts index 66205e16..e882b38f 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/guards/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/guards/index.ts @@ -1,2 +1 @@ export * from './room-edit-check.guard'; -export * from './room-validate-access.guard'; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/adapters/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/adapters/index.ts deleted file mode 100644 index 70ff8dcd..00000000 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/adapters/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './meeting-context.adapter'; -export * from './room-member.adapter'; - diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/adapters/meeting-context.adapter.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/adapters/meeting-context.adapter.ts deleted file mode 100644 index 93e56ce0..00000000 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/adapters/meeting-context.adapter.ts +++ /dev/null @@ -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('MEETING_CONTEXT_ADAPTER'); diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/adapters/room-member.adapter.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/adapters/room-member.adapter.ts deleted file mode 100644 index d1ef9739..00000000 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/adapters/room-member.adapter.ts +++ /dev/null @@ -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('ROOM_MEMBER_ADAPTER'); diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/guards/extract-query-params.guard.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/guards/extract-query-params.guard.ts deleted file mode 100644 index bd9e2694..00000000 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/guards/extract-query-params.guard.ts +++ /dev/null @@ -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; - } -}; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/guards/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/guards/index.ts index 66c45b3d..71c9aec1 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/guards/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/guards/index.ts @@ -1,4 +1,2 @@ -export * from './extract-query-params.guard'; export * from './remove-query-params.guard'; export * from './run-serially.guard'; - diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/index.ts index 7019bf96..3c4c359a 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/index.ts @@ -1,4 +1,3 @@ -export * from './adapters'; export * from './components'; export * from './guards'; export * from './interceptors'; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/utils/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/utils/index.ts index 89fe6278..bcda7740 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/utils/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/utils/index.ts @@ -1,4 +1,4 @@ export * from './array.utils'; export * from './format.utils'; export * from './token.utils'; - +export * from './url-params.utils'; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/utils/url-params.utils.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/utils/url-params.utils.ts new file mode 100644 index 00000000..95b317f8 --- /dev/null +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/utils/url-params.utils.ts @@ -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; + } +}; diff --git a/meet-ce/frontend/src/app/app.config.ts b/meet-ce/frontend/src/app/app.config.ts index bb1106e3..f56269fb 100644 --- a/meet-ce/frontend/src/app/app.config.ts +++ b/meet-ce/frontend/src/app/app.config.ts @@ -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(),