@placetime/corptime-conference 1.0.10 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@placetime/corptime-conference",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "React Native SDK from Corptime-Conference",
5
5
  "main": "index.tsx",
6
6
  "license": "Apache-2.0",
@@ -1,4 +1,4 @@
1
- import { SET_ROOM_NAME } from '../base/conference/actionTypes';
1
+ import { SET_ROOM_INFO } from '../base/conference/actionTypes';
2
2
  import { setRoom } from '../base/conference/actions';
3
3
  import { getConferenceState } from '../base/conference/functions';
4
4
  import {
@@ -36,6 +36,9 @@ import { maybeRedirectToTokenAuthUrl } from './actions.any';
36
36
  import { addTrackStateToURL, getDefaultURL } from './functions.native';
37
37
  import logger from './logger';
38
38
  import { IReloadNowOptions, IStore } from './types';
39
+ import {doGetJSON} from "../base/util/httpUtils";
40
+ import {setRoomInfo} from "../base/conference/actions.any";
41
+ import {basePageVisibilityChanged, conferenceNotFoundVisibilityChanged} from "../overlay/actions.web";
39
42
 
40
43
  export * from './actions.any';
41
44
 
@@ -153,26 +156,37 @@ export function appNavigate(uri?: string, options: IReloadNowOptions = {}) {
153
156
 
154
157
  dispatch(setLocationURL(locationURL));
155
158
  dispatch(setConfig(config));
156
- dispatch(setRoom(room));
157
159
 
158
- if (!room) {
159
- goBackToRoot(getState(), dispatch);
160
-
161
- return;
162
- }
163
-
164
- dispatch(createDesiredLocalTracks());
165
- dispatch(clearNotifications());
166
-
167
- if (!options.hidePrejoin && isPrejoinPageEnabled(getState())) {
168
- if (isUnsafeRoomWarningEnabled(getState()) && isInsecureRoomName(room)) {
169
- navigateRoot(screen.unsafeRoomWarning);
170
- } else {
171
- navigateRoot(screen.preJoin);
172
- }
160
+ if (room) {
161
+ const backendApi = config.hosts?.backendGuestApi || "https://jitsi.dev.placetime.host/backend/api";
162
+ doGetJSON(backendApi + "/v2/guest/conferences/rooms/info?cid=" + encodeURIComponent(room)).then(res => {
163
+ if (res.data) {
164
+ dispatch(setRoomInfo(res.data));
165
+ dispatch(setRoom(room));
166
+
167
+ dispatch(createDesiredLocalTracks());
168
+ dispatch(clearNotifications());
169
+
170
+ if (!options.hidePrejoin && isPrejoinPageEnabled(getState())) {
171
+ if (isUnsafeRoomWarningEnabled(getState()) && isInsecureRoomName(room)) {
172
+ navigateRoot(screen.unsafeRoomWarning);
173
+ } else {
174
+ navigateRoot(screen.preJoin);
175
+ }
176
+ } else {
177
+ dispatch(connect());
178
+ navigateRoot(screen.conference.root);
179
+ }
180
+ } else {
181
+ goBackToRoot(getState(), dispatch);
182
+ return;
183
+ }
184
+ }).catch(() => {
185
+ goBackToRoot(getState(), dispatch);
186
+ return;
187
+ });
173
188
  } else {
174
- dispatch(connect());
175
- navigateRoot(screen.conference.root);
189
+ goBackToRoot(getState(), dispatch);
176
190
  }
177
191
  };
178
192
  }
@@ -1,6 +1,6 @@
1
1
  // @ts-expect-error
2
2
  import { API_ID } from '../../../modules/API';
3
- import { setRoom, setRoomNameGlobal } from '../base/conference/actions.web';
3
+ import { setRoom, setRoomInfo } from '../base/conference/actions.web';
4
4
  import {
5
5
  configWillLoad,
6
6
  setConfig
@@ -79,13 +79,13 @@ export function appNavigate(uri?: string) {
79
79
  dispatch(configWillLoad(locationURL, room));
80
80
 
81
81
  const config = await loadConfig();
82
- dispatch(setLocationURL(locationURL));
83
82
  dispatch(setConfig(config));
83
+ dispatch(setLocationURL(locationURL));
84
84
  if (room) {
85
- const backendApi = config.hosts?.backendGuestApi || "https://jitsi.dev.placetime.host/backend/api/v2/guest";
86
- doGetJSON(backendApi + "/conferences/rooms/info?cid=" + encodeURIComponent(room)).then(res => {
87
- if (res.data.name) {
88
- dispatch(setRoomNameGlobal(res.data.name));
85
+ const backendApi = config.hosts?.backendGuestApi || "https://jitsi.dev.placetime.host/backend/api";
86
+ doGetJSON(backendApi + "/v2/guest/conferences/rooms/info?cid=" + encodeURIComponent(room)).then(res => {
87
+ if (res.data) {
88
+ dispatch(setRoomInfo(res.data));
89
89
  dispatch(setRoom(room));
90
90
  dispatch(conferenceNotFoundVisibilityChanged(false));
91
91
  } else {
@@ -14,7 +14,6 @@ import DimensionsDetector from '../../base/responsive-ui/components/DimensionsDe
14
14
  import { updateSettings } from '../../base/settings/actions';
15
15
  import JitsiThemePaperProvider from '../../base/ui/components/JitsiThemeProvider.native';
16
16
  import { isEmbedded } from '../../base/util/embedUtils.native';
17
- import { setRoomNameGlobal } from '../../base/conference/actions.any';
18
17
  import { _getRouteToRender } from '../getRouteToRender.native';
19
18
  import logger from '../logger';
20
19
 
@@ -149,7 +148,6 @@ export class App extends AbstractApp<IProps> {
149
148
  if (ready) {
150
149
  clearInterval(i);
151
150
  resolve();
152
- dispatch?.(setRoomNameGlobal(this.props.url.roomName));
153
151
  }
154
152
  }, 50);
155
153
  });
@@ -330,11 +330,11 @@ export const SET_ROOM = 'SET_ROOM';
330
330
  * Default string type room name.
331
331
  *
332
332
  * {
333
- * type: SET_ROOM_NAME,
333
+ * type: SET_ROOM_INFO,
334
334
  * room: string
335
335
  * }
336
336
  */
337
- export const SET_ROOM_NAME = 'SET_ROOM_NAME';
337
+ export const SET_ROOM_INFO = 'SET_ROOM_INFO';
338
338
 
339
339
  /**
340
340
  * The type of (redux) action which updates the current known status of the
@@ -63,7 +63,7 @@ import {
63
63
  SET_PASSWORD_FAILED,
64
64
  SET_PENDING_SUBJECT_CHANGE,
65
65
  SET_ROOM,
66
- SET_ROOM_NAME,
66
+ SET_ROOM_INFO,
67
67
  SET_START_MUTED_POLICY,
68
68
  SET_START_REACTIONS_MUTED,
69
69
  UPDATE_CONFERENCE_METADATA
@@ -974,10 +974,10 @@ export function setRoom(room?: string) {
974
974
  };
975
975
  }
976
976
 
977
- export function setRoomNameGlobal(roomName: string) {
977
+ export function setRoomInfo(roomInfo: any) {
978
978
  return {
979
- type: SET_ROOM_NAME,
980
- roomName,
979
+ type: SET_ROOM_INFO,
980
+ roomInfo,
981
981
  }
982
982
  }
983
983
 
@@ -30,7 +30,7 @@ import {
30
30
  SET_PASSWORD,
31
31
  SET_PENDING_SUBJECT_CHANGE,
32
32
  SET_ROOM,
33
- SET_ROOM_NAME,
33
+ SET_ROOM_INFO,
34
34
  SET_START_MUTED_POLICY,
35
35
  SET_START_REACTIONS_MUTED,
36
36
  UPDATE_CONFERENCE_METADATA
@@ -194,6 +194,7 @@ export interface IConferenceState {
194
194
  properties?: object;
195
195
  room?: string;
196
196
  roomName?: string;
197
+ roomInfo?: any;
197
198
  startAudioMutedPolicy?: boolean;
198
199
  startReactionsMuted?: boolean;
199
200
  startVideoMutedPolicy?: boolean;
@@ -293,8 +294,8 @@ ReducerRegistry.register<IConferenceState>('features/base/conference',
293
294
 
294
295
  case SET_ROOM:
295
296
  return _setRoom(state, action);
296
- case SET_ROOM_NAME:
297
- return _setRoomName(state, action);
297
+ case SET_ROOM_INFO:
298
+ return _setRoomInfo(state, action);
298
299
  case SET_START_MUTED_POLICY:
299
300
  return {
300
301
  ...state,
@@ -694,8 +695,8 @@ function _setRoom(state: IConferenceState, action: AnyAction) {
694
695
  });
695
696
  }
696
697
 
697
- function _setRoomName(state: IConferenceState, action: AnyAction) {
698
- let { roomName } = action;
698
+ function _setRoomInfo(state: IConferenceState, action: AnyAction) {
699
+ let { roomInfo } = action;
699
700
 
700
701
  /**
701
702
  * The name of the room of the conference (to be) joined.
@@ -704,6 +705,7 @@ function _setRoomName(state: IConferenceState, action: AnyAction) {
704
705
  */
705
706
  return assign(state, {
706
707
  error: undefined,
707
- roomName
708
+ roomName: roomInfo.name,
709
+ roomInfo: roomInfo
708
710
  });
709
711
  }
@@ -219,7 +219,7 @@ export function getWhitelistedJSON(configName: 'interfaceConfig' | 'config', con
219
219
  */
220
220
  export function isNameReadOnly(state: IReduxState): boolean {
221
221
  return Boolean(state['features/base/config'].disableProfile
222
- || state['features/base/config'].readOnlyName);
222
+ || state['features/base/config'].readOnlyName || state['features/base/jwt'].user?.name);
223
223
  }
224
224
 
225
225
  /**
@@ -111,6 +111,13 @@ function _overwriteLocalParticipant(
111
111
  }
112
112
  }
113
113
 
114
+ function getCookie(name: string): string | undefined {
115
+ const matches = document.cookie.match(new RegExp(
116
+ "(?:^|; )" + name.replace(/([.$?*|{}()[]\/+^])/g, '\\$1') + "=([^;]*)"
117
+ ));
118
+ return matches ? matches[1] : undefined;
119
+ }
120
+
114
121
  /**
115
122
  * Notifies the feature jwt that the action {@link SET_CONFIG} or
116
123
  * {@link SET_LOCATION_URL} is being dispatched within a specific redux
@@ -132,15 +139,16 @@ function _setConfigOrLocationURL({ dispatch, getState }: IStore, next: Function,
132
139
 
133
140
  const { locationURL } = getState()['features/base/connection'];
134
141
 
135
- let jwt = locationURL ? parseJWTFromURLParams(locationURL) : undefined;
136
- if (jwt) {
137
- jitsiLocalStorage.setItem('jwt', jwt ?? '');
138
- } else {
139
- jwt = jitsiLocalStorage.getItem('jwt');
142
+ let jwt = getCookie('jwt');
143
+ if (!jwt) {
144
+ jwt = locationURL && navigator.product === 'ReactNative' ? parseJWTFromURLParams(locationURL) : undefined;
140
145
  }
146
+
141
147
  if (jwt) {
142
- const backendApi = "https://jitsi.dev.placetime.host/backend/api/v1";
143
- doGetJSON(backendApi + "/users/me", undefined, {
148
+ const state = getState();
149
+ const { hosts } = state['features/base/config'];
150
+ const backendApi = hosts?.backendGuestApi || "https://jitsi.dev.placetime.host/backend/api";
151
+ doGetJSON(backendApi + "/v1/users/me", undefined, {
144
152
  headers: {
145
153
  'Authorization': 'Bearer ' + jwt,
146
154
  },
@@ -457,7 +457,7 @@ export function _mapStateToProps(state: IReduxState) {
457
457
  const showCopyUrlButton = inviteEnabledFlag || !disableInviteFunctions;
458
458
  const deviceStatusVisible = isDeviceStatusVisible(state);
459
459
  const { showHangUp = true } = getLobbyConfig(state);
460
- const { roomName, membersOnly, lobbyWaitingForHost } = state['features/base/conference'];
460
+ const { roomName, membersOnly, lobbyWaitingForHost, roomInfo } = state['features/base/conference'];
461
461
  const { isLobbyChatActive, lobbyMessageRecipient, messages } = state['features/chat'];
462
462
  const { showModeratorLogin } = state['features/authentication'];
463
463
 
@@ -476,7 +476,7 @@ export function _mapStateToProps(state: IReduxState) {
476
476
  _participantId: participantId,
477
477
  _participantName: localParticipant?.name,
478
478
  _passwordJoinFailed: passwordJoinFailed,
479
- _renderPassword: !iAmSipGateway && !lobbyWaitingForHost,
479
+ _renderPassword: !iAmSipGateway && !lobbyWaitingForHost && roomInfo.password_hash,
480
480
  showCopyUrlButton,
481
481
  roomName,
482
482
  };
@@ -42,7 +42,7 @@ const LobbyNavigationContainer = () => {
42
42
  && <LobbyStack.Screen
43
43
  component = { LobbyChatScreen }
44
44
  name = { screen.lobby.chat }
45
- options = { lobbyChatScreenOptions } />
45
+ options = {{ ...lobbyChatScreenOptions, title: i18next.t('lobby.chat') }} />
46
46
  }
47
47
  </LobbyStack.Navigator>
48
48
  </NavigationContainer>
@@ -274,8 +274,10 @@ const LocalRecordingManager: ILocalRecordingManager = {
274
274
  this.recorder.addEventListener('stop', async () => {
275
275
  const duration = Date.now() - this.startTime!;
276
276
 
277
- this.stream?.getTracks().forEach((track: MediaStreamTrack) => track.stop());
278
- gdmStream?.getTracks().forEach((track: MediaStreamTrack) => track.stop());
277
+ if (!onlySelf) {
278
+ this.stream?.getTracks().forEach((track: MediaStreamTrack) => track.stop());
279
+ gdmStream?.getTracks().forEach((track: MediaStreamTrack) => track.stop());
280
+ }
279
281
 
280
282
  // The stop event is emitted when the recorder is done, and _after_ the last buffered
281
283
  // data has been handed over to the dataavailable event.
@@ -47,10 +47,10 @@ MiddlewareRegistry.register(store => next => action => {
47
47
  // titleKey: 'notify.passwordSetRemotely'
48
48
  // }, NOTIFICATION_TIMEOUT_TYPE.SHORT));
49
49
  } else if (previousLockedState === LOCKED_REMOTELY && !currentLockedState) {
50
- store.dispatch(
51
- showNotification({
52
- titleKey: 'notify.passwordRemovedRemotely'
53
- }, NOTIFICATION_TIMEOUT_TYPE.SHORT));
50
+ // store.dispatch(
51
+ // showNotification({
52
+ // titleKey: 'notify.passwordRemovedRemotely'
53
+ // }, NOTIFICATION_TIMEOUT_TYPE.SHORT));
54
54
  }
55
55
 
56
56
  return result;
@@ -1,225 +0,0 @@
1
- import { AnyAction } from 'redux';
2
-
3
- import { IStore } from '../../app/types';
4
- import { SET_DYNAMIC_BRANDING_DATA } from '../../dynamic-branding/actionTypes';
5
- import { setUserFilmstripWidth } from '../../filmstrip/actions.web';
6
- import { getFeatureFlag } from '../flags/functions';
7
- import MiddlewareRegistry from '../redux/MiddlewareRegistry';
8
- import { updateSettings } from '../settings/actions';
9
-
10
- import { OVERWRITE_CONFIG, SET_CONFIG } from './actionTypes';
11
- import { updateConfig } from './actions';
12
- import { IConfig } from './configType';
13
-
14
- /**
15
- * The middleware of the feature {@code base/config}.
16
- *
17
- * @param {Store} store - The redux store.
18
- * @private
19
- * @returns {Function}
20
- */
21
- MiddlewareRegistry.register(store => next => action => {
22
- switch (action.type) {
23
- case SET_CONFIG:
24
- return _setConfig(store, next, action);
25
-
26
- case SET_DYNAMIC_BRANDING_DATA:
27
- return _setDynamicBrandingData(store, next, action);
28
-
29
- case OVERWRITE_CONFIG:
30
- return _updateSettings(store, next, action);
31
-
32
- }
33
-
34
- return next(action);
35
- });
36
-
37
- /**
38
- * Notifies the feature {@code base/config} that the {@link SET_CONFIG} redux
39
- * action is being {@code dispatch}ed in a specific redux store.
40
- *
41
- * @param {Store} store - The redux store in which the specified {@code action}
42
- * is being dispatched.
43
- * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
44
- * specified {@code action} in the specified {@code store}.
45
- * @param {Action} action - The redux action which is being {@code dispatch}ed
46
- * in the specified {@code store}.
47
- * @private
48
- * @returns {*} The return value of {@code next(action)}.
49
- */
50
- function _setConfig({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
51
- // The reducer is doing some alterations to the config passed in the action,
52
- // so make sure it's the final state by waiting for the action to be
53
- // reduced.
54
- const result = next(action);
55
- const state = getState();
56
-
57
- // Update the config with user defined settings.
58
- const settings = state['features/base/settings'];
59
- const config: IConfig = {};
60
-
61
- if (typeof settings.disableP2P !== 'undefined') {
62
- config.p2p = { enabled: !settings.disableP2P };
63
- }
64
-
65
- const resolutionFlag = getFeatureFlag(state, 'resolution');
66
-
67
- if (typeof resolutionFlag !== 'undefined') {
68
- config.resolution = resolutionFlag;
69
- }
70
-
71
- if (action.config.doNotFlipLocalVideo === true) {
72
- dispatch(updateSettings({
73
- localFlipX: false
74
- }));
75
- }
76
-
77
- if (action.config.disableSelfView !== undefined) {
78
- dispatch(updateSettings({
79
- disableSelfView: action.config.disableSelfView
80
- }));
81
- }
82
-
83
- const { initialWidth, stageFilmstripParticipants } = action.config.filmstrip || {};
84
-
85
- if (stageFilmstripParticipants !== undefined) {
86
- dispatch(updateSettings({
87
- maxStageParticipants: stageFilmstripParticipants
88
- }));
89
- }
90
-
91
- if (initialWidth) {
92
- dispatch(setUserFilmstripWidth(initialWidth));
93
- }
94
-
95
- dispatch(updateConfig(config));
96
-
97
- // FIXME On Web we rely on the global 'config' variable which gets altered
98
- // multiple times, before it makes it to the reducer. At some point it may
99
- // not be the global variable which is being modified anymore due to
100
- // different merge methods being used along the way. The global variable
101
- // must be synchronized with the final state resolved by the reducer.
102
- if (typeof window.config !== 'undefined') {
103
- window.config = state['features/base/config'];
104
- }
105
-
106
- return result;
107
- }
108
-
109
- /**
110
- * Updates config based on dynamic branding data.
111
- *
112
- * @param {Store} store - The redux store in which the specified {@code action}
113
- * is being dispatched.
114
- * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
115
- * specified {@code action} in the specified {@code store}.
116
- * @param {Action} action - The redux action which is being {@code dispatch}ed
117
- * in the specified {@code store}.
118
- * @private
119
- * @returns {*} The return value of {@code next(action)}.
120
- */
121
- function _setDynamicBrandingData({ dispatch }: IStore, next: Function, action: AnyAction) {
122
- const config: IConfig = {};
123
- const {
124
- customParticipantMenuButtons,
125
- customToolbarButtons,
126
- downloadAppsUrl,
127
- etherpadBase,
128
- liveStreamingDialogUrls = {},
129
- preCallTest = {},
130
- salesforceUrl,
131
- userDocumentationUrl,
132
- peopleSearchUrl,
133
- } = action.value;
134
-
135
- const { helpUrl, termsUrl, dataPrivacyUrl } = liveStreamingDialogUrls;
136
-
137
- if (helpUrl || termsUrl || dataPrivacyUrl) {
138
- config.liveStreaming = {};
139
- if (helpUrl) {
140
- config.liveStreaming.helpLink = helpUrl;
141
- }
142
-
143
- if (termsUrl) {
144
- config.liveStreaming.termsLink = termsUrl;
145
- }
146
-
147
- if (dataPrivacyUrl) {
148
- config.liveStreaming.dataPrivacyLink = dataPrivacyUrl;
149
- }
150
- }
151
-
152
- if (downloadAppsUrl || userDocumentationUrl) {
153
- config.deploymentUrls = {};
154
-
155
- if (downloadAppsUrl) {
156
- config.deploymentUrls.downloadAppsUrl = downloadAppsUrl;
157
- }
158
-
159
- if (userDocumentationUrl) {
160
- config.deploymentUrls.userDocumentationURL = userDocumentationUrl;
161
- }
162
- }
163
-
164
- if (salesforceUrl) {
165
- config.salesforceUrl = salesforceUrl;
166
- }
167
-
168
- if (peopleSearchUrl) {
169
- config.peopleSearchUrl = peopleSearchUrl;
170
- }
171
-
172
- const { enabled, iceUrl } = preCallTest;
173
-
174
- if (typeof enabled === 'boolean') {
175
- config.prejoinConfig = {
176
- preCallTestEnabled: enabled
177
- };
178
- }
179
-
180
- if (etherpadBase) {
181
- // eslint-disable-next-line camelcase
182
- config.etherpad_base = etherpadBase;
183
- }
184
-
185
- if (iceUrl) {
186
- config.prejoinConfig = config.prejoinConfig || {};
187
- config.prejoinConfig.preCallTestICEUrl = iceUrl;
188
- }
189
-
190
- if (customToolbarButtons) {
191
- config.customToolbarButtons = customToolbarButtons;
192
- }
193
-
194
- if (customParticipantMenuButtons) {
195
- config.customParticipantMenuButtons = customParticipantMenuButtons;
196
- }
197
-
198
- dispatch(updateConfig(config));
199
-
200
- return next(action);
201
- }
202
-
203
- /**
204
- * Updates settings based on some config values.
205
- *
206
- * @param {Store} store - The redux store in which the specified {@code action}
207
- * is being dispatched.
208
- * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
209
- * specified {@code action} in the specified {@code store}.
210
- * @param {Action} action - The redux action which is being {@code dispatch}ed
211
- * in the specified {@code store}.
212
- * @private
213
- * @returns {*} The return value of {@code next(action)}.
214
- */
215
- function _updateSettings({ dispatch }: IStore, next: Function, action: AnyAction) {
216
- const { config: { doNotFlipLocalVideo } } = action;
217
-
218
- if (doNotFlipLocalVideo === true) {
219
- dispatch(updateSettings({
220
- localFlipX: false
221
- }));
222
- }
223
-
224
- return next(action);
225
- }
@@ -1,40 +0,0 @@
1
- import { isEqual, sortBy } from 'lodash-es';
2
-
3
- import { MEDIA_TYPE } from '../media/constants';
4
- import { getScreenshareParticipantIds } from '../participants/functions';
5
- import StateListenerRegistry from '../redux/StateListenerRegistry';
6
-
7
- import { isLocalTrackMuted } from './functions';
8
-
9
- /**
10
- * Notifies when the list of currently sharing participants changes.
11
- */
12
- StateListenerRegistry.register(
13
- /* selector */ state => getScreenshareParticipantIds(state),
14
- /* listener */ (participantIDs, store, previousParticipantIDs) => {
15
- if (typeof APP !== 'object') {
16
- return;
17
- }
18
-
19
- if (!isEqual(sortBy(participantIDs), sortBy(previousParticipantIDs))) {
20
- APP.API.notifySharingParticipantsChanged(participantIDs);
21
- }
22
- }
23
- );
24
-
25
-
26
- /**
27
- * Notifies when the local video mute state changes.
28
- */
29
- StateListenerRegistry.register(
30
- /* selector */ state => isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO),
31
- /* listener */ (muted, store, previousMuted) => {
32
- if (typeof APP !== 'object') {
33
- return;
34
- }
35
-
36
- if (muted !== previousMuted) {
37
- APP.API.notifyVideoMutedStatusChanged(muted);
38
- }
39
- }
40
- );
@@ -1,26 +0,0 @@
1
- import StateListenerRegistry from '../base/redux/StateListenerRegistry';
2
-
3
- import { SELECT_LARGE_VIDEO_PARTICIPANT } from './actionTypes';
4
- import { selectParticipantInLargeVideo } from './actions.any';
5
- import { shouldHideLargeVideo } from './functions';
6
-
7
- /**
8
- * Updates the large video when transitioning from a hidden state to visible state.
9
- * This ensures the large video is properly updated when exiting tile view, stage filmstrip,
10
- * whiteboard, or etherpad editing modes.
11
- */
12
- StateListenerRegistry.register(
13
- /* selector */ state => shouldHideLargeVideo(state),
14
- /* listener */ (isHidden, { dispatch }) => {
15
- // When transitioning from hidden to visible state, select participant (because currently it is undefined).
16
- // Otherwise set it to undefined because we don't show the large video.
17
- if (!isHidden) {
18
- dispatch(selectParticipantInLargeVideo());
19
- } else {
20
- dispatch({
21
- type: SELECT_LARGE_VIDEO_PARTICIPANT,
22
- participantId: undefined
23
- });
24
- }
25
- }
26
- );
@@ -1,282 +0,0 @@
1
- // Regex constants for efficient reuse across selector parsing
2
- const SIMPLE_TAG_NAME_REGEX = /^[a-zA-Z][\w-]*$/;
3
- const MULTI_ATTRIBUTE_SELECTOR_REGEX = /^([a-zA-Z][\w-]*)?(\[(?:\*\|)?([^=\]]+)=["']?([^"'\]]+)["']?\])+$/;
4
- const SINGLE_ATTRIBUTE_REGEX = /\[(?:\*\|)?([^=\]]+)=["']?([^"'\]]+)["']?\]/g;
5
- const WHITESPACE_AROUND_COMBINATOR_REGEX = /\s*>\s*/g;
6
-
7
- /**
8
- * Parses a CSS selector into reusable components.
9
- *
10
- * @param {string} selector - The CSS selector to parse.
11
- * @returns {Object} - Object with tagName and attrConditions properties.
12
- */
13
- function _parseSelector(selector) {
14
- // Wildcard selector
15
- if (selector === '*') {
16
- return {
17
- tagName: null, // null means match all tag names
18
- attrConditions: []
19
- };
20
- }
21
-
22
- // Simple tag name
23
- if (SIMPLE_TAG_NAME_REGEX.test(selector)) {
24
- return {
25
- tagName: selector,
26
- attrConditions: []
27
- };
28
- }
29
-
30
- // Attribute selector: tagname[attr="value"] or
31
- // tagname[attr1="value1"][attr2="value2"] (with optional wildcard namespace)
32
- const multiAttrMatch = selector.match(MULTI_ATTRIBUTE_SELECTOR_REGEX);
33
-
34
- if (multiAttrMatch) {
35
- const tagName = multiAttrMatch[1];
36
- const attrConditions = [];
37
- let attrMatch;
38
-
39
- while ((attrMatch = SINGLE_ATTRIBUTE_REGEX.exec(selector)) !== null) {
40
- attrConditions.push({
41
- name: attrMatch[1], // This properly strips the *| prefix
42
- value: attrMatch[2]
43
- });
44
- }
45
-
46
- return {
47
- tagName,
48
- attrConditions
49
- };
50
- }
51
-
52
- // Unsupported selector
53
- throw new SyntaxError(`Unsupported selector pattern: '${selector}'`);
54
- }
55
-
56
- /**
57
- * Filters elements by selector pattern and handles findFirst logic.
58
- *
59
- * @param {Element[]} elements - Array of elements to filter.
60
- * @param {string} selector - CSS selector to match against.
61
- * @param {boolean} findFirst - If true, return after finding the first match.
62
- * @returns {Element[]|Element|null} - Filtered results with proper return type.
63
- */
64
- function _filterAndMatchElements(elements, selector, findFirst) {
65
- const { tagName, attrConditions } = _parseSelector(selector);
66
-
67
- const results = [];
68
-
69
- for (const element of elements) {
70
- // Check tag name if specified
71
- if (tagName && !(element.localName === tagName || element.tagName === tagName)) {
72
- continue;
73
- }
74
-
75
- // Check if all attribute conditions match
76
- const allMatch = attrConditions.every(condition =>
77
- element.getAttribute(condition.name) === condition.value
78
- );
79
-
80
- if (allMatch) {
81
- results.push(element);
82
- if (findFirst) {
83
- return element;
84
- }
85
- }
86
- }
87
-
88
- return findFirst ? null : results;
89
- }
90
-
91
- /**
92
- * Handles direct child traversal for selectors with > combinators.
93
- * This is the shared logic used by both scope selectors and regular direct child selectors.
94
- *
95
- * @param {Element[]} startElements - Array of starting elements to traverse from.
96
- * @param {string[]} selectorParts - Array of selector parts split by '>'.
97
- * @param {boolean} findFirst - If true, return after finding the first match.
98
- * @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
99
- * single Element or null for querySelector.
100
- */
101
- function _traverseDirectChildren(startElements, selectorParts, findFirst) {
102
- let currentElements = startElements;
103
-
104
- for (const part of selectorParts) {
105
- const nextElements = [];
106
-
107
- currentElements.forEach(el => {
108
- // Get direct children
109
- const directChildren = Array.from(el.children || []);
110
-
111
- // Use same helper as handlers
112
- const matchingChildren = _filterAndMatchElements(directChildren, part, false);
113
-
114
- nextElements.push(...matchingChildren);
115
- });
116
-
117
- currentElements = nextElements;
118
-
119
- // If we have no results, we can stop early (applies to both querySelector and querySelectorAll)
120
- if (currentElements.length === 0) {
121
- return findFirst ? null : [];
122
- }
123
- }
124
-
125
- return findFirst ? currentElements[0] || null : currentElements;
126
- }
127
-
128
- /**
129
- * Handles :scope pseudo-selector cases with direct child combinators.
130
- *
131
- * @param {Node} node - The Node which is the root of the tree to query.
132
- * @param {string} selector - The CSS selector.
133
- * @param {boolean} findFirst - If true, return after finding the first match.
134
- * @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
135
- * single Element or null for querySelector.
136
- */
137
- function _handleScopeSelector(node, selector, findFirst) {
138
- let searchSelector = selector.substring(6);
139
-
140
- // Handle :scope > tagname (direct children)
141
- if (searchSelector.startsWith('>')) {
142
- searchSelector = searchSelector.substring(1);
143
-
144
- // Split by > and use shared traversal logic
145
- const parts = searchSelector.split('>');
146
-
147
- // Start from the node itself (scope)
148
- return _traverseDirectChildren([ node ], parts, findFirst);
149
- }
150
-
151
- return null;
152
- }
153
-
154
- /**
155
- * Handles nested > selectors (direct child combinators).
156
- *
157
- * @param {Node} node - The Node which is the root of the tree to query.
158
- * @param {string} selector - The CSS selector.
159
- * @param {boolean} findFirst - If true, return after finding the first match.
160
- * @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
161
- * single Element or null for querySelector.
162
- */
163
- function _handleDirectChildSelectors(node, selector, findFirst) {
164
- const parts = selector.split('>');
165
-
166
- // First find elements matching the first part (this could be descendants, not just direct children)
167
- const startElements = _querySelectorInternal(node, parts[0], false);
168
-
169
- // If no starting elements found, return early
170
- if (startElements.length === 0) {
171
- return findFirst ? null : [];
172
- }
173
-
174
- // Use shared traversal logic for the remaining parts
175
- return _traverseDirectChildren(startElements, parts.slice(1), findFirst);
176
- }
177
-
178
- /**
179
- * Handles simple tag name selectors.
180
- *
181
- * @param {Node} node - The Node which is the root of the tree to query.
182
- * @param {string} selector - The CSS selector.
183
- * @param {boolean} findFirst - If true, return after finding the first match.
184
- * @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
185
- * single Element or null for querySelector.
186
- */
187
- function _handleSimpleTagSelector(node, selector, findFirst) {
188
- const elements = Array.from(node.getElementsByTagName(selector));
189
-
190
- if (findFirst) {
191
- return elements[0] || null;
192
- }
193
-
194
- return elements;
195
- }
196
-
197
- /**
198
- * Handles attribute selectors: tagname[attr="value"] or tagname[attr1="value1"][attr2="value2"].
199
- * Supports single or multiple attributes with optional wildcard namespace (*|).
200
- *
201
- * @param {Node} node - The Node which is the root of the tree to query.
202
- * @param {string} selector - The CSS selector.
203
- * @param {boolean} findFirst - If true, return after finding the first match.
204
- * @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
205
- * single Element or null for querySelector.
206
- */
207
- function _handleAttributeSelector(node, selector, findFirst) {
208
- const { tagName } = _parseSelector(selector); // Just to get tagName for optimization
209
-
210
- // Handler's job: find the right elements to search
211
- const elementsToCheck = tagName
212
- ? Array.from(node.getElementsByTagName(tagName))
213
- : Array.from(node.getElementsByTagName('*'));
214
-
215
- // Common helper does the matching
216
- return _filterAndMatchElements(elementsToCheck, selector, findFirst);
217
- }
218
-
219
- /**
220
- * Internal function that implements the core selector matching logic for both
221
- * querySelector and querySelectorAll. Supports :scope pseudo-selector, direct
222
- * child selectors, and common CSS selectors.
223
- *
224
- * @param {Node} node - The Node which is the root of the tree to query.
225
- * @param {string} selector - The CSS selector to match elements against.
226
- * @param {boolean} findFirst - If true, return after finding the first match.
227
- * @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
228
- * single Element or null for querySelector.
229
- */
230
- function _querySelectorInternal(node, selector, findFirst = false) {
231
- // Normalize whitespace around > combinators first
232
- const normalizedSelector = selector.replace(WHITESPACE_AROUND_COMBINATOR_REGEX, '>');
233
-
234
- // Handle :scope pseudo-selector
235
- if (normalizedSelector.startsWith(':scope')) {
236
- return _handleScopeSelector(node, normalizedSelector, findFirst);
237
- }
238
-
239
- // Handle nested > selectors (direct child combinators)
240
- if (normalizedSelector.includes('>')) {
241
- return _handleDirectChildSelectors(node, normalizedSelector, findFirst);
242
- }
243
-
244
- // Fast path: simple tag name
245
- if (normalizedSelector === '*' || SIMPLE_TAG_NAME_REGEX.test(normalizedSelector)) {
246
- return _handleSimpleTagSelector(node, normalizedSelector, findFirst);
247
- }
248
-
249
- // Attribute selector: tagname[attr="value"] or
250
- // tagname[attr1="value1"][attr2="value2"] (with optional wildcard namespace)
251
- if (normalizedSelector.match(MULTI_ATTRIBUTE_SELECTOR_REGEX)) {
252
- return _handleAttributeSelector(node, normalizedSelector, findFirst);
253
- }
254
-
255
- // Unsupported selector - throw SyntaxError to match browser behavior
256
- throw new SyntaxError(`Failed to execute 'querySelector${
257
- findFirst ? '' : 'All'}' on 'Element': '${selector}' is not a valid selector.`);
258
- }
259
-
260
- /**
261
- * Implements querySelector functionality using the shared internal logic.
262
- * Supports the same selectors as querySelectorAll but returns only the first match.
263
- *
264
- * @param {Node} node - The Node which is the root of the tree to query.
265
- * @param {string} selectors - The CSS selector to match elements against.
266
- * @returns {Element|null} - The first Element which matches the selector, or null.
267
- */
268
- export function querySelector(node, selectors) {
269
- return _querySelectorInternal(node, selectors, true);
270
- }
271
-
272
- /**
273
- * Implements querySelectorAll functionality using the shared internal logic.
274
- * Supports :scope pseudo-selector, direct child selectors, and common CSS selectors.
275
- *
276
- * @param {Node} node - The Node which is the root of the tree to query.
277
- * @param {string} selector - The CSS selector to match elements against.
278
- * @returns {Element[]} - Array of Elements matching the selector.
279
- */
280
- export function querySelectorAll(node, selector) {
281
- return _querySelectorInternal(node, selector, false);
282
- }