init
Some checks failed
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
2025-09-02 14:49:16 +08:00
commit 38ba663466
2885 changed files with 391107 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
/**
* Action used to store jaas customer details
*/
export const SET_DETAILS = 'SET_DETAILS';

View File

@@ -0,0 +1,46 @@
import { IStore } from '../app/types';
import { SET_DETAILS } from './actionTypes';
import { getVpaasTenant, sendGetDetailsRequest } from './functions';
import logger from './logger';
/**
* Action used to set the jaas customer details in store.
*
* @param {Object} details - The customer details object.
* @returns {Object}
*/
function setCustomerDetails(details: Object) {
return {
type: SET_DETAILS,
payload: details
};
}
/**
* Sends a request for retrieving jaas customer details.
*
* @returns {Function}
*/
export function getCustomerDetails() {
return async function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
const state = getState();
const baseUrl = state['features/base/config'].jaasActuatorUrl ?? '';
const appId = getVpaasTenant(state);
const shouldSendRequest = Boolean(baseUrl && appId);
if (shouldSendRequest) {
try {
const details = await sendGetDetailsRequest({
appId,
baseUrl
});
dispatch(setCustomerDetails(details));
} catch (err) {
logger.error('Could not send request', err);
}
}
};
}

View File

@@ -0,0 +1,11 @@
/**
* Shows a dialog prompting users to upgrade, if requested feature is disabled.
*
* @param {string} _feature - Used on web.
* @returns {Function}
*/
export function maybeShowPremiumFeatureDialog(_feature: string) {
return function() {
return false;
};
}

View File

@@ -0,0 +1,25 @@
import { IStore } from '../app/types';
import { openDialog } from '../base/dialog/actions';
import { ParticipantFeaturesKey } from '../base/participants/types';
import PremiumFeatureDialog from './components/web/PremiumFeatureDialog';
import { isFeatureDisabled } from './functions';
/**
* Shows a dialog prompting users to upgrade, if requested feature is disabled.
*
* @param {ParticipantFeaturesKey} feature - The feature to check availability for.
*
* @returns {Function}
*/
export function maybeShowPremiumFeatureDialog(feature: ParticipantFeaturesKey) {
return function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
if (isFeatureDisabled(getState(), feature)) {
dispatch(openDialog(PremiumFeatureDialog));
return true;
}
return false;
};
}

View File

@@ -0,0 +1,56 @@
import React, { PureComponent } from 'react';
import { translate } from '../../../base/i18n/functions';
import Dialog from '../../../base/ui/components/web/Dialog';
import { openURLInBrowser } from '../../../base/util/openURLInBrowser.web';
import { JAAS_UPGRADE_URL } from '../../constants';
/**
* Component that renders the premium feature dialog.
*
* @returns {React$Element<any>}
*/
class PremiumFeatureDialog extends PureComponent<any> {
/**
* Instantiates a new component.
*
* @inheritdoc
*/
constructor(props: any) {
super(props);
this._onSubmitValue = this._onSubmitValue.bind(this);
}
/**
* Callback to be invoked when the dialog ok is pressed.
*
* @returns {boolean}
*/
_onSubmitValue() {
openURLInBrowser(JAAS_UPGRADE_URL, true);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
*/
override render() {
const { t } = this.props;
return (
<Dialog
cancel = {{ hidden: true }}
ok = {{ translationKey: 'dialog.viewUpgradeOptions' }}
onSubmit = { this._onSubmitValue }
titleKey = { t('dialog.viewUpgradeOptionsTitle') }>
<span>{t('dialog.viewUpgradeOptionsContent')}</span>
</Dialog>
);
}
}
export default translate(PremiumFeatureDialog);

View File

@@ -0,0 +1,17 @@
/**
* JaaS customer statuses which represent their account state.
*/
export const STATUSES = {
ACTIVE: 'ACTIVE',
BLOCKED: 'BLOCKED'
};
/**
* URL for displaying JaaS upgrade options.
*/
export const JAAS_UPGRADE_URL = 'https://jaas.8x8.vc/#/plan/upgrade';
/**
* The prefix for the vpaas tenant.
*/
export const VPAAS_TENANT_PREFIX = 'vpaas-magic-cookie-';

View File

@@ -0,0 +1,170 @@
import { IReduxState } from '../app/types';
import { IJitsiConference } from '../base/conference/reducer';
import { ParticipantFeaturesKey } from '../base/participants/types';
import { VPAAS_TENANT_PREFIX } from './constants';
import logger from './logger';
/**
* Returns the full vpaas tenant if available, given a path.
*
* @param {string} path - The meeting url path.
* @returns {string}
*/
function extractVpaasTenantFromPath(path: string) {
const [ , tenant ] = path.split('/');
if (tenant.startsWith(VPAAS_TENANT_PREFIX)) {
return tenant;
}
return '';
}
/**
* Returns the vpaas tenant.
*
* @param {IReduxState} state - The global state.
* @returns {string}
*/
export function getVpaasTenant(state: IReduxState) {
return extractVpaasTenantFromPath(state['features/base/connection'].locationURL?.pathname ?? '');
}
/**
* Returns true if the current meeting is a vpaas one.
*
* @param {IReduxState} state - The state of the app.
* @returns {boolean}
*/
export function isVpaasMeeting(state: IReduxState) {
const connection = state['features/base/connection'];
if (connection?.locationURL?.pathname) {
return Boolean(
extractVpaasTenantFromPath(connection?.locationURL?.pathname)
);
}
return false;
}
/**
* Sends a request for retrieving the conference creator's customer id.
*
* @param {IJitsiConference} conference - The conference state.
* @param {IReduxState} state - The state of the app.
* @returns {Object} - Object containing customerId field.
*/
export async function sendGetCustomerIdRequest(conference: IJitsiConference, state: IReduxState) {
const { jaasConferenceCreatorUrl } = state['features/base/config'];
const roomJid = conference?.room?.roomjid;
if (jaasConferenceCreatorUrl && roomJid) {
const fullUrl = `${jaasConferenceCreatorUrl}?conference=${encodeURIComponent(roomJid)}`;
const response = await fetch(fullUrl);
const responseBody = await response.json();
if (response.ok) {
return responseBody;
}
logger.error(`Failed to fetch ${fullUrl}. with: ${JSON.stringify(responseBody)}`);
}
}
/**
* Sends a request for retrieving jaas customer details.
*
* @param {Object} reqData - The request info.
* @param {string} reqData.appId - The client appId.
* @param {string} reqData.baseUrl - The base url for the request.
* @returns {void}
*/
export async function sendGetDetailsRequest({ appId, baseUrl }: {
appId: string;
baseUrl: string;
}) {
const fullUrl = `${baseUrl}/v1/public/tenants/${encodeURIComponent(appId)}`;
try {
const res = await fetch(fullUrl);
if (res.ok) {
return res.json();
}
throw new Error('Request not successful');
} catch (err: any) {
throw new Error(err);
}
}
/**
* Returns the billing id for vpaas meetings.
*
* @param {IReduxState} state - The state of the app.
* @param {ParticipantFeaturesKey} feature - Feature to be looked up for disable state.
* @returns {boolean}
*/
export function isFeatureDisabled(state: IReduxState, feature: ParticipantFeaturesKey) {
return state['features/jaas'].disabledFeatures.includes(feature);
}
/**
* Sends a request for retrieving jaas JWT.
*
* @param {Object} reqData - The request info.
* @param {string} reqData.appId - The client appId.
* @param {string} reqData.baseUrl - The base url for the request.
* @returns {void}
*/
export async function sendGetJWTRequest({ appId, baseUrl }: {
appId: string;
baseUrl: string;
}) {
const fullUrl = `${baseUrl}/v1/public/token/${encodeURIComponent(appId)}`;
try {
const res = await fetch(fullUrl, {
method: 'GET'
});
if (res.ok) {
return res.json();
}
throw new Error('Request not successful');
} catch (err: any) {
throw new Error(err);
}
}
/**
* Gets a jaas JWT.
*
* @param {IReduxState} state - Redux state.
* @returns {string} The JWT.
*/
export async function getJaasJWT(state: IReduxState) {
const baseUrl = state['features/base/config'].jaasTokenUrl;
const appId = getVpaasTenant(state);
const shouldSendRequest = Boolean(baseUrl && appId);
if (shouldSendRequest) {
try {
const jwt = await sendGetJWTRequest({
appId,
baseUrl: baseUrl ?? ''
});
return jwt.token;
} catch (err) {
logger.error('Could not send request', err);
}
}
}

View File

@@ -0,0 +1,3 @@
import { getLogger } from '../base/logging/functions';
export default getLogger('features/jaas');

View File

@@ -0,0 +1,39 @@
import { createVpaasConferenceJoinedEvent } from '../analytics/AnalyticsEvents';
import { sendAnalytics } from '../analytics/functions';
import { IReduxState } from '../app/types';
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { getVpaasTenant, isVpaasMeeting } from './functions';
/**
* The redux middleware for billing counter.
*
* @param {Store} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case CONFERENCE_JOINED: {
_maybeTrackVpaasConferenceJoin(store.getState());
break;
}
}
return next(action);
});
/**
* Tracks the conference join event if the meeting is a vpaas one.
*
* @param {Store} state - The app state.
* @returns {Function}
*/
function _maybeTrackVpaasConferenceJoin(state: IReduxState) {
if (isVpaasMeeting(state)) {
sendAnalytics(createVpaasConferenceJoinedEvent(
getVpaasTenant(state)));
}
}

View File

View File

@@ -0,0 +1,47 @@
import { redirectToStaticPage } from '../app/actions.web';
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
import {
JitsiConferenceErrors,
JitsiConferenceEvents
} from '../base/lib-jitsi-meet';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { SET_DETAILS } from './actionTypes';
import { STATUSES } from './constants';
import logger from './logger';
/**
* The redux middleware for jaas.
*
* @param {Store} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case CONFERENCE_JOINED: {
const { conference } = action;
if (store.getState()['features/base/config'].iAmRecorder) {
// We don't register anything on web if we are in iAmRecorder mode
return next(action);
}
conference.on(
JitsiConferenceEvents.CONFERENCE_ERROR, (errorType: string, errorMsg: string) => {
errorType === JitsiConferenceErrors.SETTINGS_ERROR && logger.error(errorMsg);
});
break;
}
case SET_DETAILS: {
const { status } = action.payload;
if (status === STATUSES.BLOCKED) {
store.dispatch(redirectToStaticPage('/static/planLimit.html'));
}
break;
}
}
return next(action);
});

View File

@@ -0,0 +1,32 @@
import ReducerRegistry from '../base/redux/ReducerRegistry';
import {
SET_DETAILS
} from './actionTypes';
import { STATUSES } from './constants';
const DEFAULT_STATE = {
disabledFeatures: [],
status: STATUSES.ACTIVE
};
export interface IJaaSState {
[key: string]: any;
}
/**
* Listen for actions that mutate the billing-counter state.
*/
ReducerRegistry.register<IJaaSState>(
'features/jaas', (state = DEFAULT_STATE, action): IJaaSState => {
switch (action.type) {
case SET_DETAILS: {
return action.payload;
}
default:
return state;
}
}
);