Merge remote-tracking branch 'upstream/v2.5.x-release' into merge-2526-dec14
This commit is contained in:
commit
064b554dfd
@ -47,6 +47,7 @@ import org.bigbluebutton.api.domain.BreakoutRoomsParams;
|
||||
import org.bigbluebutton.api.domain.LockSettingsParams;
|
||||
import org.bigbluebutton.api.domain.Meeting;
|
||||
import org.bigbluebutton.api.domain.Group;
|
||||
import org.bigbluebutton.api.service.ServiceUtils;
|
||||
import org.bigbluebutton.api.util.ParamsUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -685,7 +686,7 @@ public class ParamsProcessorUtil {
|
||||
String parentMeetingId = "";
|
||||
if (isBreakout) {
|
||||
internalMeetingId = params.get(ApiParams.MEETING_ID);
|
||||
parentMeetingId = params.get(ApiParams.PARENT_MEETING_ID);
|
||||
parentMeetingId = ServiceUtils.findMeetingFromMeetingID(params.get(ApiParams.PARENT_MEETING_ID)).getInternalId();
|
||||
// We rebuild the the external meeting using the has of the parent
|
||||
// meeting, the shared timestamp and the sequence number
|
||||
String timeStamp = StringUtils.substringAfter(internalMeetingId, "-");
|
||||
|
@ -27,74 +27,52 @@ import Users, { CurrentUser } from '/imports/api/users';
|
||||
import { Slides, SlidePositions } from '/imports/api/slides';
|
||||
|
||||
// Custom Publishers
|
||||
export const localCurrentPollSync = new AbstractCollection(CurrentPoll, CurrentPoll);
|
||||
export const localCurrentUserSync = new AbstractCollection(CurrentUser, CurrentUser);
|
||||
export const localSlidesSync = new AbstractCollection(Slides, Slides);
|
||||
export const localSlidePositionsSync = new AbstractCollection(SlidePositions, SlidePositions);
|
||||
export const localPollsSync = new AbstractCollection(Polls, Polls);
|
||||
export const localPresentationsSync = new AbstractCollection(Presentations, Presentations);
|
||||
export const localPresentationPodsSync = new AbstractCollection(PresentationPods, PresentationPods);
|
||||
export const localPresentationUploadTokenSync = new AbstractCollection(PresentationUploadToken, PresentationUploadToken);
|
||||
export const localScreenshareSync = new AbstractCollection(Screenshare, Screenshare);
|
||||
export const localUserInfosSync = new AbstractCollection(UserInfos, UserInfos);
|
||||
export const localUsersPersistentDataSync = new AbstractCollection(UsersPersistentData, UsersPersistentData);
|
||||
export const localUserSettingsSync = new AbstractCollection(UserSettings, UserSettings);
|
||||
export const localVideoStreamsSync = new AbstractCollection(VideoStreams, VideoStreams);
|
||||
export const localVoiceUsersSync = new AbstractCollection(VoiceUsers, VoiceUsers);
|
||||
export const localWhiteboardMultiUserSync = new AbstractCollection(WhiteboardMultiUser, WhiteboardMultiUser);
|
||||
export const localGroupChatSync = new AbstractCollection(GroupChat, GroupChat);
|
||||
export const localConnectionStatusSync = new AbstractCollection(ConnectionStatus, ConnectionStatus);
|
||||
export const localCaptionsSync = new AbstractCollection(Captions, Captions);
|
||||
export const localPadsSync = new AbstractCollection(Pads, Pads);
|
||||
export const localPadsSessionsSync = new AbstractCollection(PadsSessions, PadsSessions);
|
||||
export const localPadsUpdatesSync = new AbstractCollection(PadsUpdates, PadsUpdates);
|
||||
export const localAuthTokenValidationSync = new AbstractCollection(AuthTokenValidation, AuthTokenValidation);
|
||||
export const localAnnotationsSync = new AbstractCollection(Annotations, Annotations);
|
||||
export const localRecordMeetingsSync = new AbstractCollection(RecordMeetings, RecordMeetings);
|
||||
export const localExternalVideoMeetingsSync = new AbstractCollection(ExternalVideoMeetings, ExternalVideoMeetings);
|
||||
export const localMeetingTimeRemainingSync = new AbstractCollection(MeetingTimeRemaining, MeetingTimeRemaining);
|
||||
export const localUsersTypingSync = new AbstractCollection(UsersTyping, UsersTyping);
|
||||
export const localBreakoutsSync = new AbstractCollection(Breakouts, Breakouts);
|
||||
export const localBreakoutsHistorySync = new AbstractCollection(BreakoutsHistory, BreakoutsHistory);
|
||||
export const localGuestUsersSync = new AbstractCollection(guestUsers, guestUsers);
|
||||
export const localMeetingsSync = new AbstractCollection(Meetings, Meetings);
|
||||
export const localUsersSync = new AbstractCollection(Users, Users);
|
||||
export const localNotificationsSync = new AbstractCollection(Notifications, Notifications);
|
||||
export const localCollectionRegistry = {
|
||||
localCurrentPollSync: new AbstractCollection(CurrentPoll, CurrentPoll),
|
||||
localCurrentUserSync: new AbstractCollection(CurrentUser, CurrentUser),
|
||||
localSlidesSync: new AbstractCollection(Slides, Slides),
|
||||
localSlidePositionsSync: new AbstractCollection(SlidePositions, SlidePositions),
|
||||
localPollsSync: new AbstractCollection(Polls, Polls),
|
||||
localPresentationsSync: new AbstractCollection(Presentations, Presentations),
|
||||
localPresentationPodsSync: new AbstractCollection(PresentationPods, PresentationPods),
|
||||
localPresentationUploadTokenSync: new AbstractCollection(
|
||||
PresentationUploadToken,
|
||||
PresentationUploadToken,
|
||||
),
|
||||
localScreenshareSync: new AbstractCollection(Screenshare, Screenshare),
|
||||
localUserInfosSync: new AbstractCollection(UserInfos, UserInfos),
|
||||
localUsersPersistentDataSync: new AbstractCollection(UsersPersistentData, UsersPersistentData),
|
||||
localUserSettingsSync: new AbstractCollection(UserSettings, UserSettings),
|
||||
localVideoStreamsSync: new AbstractCollection(VideoStreams, VideoStreams),
|
||||
localVoiceUsersSync: new AbstractCollection(VoiceUsers, VoiceUsers),
|
||||
localWhiteboardMultiUserSync: new AbstractCollection(WhiteboardMultiUser, WhiteboardMultiUser),
|
||||
localGroupChatSync: new AbstractCollection(GroupChat, GroupChat),
|
||||
localConnectionStatusSync: new AbstractCollection(ConnectionStatus, ConnectionStatus),
|
||||
localCaptionsSync: new AbstractCollection(Captions, Captions),
|
||||
localPadsSync: new AbstractCollection(Pads, Pads),
|
||||
localPadsSessionsSync: new AbstractCollection(PadsSessions, PadsSessions),
|
||||
localPadsUpdatesSync: new AbstractCollection(PadsUpdates, PadsUpdates),
|
||||
localAuthTokenValidationSync: new AbstractCollection(AuthTokenValidation, AuthTokenValidation),
|
||||
localAnnotationsSync: new AbstractCollection(Annotations, Annotations),
|
||||
localRecordMeetingsSync: new AbstractCollection(RecordMeetings, RecordMeetings),
|
||||
localExternalVideoMeetingsSync: new AbstractCollection(
|
||||
ExternalVideoMeetings,
|
||||
ExternalVideoMeetings,
|
||||
),
|
||||
localMeetingTimeRemainingSync: new AbstractCollection(MeetingTimeRemaining, MeetingTimeRemaining),
|
||||
localUsersTypingSync: new AbstractCollection(UsersTyping, UsersTyping),
|
||||
localBreakoutsSync: new AbstractCollection(Breakouts, Breakouts),
|
||||
localBreakoutsHistorySync: new AbstractCollection(BreakoutsHistory, BreakoutsHistory),
|
||||
localGuestUsersSync: new AbstractCollection(guestUsers, guestUsers),
|
||||
localMeetingsSync: new AbstractCollection(Meetings, Meetings),
|
||||
localUsersSync: new AbstractCollection(Users, Users),
|
||||
localNotificationsSync: new AbstractCollection(Notifications, Notifications),
|
||||
};
|
||||
|
||||
const collectionMirrorInitializer = () => {
|
||||
localCurrentPollSync.setupListeners();
|
||||
localCurrentUserSync.setupListeners();
|
||||
localSlidesSync.setupListeners();
|
||||
localSlidePositionsSync.setupListeners();
|
||||
localPollsSync.setupListeners();
|
||||
localPresentationsSync.setupListeners();
|
||||
localPresentationPodsSync.setupListeners();
|
||||
localPresentationUploadTokenSync.setupListeners();
|
||||
localScreenshareSync.setupListeners();
|
||||
localUserInfosSync.setupListeners();
|
||||
localUsersPersistentDataSync.setupListeners();
|
||||
localUserSettingsSync.setupListeners();
|
||||
localVideoStreamsSync.setupListeners();
|
||||
localVoiceUsersSync.setupListeners();
|
||||
localWhiteboardMultiUserSync.setupListeners();
|
||||
localGroupChatSync.setupListeners();
|
||||
localConnectionStatusSync.setupListeners();
|
||||
localCaptionsSync.setupListeners();
|
||||
localPadsSync.setupListeners();
|
||||
localPadsSessionsSync.setupListeners();
|
||||
localPadsUpdatesSync.setupListeners();
|
||||
localAuthTokenValidationSync.setupListeners();
|
||||
localAnnotationsSync.setupListeners();
|
||||
localRecordMeetingsSync.setupListeners();
|
||||
localExternalVideoMeetingsSync.setupListeners();
|
||||
localMeetingTimeRemainingSync.setupListeners();
|
||||
localUsersTypingSync.setupListeners();
|
||||
localBreakoutsSync.setupListeners();
|
||||
localBreakoutsHistorySync.setupListeners();
|
||||
localGuestUsersSync.setupListeners();
|
||||
localMeetingsSync.setupListeners();
|
||||
localUsersSync.setupListeners();
|
||||
localNotificationsSync.setupListeners();
|
||||
Object.values(localCollectionRegistry).forEach((localCollection) => {
|
||||
localCollection.setupListeners();
|
||||
});
|
||||
};
|
||||
|
||||
export default collectionMirrorInitializer;
|
||||
|
@ -228,6 +228,7 @@ class App extends Component {
|
||||
layoutContextDispatch,
|
||||
numCameras,
|
||||
presentationIsOpen,
|
||||
ignorePollNotifications,
|
||||
} = this.props;
|
||||
|
||||
this.renderDarkMode();
|
||||
|
@ -311,5 +311,6 @@ export default injectIntl(withModalMounter(withTracker(({ intl, baseControls })
|
||||
hidePresentation: getFromUserSettings('bbb_hide_presentation', LAYOUT_CONFIG.hidePresentation),
|
||||
hideActionsBar: getFromUserSettings('bbb_hide_actions_bar', false),
|
||||
isModalOpen: !!getModal(),
|
||||
ignorePollNotifications: Session.get('ignorePollNotifications'),
|
||||
};
|
||||
})(AppContainer)));
|
||||
|
@ -10,6 +10,7 @@ import { stripTags, unescapeHtml } from '/imports/utils/string-utils';
|
||||
import Service from '../service';
|
||||
import Styled from './styles';
|
||||
import { usePreviousValue } from '/imports/ui/components/utils/hooks';
|
||||
import { Session } from 'meteor/session';
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const PUBLIC_CHAT_CLEAR = CHAT_CONFIG.chat_clear;
|
||||
@ -193,9 +194,11 @@ const ChatAlert = (props) => {
|
||||
const mappedMessage = Service.mapGroupMessage(timeWindow);
|
||||
|
||||
let content = null;
|
||||
let isPollResult = false;
|
||||
if (mappedMessage) {
|
||||
if (mappedMessage.id.includes(POLL_RESULT_KEY)) {
|
||||
content = createPollMessage();
|
||||
isPollResult = true;
|
||||
} else {
|
||||
content = createMessage(mappedMessage.sender.name, mappedMessage.content.slice(-5));
|
||||
}
|
||||
@ -218,10 +221,22 @@ const ChatAlert = (props) => {
|
||||
: <span>{intl.formatMessage(intlMessages.appToastChatPrivate)}</span>
|
||||
}
|
||||
onOpen={
|
||||
() => setUnreadMessages(newUnreadMessages)
|
||||
() => {
|
||||
if (isPollResult) {
|
||||
Session.set('ignorePollNotifications', true);
|
||||
}
|
||||
|
||||
setUnreadMessages(newUnreadMessages);
|
||||
}
|
||||
}
|
||||
onClose={
|
||||
() => setUnreadMessages(newUnreadMessages)
|
||||
() => {
|
||||
if (isPollResult) {
|
||||
Session.set('ignorePollNotifications', false);
|
||||
}
|
||||
|
||||
setUnreadMessages(newUnreadMessages);
|
||||
}
|
||||
}
|
||||
alertDuration={timeWindow.durationDiff}
|
||||
layoutContextDispatch={layoutContextDispatch}
|
||||
|
@ -8,11 +8,7 @@ import Users from '/imports/api/users';
|
||||
import AnnotationsTextService from '/imports/ui/components/whiteboard/annotations/text/service';
|
||||
import { Annotations as AnnotationsLocal } from '/imports/ui/components/whiteboard/service';
|
||||
import {
|
||||
localBreakoutsSync,
|
||||
localBreakoutsHistorySync,
|
||||
localGuestUsersSync,
|
||||
localMeetingsSync,
|
||||
localUsersSync,
|
||||
localCollectionRegistry,
|
||||
} from '/client/collection-mirror-initializer';
|
||||
import SubscriptionRegistry, { subscriptionReactivity } from '../../services/subscription-registry/subscriptionRegistry';
|
||||
import { isChatEnabled } from '/imports/ui/services/features';
|
||||
@ -30,6 +26,13 @@ const SUBSCRIPTIONS = [
|
||||
'pads', 'pads-sessions', 'pads-updates', 'notifications', 'audio-captions',
|
||||
'layout-meetings',
|
||||
];
|
||||
const {
|
||||
localBreakoutsSync,
|
||||
localBreakoutsHistorySync,
|
||||
localGuestUsersSync,
|
||||
localMeetingsSync,
|
||||
localUsersSync,
|
||||
} = localCollectionRegistry;
|
||||
|
||||
const EVENT_NAME = 'bbb-group-chat-messages-subscription-has-stoppped';
|
||||
const EVENT_NAME_SUBSCRIPTION_READY = 'bbb-group-chat-messages-subscriptions-ready';
|
||||
@ -165,6 +168,10 @@ export default withTracker(() => {
|
||||
},
|
||||
...subscriptionErrorHandler,
|
||||
});
|
||||
|
||||
Object.values(localCollectionRegistry).forEach(
|
||||
(localCollection) => localCollection.checkForStaleData(),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -25,13 +25,17 @@ const intlMessages = defineMessages({
|
||||
|
||||
let annotationsStreamListener = null;
|
||||
|
||||
export function addAnnotationToDiscardedList(annotation) {
|
||||
if (!discardedList.includes(annotation)) discardedList.push(annotation);
|
||||
}
|
||||
|
||||
const clearPreview = (annotation) => {
|
||||
UnsentAnnotations.remove({ id: annotation });
|
||||
};
|
||||
|
||||
const clearFakeAnnotations = () => {
|
||||
UnsentAnnotations.remove({});
|
||||
Annotations.remove({ id: /-fake/g, annotationType: { $ne: 'text' } });
|
||||
Annotations.remove({ id: /-fake/g });
|
||||
}
|
||||
|
||||
function handleAddedAnnotation({
|
||||
|
@ -238,6 +238,8 @@ export default class WhiteboardOverlay extends Component {
|
||||
setTextShapeActiveId,
|
||||
contextMenuHandler,
|
||||
clearPreview,
|
||||
addAnnotationToDiscardedList,
|
||||
undoAnnotation,
|
||||
updateCursor,
|
||||
} = this.props;
|
||||
|
||||
@ -255,6 +257,8 @@ export default class WhiteboardOverlay extends Component {
|
||||
setTextShapeActiveId,
|
||||
contextMenuHandler,
|
||||
clearPreview,
|
||||
addAnnotationToDiscardedList,
|
||||
undoAnnotation,
|
||||
};
|
||||
|
||||
return (
|
||||
@ -319,4 +323,6 @@ WhiteboardOverlay.propTypes = {
|
||||
setTextShapeActiveId: PropTypes.func.isRequired,
|
||||
// Defines a handler to publish cursor position to the server
|
||||
updateCursor: PropTypes.func.isRequired,
|
||||
addAnnotationToDiscardedList: PropTypes.func.isRequired,
|
||||
undoAnnotation: PropTypes.func.isRequired,
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import PropTypes from 'prop-types';
|
||||
import WhiteboardOverlayService from './service';
|
||||
import WhiteboardToolbarService from '../whiteboard-toolbar/service';
|
||||
import WhiteboardOverlay from './component';
|
||||
|
||||
const WhiteboardOverlayContainer = (props) => {
|
||||
@ -15,10 +16,12 @@ const WhiteboardOverlayContainer = (props) => {
|
||||
};
|
||||
|
||||
export default withTracker(() => ({
|
||||
undoAnnotation: WhiteboardToolbarService.undoAnnotation,
|
||||
clearPreview: WhiteboardOverlayService.clearPreview,
|
||||
contextMenuHandler: WhiteboardOverlayService.contextMenuHandler,
|
||||
sendAnnotation: WhiteboardOverlayService.sendAnnotation,
|
||||
sendLiveSyncPreviewAnnotation: WhiteboardOverlayService.sendLiveSyncPreviewAnnotation,
|
||||
addAnnotationToDiscardedList: WhiteboardOverlayService.addAnnotationToDiscardedList,
|
||||
setTextShapeActiveId: WhiteboardOverlayService.setTextShapeActiveId,
|
||||
resetTextShapeSession: WhiteboardOverlayService.resetTextShapeSession,
|
||||
drawSettings: WhiteboardOverlayService.getWhiteboardToolbarValues(),
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Storage from '/imports/ui/services/storage/session';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import { sendAnnotation, sendLiveSyncPreviewAnnotation, clearPreview } from '/imports/ui/components/whiteboard/service';
|
||||
import { sendAnnotation, sendLiveSyncPreviewAnnotation, clearPreview, addAnnotationToDiscardedList } from '/imports/ui/components/whiteboard/service';
|
||||
import { publishCursorUpdate } from '/imports/ui/components/cursor/service';
|
||||
|
||||
const DRAW_SETTINGS = 'drawSettings';
|
||||
@ -55,6 +55,7 @@ const updateCursor = (payload) => {
|
||||
};
|
||||
|
||||
export default {
|
||||
addAnnotationToDiscardedList,
|
||||
sendAnnotation,
|
||||
sendLiveSyncPreviewAnnotation,
|
||||
getWhiteboardToolbarValues,
|
||||
|
@ -218,11 +218,12 @@ export default class TextDrawListener extends Component {
|
||||
}
|
||||
|
||||
// second case is when a user finished writing the text and publishes the final result
|
||||
} else if (isRightClick) {
|
||||
this.discardAnnotation();
|
||||
} else {
|
||||
// publishing the final shape and resetting the state
|
||||
this.sendLastMessage();
|
||||
if (isRightClick) {
|
||||
this.discardAnnotation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -485,15 +486,18 @@ export default class TextDrawListener extends Component {
|
||||
discardAnnotation() {
|
||||
const {
|
||||
actions,
|
||||
whiteboardId,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
getCurrentShapeId,
|
||||
clearPreview,
|
||||
undoAnnotation,
|
||||
addAnnotationToDiscardedList,
|
||||
} = actions;
|
||||
|
||||
this.resetState();
|
||||
clearPreview(getCurrentShapeId());
|
||||
undoAnnotation(whiteboardId);
|
||||
addAnnotationToDiscardedList(getCurrentShapeId());
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -598,5 +602,7 @@ TextDrawListener.propTypes = {
|
||||
resetTextShapeSession: PropTypes.func.isRequired,
|
||||
// Defines a function that sets a session value for the current active text shape
|
||||
setTextShapeActiveId: PropTypes.func.isRequired,
|
||||
undoAnnotation: PropTypes.func.isRequired,
|
||||
addAnnotationToDiscardedList: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import SubscriptionRegistry from '/imports/ui/services/subscription-registry/sub
|
||||
import CollectionEventsBroker from '/imports/ui/services/LiveDataEventBroker/LiveDataEventBroker';
|
||||
|
||||
/*
|
||||
This class connects a local collection with the LiveDataEventBroker, propagating the changes of a server-side published cursor to a local collection.
|
||||
This class connects a local collection with the LiveDataEventBroker, propagating the changes of a server-side published cursor to a local collection.
|
||||
|
||||
It also guarantee that in case of a reconnection or a re-subscription, the data is only removed after subscription is ready, avoiding the situation of missing data during re-synchronization.
|
||||
*/
|
||||
@ -15,6 +15,7 @@ class LocalCollectionSynchronizer {
|
||||
this.lastSubscriptionId = '';
|
||||
this.options = options;
|
||||
this.ignoreDeletes = false;
|
||||
this.checkForStaleData = this.checkForStaleData.bind(this);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -31,26 +32,12 @@ class LocalCollectionSynchronizer {
|
||||
const self = this;
|
||||
|
||||
const addedCallback = function (item) {
|
||||
const subscription = SubscriptionRegistry
|
||||
.getSubscription(self.serverCollection._name);
|
||||
if (item.id === 'publication-stop-marker' && item.stopped) {
|
||||
self.ignoreDeletes = true;
|
||||
return;
|
||||
}
|
||||
// If the subscriptionId changes means the subscriptions was redone
|
||||
// or theres more than one subscription per collection
|
||||
if (subscription && (self.lastSubscriptionId !== subscription.subscriptionId)) {
|
||||
const wasEmpty = self.lastSubscriptionId === '';
|
||||
self.lastSubscriptionId = subscription.subscriptionId;
|
||||
if (!wasEmpty) {
|
||||
self.callWhenSubscriptionReady(() => {
|
||||
self.ignoreDeletes = false;
|
||||
Session.set('globalIgnoreDeletes', false);
|
||||
self.removeOldSubscriptionData();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.checkForStaleData();
|
||||
const selector = { referenceId: item.referenceId };
|
||||
const itemExistInCollection = self.localCollection.findOne(selector);
|
||||
|
||||
@ -117,6 +104,24 @@ class LocalCollectionSynchronizer {
|
||||
return tempPromise;
|
||||
}
|
||||
|
||||
checkForStaleData() {
|
||||
const subscription = SubscriptionRegistry.getSubscription(this.serverCollection._name);
|
||||
|
||||
// If the subscriptionId changes means the subscriptions was redone
|
||||
// or theres more than one subscription per collection
|
||||
if (subscription && (this.lastSubscriptionId !== subscription.subscriptionId)) {
|
||||
const wasEmpty = this.lastSubscriptionId === '';
|
||||
this.lastSubscriptionId = subscription.subscriptionId;
|
||||
if (!wasEmpty) {
|
||||
this.callWhenSubscriptionReady(() => {
|
||||
this.ignoreDeletes = false;
|
||||
Session.set('globalIgnoreDeletes', false);
|
||||
this.removeOldSubscriptionData();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
This method removes data from previous subscriptions after the current one is ready.
|
||||
*/
|
||||
|
@ -16,7 +16,7 @@ const SETTINGS = [
|
||||
];
|
||||
|
||||
const CHANGED_SETTINGS = 'changed_settings';
|
||||
const DEFAULT_SETTINGS = 'dafault_settings';
|
||||
const DEFAULT_SETTINGS = 'default_settings';
|
||||
|
||||
class Settings {
|
||||
constructor(defaultValues = {}) {
|
||||
|
@ -92,3 +92,18 @@ sudo systemctl reload nginx
|
||||
Meteor messages for Big Blue Button sessions will now be recorded for later review.
|
||||
|
||||
It doesn't seem necessary to relay cookies, but that could be done by giving a `--ws-relay-header=Cookie` argument to `websockify`.
|
||||
|
||||
You can print the browser console log to standard output by setting the environment variable `CONSOLE`:
|
||||
```
|
||||
$ CONSOLE= npm test chat -- --project=firefox
|
||||
```
|
||||
|
||||
`CONSOLE` can be blank (as in the example), or can be a comma-separated list of the following options:
|
||||
|
||||
| Option | Meaning |
|
||||
| ------ | ------- |
|
||||
| color | (or "colour") colorize the output |
|
||||
| label | label each line with the BigBlueButton user |
|
||||
| norefs | remove JavaScript reference URLs |
|
||||
| nots | remove timestamps |
|
||||
| nocl | remove "clientLogger:" strings |
|
||||
|
125
bigbluebutton-tests/playwright/api/api.js
Normal file
125
bigbluebutton-tests/playwright/api/api.js
Normal file
@ -0,0 +1,125 @@
|
||||
// const util = require('node:util');
|
||||
|
||||
const { expect } = require("@playwright/test");
|
||||
|
||||
const Page = require('../core/page');
|
||||
const parameters = require('../core/parameters');
|
||||
const { apiCall, createMeeting } = require('../core/helpers');
|
||||
const e = require('../core/elements');
|
||||
|
||||
function getMeetings() {
|
||||
return apiCall('getMeetings', {});
|
||||
}
|
||||
|
||||
function getMeetingInfo(meetingID) {
|
||||
return apiCall('getMeetingInfo', {meetingID: meetingID});
|
||||
}
|
||||
|
||||
class API {
|
||||
|
||||
constructor(browser, context, page) {
|
||||
this.modPage = new Page(browser, page);
|
||||
this.browser = browser;
|
||||
this.context = context;
|
||||
this.userPages = [];
|
||||
}
|
||||
|
||||
async getNewPageTab() {
|
||||
return this.browser.newPage();
|
||||
}
|
||||
|
||||
async testGetMeetings() {
|
||||
const meetingId = await createMeeting(parameters);
|
||||
const modPage = new Page(this.browser, await this.getNewPageTab());
|
||||
const userPage = new Page(this.browser, await this.getNewPageTab());
|
||||
await Promise.all([
|
||||
modPage.init(true, false, { meetingId, fullName: 'Moderator' }),
|
||||
userPage.init(false, false, { meetingId, fullName: 'Attendee' }),
|
||||
]);
|
||||
await Promise.all([
|
||||
modPage.joinMicrophone(),
|
||||
userPage.joinMicrophone()
|
||||
]);
|
||||
|
||||
/* hasJoinedVoice: ['true'] is not part of these expectedUser patterns because it isn't consistently true
|
||||
* in the API's returned data structures. Is there something we can await on the browser page that
|
||||
* should ensure that the API will report hasJoinedVoice?
|
||||
*/
|
||||
|
||||
const expectedUsers = [expect.objectContaining({fullName: ['Moderator'],
|
||||
role: ['MODERATOR'],
|
||||
isPresenter: ['true'],
|
||||
}),
|
||||
expect.objectContaining({fullName: ['Attendee'],
|
||||
role: ['VIEWER'],
|
||||
isPresenter: ['false'],
|
||||
})
|
||||
];
|
||||
const expectedMeeting = {meetingName : [meetingId],
|
||||
running : ['true'],
|
||||
participantCount : ['2'],
|
||||
moderatorCount : ['1'],
|
||||
isBreakout: ['false'],
|
||||
attendees: [{ attendee: expect.arrayContaining(expectedUsers) }]
|
||||
};
|
||||
|
||||
/* check that this meeting is in the server's list of all meetings */
|
||||
const response = await getMeetings();
|
||||
expect(response.response.returncode).toEqual(['SUCCESS']);
|
||||
expect(response.response.meetings[0].meeting).toContainEqual(expect.objectContaining(expectedMeeting));
|
||||
|
||||
await modPage.page.close();
|
||||
await userPage.page.close();
|
||||
}
|
||||
|
||||
async testGetMeetingInfo() {
|
||||
const meetingId = await createMeeting(parameters);
|
||||
const modPage = new Page(this.browser, await this.getNewPageTab());
|
||||
const userPage = new Page(this.browser, await this.getNewPageTab());
|
||||
await Promise.all([
|
||||
modPage.init(true, false, { meetingId, fullName: 'Moderator' }),
|
||||
userPage.init(false, false, { meetingId, fullName: 'Attendee' }),
|
||||
]);
|
||||
await Promise.all([
|
||||
modPage.joinMicrophone(),
|
||||
userPage.joinMicrophone()
|
||||
]);
|
||||
|
||||
/* hasJoinedVoice: ['true'] is not part of these expectedUser patterns because it isn't consistently true
|
||||
* in the API's returned data structures. Is there something we can await on the browser page that
|
||||
* should ensure that the API will report hasJoinedVoice?
|
||||
*/
|
||||
|
||||
const expectedUsers = [expect.objectContaining({fullName: ['Moderator'],
|
||||
role: ['MODERATOR'],
|
||||
isPresenter: ['true'],
|
||||
}),
|
||||
expect.objectContaining({fullName: ['Attendee'],
|
||||
role: ['VIEWER'],
|
||||
isPresenter: ['false'],
|
||||
})
|
||||
];
|
||||
const expectedMeeting = {meetingName : [meetingId],
|
||||
running : ['true'],
|
||||
participantCount : ['2'],
|
||||
moderatorCount : ['1'],
|
||||
isBreakout: ['false'],
|
||||
attendees: [{ attendee: expect.arrayContaining(expectedUsers) }]
|
||||
};
|
||||
|
||||
/* check that we can retrieve this meeting by its meetingId */
|
||||
const response2 = await getMeetingInfo(meetingId);
|
||||
expect(response2.response.returncode).toEqual(['SUCCESS']);
|
||||
expect(response2.response).toMatchObject(expectedMeeting);
|
||||
|
||||
/* check that we can retrieve this meeting by its internal meeting ID */
|
||||
const response3 = await getMeetingInfo(response2.response.internalMeetingID[0]);
|
||||
expect(response3.response.returncode).toEqual(['SUCCESS']);
|
||||
expect(response3.response).toMatchObject(expectedMeeting);
|
||||
|
||||
await modPage.page.close();
|
||||
await userPage.page.close();
|
||||
}
|
||||
}
|
||||
|
||||
exports.API = API;
|
16
bigbluebutton-tests/playwright/api/api.spec.js
Normal file
16
bigbluebutton-tests/playwright/api/api.spec.js
Normal file
@ -0,0 +1,16 @@
|
||||
const { test } = require('@playwright/test');
|
||||
const { API } = require('./api.js');
|
||||
|
||||
test.describe.parallel('API', () => {
|
||||
|
||||
test('getMeetings', async ({ browser, context, page }) => {
|
||||
const api = new API(browser, context, page);
|
||||
await api.testGetMeetings();
|
||||
});
|
||||
|
||||
test('getMeetingInfo', async ({ browser, context, page }) => {
|
||||
const api = new API(browser, context, page);
|
||||
await api.testGetMeetingInfo();
|
||||
});
|
||||
|
||||
});
|
@ -3,6 +3,11 @@ const sha1 = require('sha1');
|
||||
const path = require('path');
|
||||
const axios = require('axios');
|
||||
const { test } = require('@playwright/test');
|
||||
const xml2js = require('xml2js');
|
||||
|
||||
const { expect } = require("@playwright/test");
|
||||
|
||||
const parameters = require('./parameters');
|
||||
|
||||
const httpPath = path.join(path.dirname(require.resolve('axios')), 'lib/adapters/http');
|
||||
const http = require(httpPath);
|
||||
@ -13,8 +18,21 @@ function getRandomInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
}
|
||||
|
||||
async function createMeeting(params, customParameter, customMeetingId) {
|
||||
const meetingID = (customMeetingId) ? customMeetingId : `random-${getRandomInt(1000000, 10000000).toString()}`;
|
||||
function apiCallUrl(name, callParams) {
|
||||
const query = new URLSearchParams(callParams).toString();
|
||||
const apicall = `${name}${query}${parameters.secret}`;
|
||||
const checksum = sha1(apicall);
|
||||
const url = `${parameters.server}/${name}?${query}&checksum=${checksum}`;
|
||||
return url;
|
||||
}
|
||||
|
||||
function apiCall(name, callParams) {
|
||||
const url = apiCallUrl(name, callParams);
|
||||
return axios.get(url, { adapter: http }).then(response => xml2js.parseStringPromise(response.data));
|
||||
}
|
||||
|
||||
function createMeetingUrl(params, customParameter) {
|
||||
const meetingID = `random-${getRandomInt(1000000, 10000000).toString()}`;
|
||||
const mp = params.moderatorPW;
|
||||
const ap = params.attendeePW;
|
||||
const query = customParameter !== undefined ? `name=${meetingID}&meetingID=${meetingID}&attendeePW=${ap}&moderatorPW=${mp}`
|
||||
@ -24,8 +42,20 @@ async function createMeeting(params, customParameter, customMeetingId) {
|
||||
const apicall = `create${query}${params.secret}`;
|
||||
const checksum = sha1(apicall);
|
||||
const url = `${params.server}/create?${query}&checksum=${checksum}`;
|
||||
await axios.get(url, { adapter: http });
|
||||
return meetingID;
|
||||
return url;
|
||||
}
|
||||
|
||||
function createMeetingPromise(params, customParameter) {
|
||||
const url = createMeetingUrl(params, customParameter);
|
||||
return axios.get(url, { adapter: http });
|
||||
}
|
||||
|
||||
async function createMeeting(params, customParameter) {
|
||||
const promise = createMeetingPromise(params, customParameter);
|
||||
const response = await promise;
|
||||
expect(response.status).toEqual(200);
|
||||
const xmlresponse = await xml2js.parseStringPromise(response.data);
|
||||
return xmlresponse.response.meetingID[0];
|
||||
}
|
||||
|
||||
function getJoinURL(meetingID, params, moderator, customParameter) {
|
||||
@ -51,6 +81,10 @@ function sleep(time) {
|
||||
}
|
||||
|
||||
exports.getRandomInt = getRandomInt;
|
||||
exports.apiCallUrl = apiCallUrl;
|
||||
exports.apiCall = apiCall;
|
||||
exports.createMeetingUrl = createMeetingUrl;
|
||||
exports.createMeetingPromise = createMeetingPromise;
|
||||
exports.createMeeting = createMeeting;
|
||||
exports.getJoinURL = getJoinURL;
|
||||
exports.linkIssue = linkIssue;
|
||||
|
@ -1,13 +1,86 @@
|
||||
require('dotenv').config();
|
||||
const { expect, default: test } = require('@playwright/test');
|
||||
const { readFileSync } = require('fs');
|
||||
const { format } = require('node:util');
|
||||
|
||||
// This is version 4 of chalk, not version 5, which uses ESM
|
||||
const chalk = require('chalk');
|
||||
|
||||
const parameters = require('./parameters');
|
||||
const helpers = require('./helpers');
|
||||
const e = require('./elements');
|
||||
const { env } = require('node:process');
|
||||
const { ELEMENT_WAIT_TIME, ELEMENT_WAIT_LONGER_TIME, VIDEO_LOADING_WAIT_TIME } = require('./constants');
|
||||
const { checkElement, checkElementLengthEqualTo } = require('./util');
|
||||
const { generateSettingsData, getSettings } = require('./settings');
|
||||
|
||||
function formatWithCss(CONSOLE_options, ...args) {
|
||||
// For Chrome, args[0] is a format string that we will process using
|
||||
// node.js's util.format, but that function discards css style
|
||||
// information from "%c" format specifiers. So first loop over the
|
||||
// format string, replacing every "%c" with "%s" and replacing the
|
||||
// corresponding css style with an ANSI color sequence.
|
||||
//
|
||||
// See https://console.spec.whatwg.org/ sections 2.2.1 and 2.3.4
|
||||
|
||||
let split_arg0 = args[0].split("%");
|
||||
for (let i=1, j=1; i<split_arg0.length; i++, j++) {
|
||||
if (split_arg0[i].startsWith('c')) {
|
||||
split_arg0[i] = 's' + split_arg0[i].substr(1);
|
||||
const styles = args[j].split(';');
|
||||
args[j] = '';
|
||||
for (const style of styles) {
|
||||
const stdStyle = style.trim().toLowerCase();
|
||||
if (stdStyle.startsWith('color:') && CONSOLE_options.colorize) {
|
||||
const color = stdStyle.substr(6).trim();
|
||||
args[j] = chalk.keyword(color)._styler.open;
|
||||
} else if (stdStyle.startsWith('font-size:') && CONSOLE_options.drop_references) {
|
||||
// For Chrome, we "drop references" by discarding everything after a font size change
|
||||
split_arg0.length = i;
|
||||
args.length = j;
|
||||
}
|
||||
}
|
||||
} else if (split_arg0[i] == "") {
|
||||
// format is "%%", so don't do special processing for
|
||||
// split_arg0[i+1], and only increment i, not j
|
||||
i ++; // NOSONAR
|
||||
}
|
||||
}
|
||||
args[0] = split_arg0.join('%');
|
||||
return format(...args);
|
||||
}
|
||||
|
||||
async function console_format(msg, CONSOLE_options) {
|
||||
// see playwright consoleMessage class documentation
|
||||
const args = await Promise.all(msg.args().map(itm => itm.jsonValue()));
|
||||
let result = formatWithCss(CONSOLE_options, ...args);
|
||||
|
||||
if (CONSOLE_options.drop_references) {
|
||||
// For Firefox, we "drop references" by discarding a URL at the end of the line
|
||||
result = result.replace(/https:\/\/\S*$/, '');
|
||||
}
|
||||
|
||||
if (CONSOLE_options.noClientLogger) {
|
||||
result = result.replace(/clientLogger: /, '');
|
||||
}
|
||||
|
||||
if (CONSOLE_options.drop_timestamps) {
|
||||
// timestamp formatting is a bit complicated, with four "%s" fields and corresponding arguments,
|
||||
// so just filter them out (if requested) after all the other formatting is done
|
||||
result = result.replace(/\[\d\d:\d\d:\d\d:\d\d\d\d\] /, '');
|
||||
}
|
||||
|
||||
if (CONSOLE_options.line_label) {
|
||||
if (CONSOLE_options.colorize) {
|
||||
result = chalk.keyword('green')(CONSOLE_options.line_label) + result;
|
||||
} else {
|
||||
result = CONSOLE_options.line_label + result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
class Page {
|
||||
constructor(browser, page) {
|
||||
this.browser = browser;
|
||||
@ -31,7 +104,19 @@ class Page {
|
||||
if (fullName) this.initParameters.fullName = fullName;
|
||||
this.username = this.initParameters.fullName;
|
||||
|
||||
this.meetingId = (meetingId) ? meetingId : await helpers.createMeeting(parameters, customParameter, customMeetingId);
|
||||
if (env.CONSOLE !== undefined) {
|
||||
const CONSOLE_strings = env.CONSOLE.split(',').map(opt => opt.trim().toLowerCase());
|
||||
const CONSOLE_options = {
|
||||
colorize: CONSOLE_strings.includes('color') || CONSOLE_strings.includes('colour'),
|
||||
drop_references: CONSOLE_strings.includes('norefs'),
|
||||
drop_timestamps: CONSOLE_strings.includes('nots'),
|
||||
line_label: CONSOLE_strings.includes('label') ? this.username + " " : undefined,
|
||||
noClientLogger: CONSOLE_strings.includes('nocl') || CONSOLE_strings.includes('noclientlogger'),
|
||||
};
|
||||
this.page.on('console', async (msg) => console.log(await console_format(msg, CONSOLE_options)));
|
||||
}
|
||||
|
||||
this.meetingId = (meetingId) ? meetingId : await helpers.createMeeting(parameters, customParameter);
|
||||
const joinUrl = helpers.getJoinURL(this.meetingId, this.initParameters, isModerator, customParameter);
|
||||
const response = await this.page.goto(joinUrl);
|
||||
await expect(response.ok()).toBeTruthy();
|
||||
|
155
bigbluebutton-tests/playwright/package-lock.json
generated
155
bigbluebutton-tests/playwright/package-lock.json
generated
@ -7,9 +7,11 @@
|
||||
"dependencies": {
|
||||
"@playwright/test": "^1.25.0",
|
||||
"axios": "^0.26.1",
|
||||
"chalk": "^4.1.2",
|
||||
"dotenv": "^16.0.0",
|
||||
"playwright": "^1.19.2",
|
||||
"sha1": "^1.1.1"
|
||||
"sha1": "^1.1.1",
|
||||
"xml2js": "^0.4.23"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
@ -43,6 +45,20 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.40.tgz",
|
||||
"integrity": "sha512-UXdBxNGqTMtm7hCwh9HtncFVLrXoqA3oJW30j6XWp5BH/wu3mVeaxo7cq5benFdBw34HB3XDT2TRPI7rXZ+mDg=="
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.26.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
|
||||
@ -51,6 +67,21 @@
|
||||
"follow-redirects": "^1.14.8"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/charenc": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
|
||||
@ -59,6 +90,22 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/crypt": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
|
||||
@ -94,6 +141,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.22.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.22.2.tgz",
|
||||
@ -120,6 +175,11 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||
},
|
||||
"node_modules/sha1": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz",
|
||||
@ -131,6 +191,37 @@
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/xml2js": {
|
||||
"version": "0.4.23",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
||||
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
|
||||
"dependencies": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@ -155,6 +246,14 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.40.tgz",
|
||||
"integrity": "sha512-UXdBxNGqTMtm7hCwh9HtncFVLrXoqA3oJW30j6XWp5BH/wu3mVeaxo7cq5benFdBw34HB3XDT2TRPI7rXZ+mDg=="
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.26.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
|
||||
@ -163,11 +262,33 @@
|
||||
"follow-redirects": "^1.14.8"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"charenc": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
|
||||
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA=="
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"crypt": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
|
||||
@ -183,6 +304,11 @@
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
|
||||
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
||||
},
|
||||
"playwright": {
|
||||
"version": "1.22.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.22.2.tgz",
|
||||
@ -196,6 +322,11 @@
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.22.2.tgz",
|
||||
"integrity": "sha512-w/hc/Ld0RM4pmsNeE6aL/fPNWw8BWit2tg+TfqJ3+p59c6s3B6C8mXvXrIPmfQEobkcFDc+4KirNzOQ+uBSP1Q=="
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||
},
|
||||
"sha1": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz",
|
||||
@ -204,6 +335,28 @@
|
||||
"charenc": ">= 0.0.1",
|
||||
"crypt": ">= 0.0.1"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"xml2js": {
|
||||
"version": "0.4.23",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
||||
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
|
||||
"requires": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
}
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,9 @@
|
||||
"@playwright/test": "^1.25.0",
|
||||
"playwright": "^1.19.2",
|
||||
"axios": "^0.26.1",
|
||||
"chalk": "^4.1.2",
|
||||
"dotenv": "^16.0.0",
|
||||
"sha1": "^1.1.1"
|
||||
"sha1": "^1.1.1",
|
||||
"xml2js": "^0.4.23"
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
. ./opts-global.sh
|
||||
|
||||
AKKA_APPS="bbb-fsesl-akka,bbb-apps-akka"
|
||||
OPTS="$OPTS -t deb -d netcat-openbsd,bbb-html5,bbb-playback-presentation,bbb-playback,bbb-freeswitch-core,$AKKA_APPS"
|
||||
OPTS="$OPTS -t deb -d netcat-openbsd,stun-client,bbb-html5,bbb-playback-presentation,bbb-playback,bbb-freeswitch-core,$AKKA_APPS"
|
||||
|
Loading…
Reference in New Issue
Block a user