Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ deploy-init:
deploy-appbundle:
cp $(BUILD_DIR)/*.min.js $(DEPLOY_DIR)
-cp $(BUILD_DIR)/*.min.js.map $(DEPLOY_DIR)
@if [ -d "$(BUILD_DIR)/chunks" ]; then \
mkdir -p $(DEPLOY_DIR)/chunks; \
cp $(BUILD_DIR)/chunks/* $(DEPLOY_DIR)/chunks/; \
fi

deploy-lib-meet:
cp \
Expand Down
4 changes: 2 additions & 2 deletions conference.js
Original file line number Diff line number Diff line change
Expand Up @@ -2133,7 +2133,7 @@ export default {
* @param {boolean} [notifyOnConferenceTermination] whether to notify
* the user on conference termination
*/
hangup(requestFeedback = false, hangupReason, notifyOnConferenceTermination) {
hangup(requestFeedback = false, hangupReason, notifyOnConferenceTermination, hangupMessage) {
APP.store.dispatch(disableReceiver());

this._stopProxyConnection();
Expand All @@ -2154,7 +2154,7 @@ export default {
const feedbackDialogClosed = (feedbackResult = {}) => {
if (!feedbackResult.wasDialogShown && hangupReason && notifyOnConferenceTermination) {
return APP.store.dispatch(
openLeaveReasonDialog(hangupReason)).then(() => feedbackResult);
openLeaveReasonDialog(hangupReason, hangupMessage)).then(() => feedbackResult);
}

return Promise.resolve(feedbackResult);
Expand Down
Binary file added images/icon-small.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/logo192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed images/logo512.png
Binary file not shown.
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<meta name="theme-color" content="#2A3A4B">
<!--#include virtual="base.html" -->

<link rel="apple-touch-icon" href="images/apple-touch-icon.png">
<link rel="apple-touch-icon" href="images/icon-small.png">
<link rel="stylesheet" href="css/all.css">
<!--#include virtual="fonts.html"-->
<link rel="manifest" id="manifest-placeholder">
Expand Down
2 changes: 2 additions & 0 deletions lang/main-es.json
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,8 @@
"incorrectRoomLockPassword": "Contraseña incorrecta",
"internalError": "¡Ups! Algo salió mal. El siguiente error ocurrió: {{error}}",
"internalErrorTitle": "Error interno",
"kickDuplicateTitle": "Oops! Ya estas en la reunión ",
"kickDuplicateMessage": "Parece que ya estabas en la reunión. Prueba a mirar en otra ventana, quiza ya tengas la reunion iniciada.",
"kickMessage": "Puede ponerse en contacto con {{participantDisplayName}} para obtener más detalles.",
"kickParticipantButton": "Expulsar",
"kickParticipantDialog": "¿Seguro que quiere expulsar a este participante?",
Expand Down
2 changes: 2 additions & 0 deletions lang/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,8 @@
"incorrectRoomLockPassword": "Incorrect password",
"internalError": "Oops! Something went wrong. The following error occurred: {{error}}",
"internalErrorTitle": "Internal error",
"kickDuplicateTitle": "Oops! You are already in the meeting",
"kickDuplicateMessage": "It looks like you already joined this meeting. Try looking into other open windows where you might have the meeting running",
"kickMessage": "You can contact {{participantDisplayName}} for more details.",
"kickParticipantButton": "Kick",
"kickParticipantDialog": "Are you sure you want to kick this participant?",
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"@internxt/css-config": "^1.1.0",
"@internxt/eslint-config-internxt": "^2.0.1",
"@internxt/lib": "^1.4.1",
"@internxt/sdk": "1.16.3",
"@internxt/sdk": "1.17.0",
"@internxt/ui": "0.1.15",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/0.18.5/jitsi-excalidraw-v0.18.5.tgz",
"@jitsi/js-utils": "2.6.7",
Expand Down Expand Up @@ -85,7 +85,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-meet": "https://github.com/internxt/lib-meet/releases/download/v.1.0.0/lib-meet-1.0.0.tgz",
"lib-meet": "https://github.com/internxt/lib-meet/releases/download/v.1.0.1/lib-meet-1.0.1.tgz",
"lodash-es": "4.18.1",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
Expand Down
6 changes: 4 additions & 2 deletions react/features/base/connection/actions.any.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,15 +247,17 @@ export function _connectInternal({
let userUUID: string | undefined;

if (isAnonymous) {
userUUID = SessionStorageManager.instance.getOrCreateAnonymousUUID();
userUUID = SessionStorageManager.instance.getOrCreateUserID();
}
const { token: jwt, appId } = await MeetingService.instance.joinCall(room, {
const { token: jwt, appId, userId } = await MeetingService.instance.joinCall(room, {
name: displayName ?? name ?? "",
lastname: lastname ?? "",
anonymous: !!isAnonymous,
anonymousId: userUUID,
});

SessionStorageManager.instance.setUserID(userId);

const newOptions = get8x8Options(options, appId, room);

const connection = new JitsiMeetJS.JitsiConnection(appId, jwt, newOptions);
Expand Down
12 changes: 5 additions & 7 deletions react/features/base/connection/actions.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@ import { SessionStorageManager } from '../meet/SessionStorageManager';
* @returns {Promise<void>}
*/
export async function leaveCallWithUserIdentification(roomId: string): Promise<void> {
const user = LocalStorageManager.instance.getUser();
let payload = undefined;
if (!user){
payload = { userId: SessionStorageManager.instance.getAnonymousUUID() || '' };
const userId = SessionStorageManager.instance.getUserID();
if (userId) {
return await MeetingService.instance.leaveCall(roomId, { userId });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this causes an error, does the web crash?

Copy link
Copy Markdown
Author

@TamaraFinogina TamaraFinogina May 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, but the server gets to do checks and log errors. If the user reloads before joining the call, leaveCall is dispatched, but it has no user ID, so technically no need for the call

}
return await MeetingService.instance.leaveCall(roomId, payload);
}

export * from "./actions.any";
Expand Down Expand Up @@ -105,7 +103,7 @@ export function connect(id?: string, password?: string) {
* the user on conference termination.
* @returns {Function}
*/
export function hangup(requestFeedback = false, roomId?: string, feedbackTitle?: string, notifyOnConferenceTermination?: boolean) {
export function hangup(requestFeedback = false, roomId?: string, feedbackTitle?: string, notifyOnConferenceTermination?: boolean, feedbackMessage?: string) {
// XXX For web based version we use conference hanging up logic from the old app.
return async (dispatch: IStore["dispatch"]) => {
if (LocalRecordingManager.isRecordingLocally()) {
Expand All @@ -132,7 +130,7 @@ export function hangup(requestFeedback = false, roomId?: string, feedbackTitle?:

await leaveCallWithUserIdentification(roomId);

return APP.conference.hangup(requestFeedback, feedbackTitle, notifyOnConferenceTermination);
return APP.conference.hangup(requestFeedback, feedbackTitle, notifyOnConferenceTermination, feedbackMessage);
};
}

Expand Down
46 changes: 46 additions & 0 deletions react/features/base/meet/LocalStorageManager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { beforeEach, describe, expect, test, vi } from "vitest";
import { LocalStorageManager } from "./LocalStorageManager";

const MOCK_DISPlAY_NAME = "mock-display-name";
const key = (LocalStorageManager as any)['KEYS']?.DISPLAY_NAME;

describe("LocalStorageManager tests", () => {
beforeEach(() => {
localStorage.clear();
vi.clearAllMocks();
});

test("when local storage is called repeatedly, then the same instance is returned", () => {
const a = LocalStorageManager.instance;
const b = LocalStorageManager.instance;
expect(a).toBe(b);
});

test("when no display name is stored, then returns underfined", () => {
expect(LocalStorageManager.instance.getDisplayName()).toBeUndefined();
});

test("when display name is stored, then returns it", () => {
LocalStorageManager.instance.setDisplayName(MOCK_DISPlAY_NAME);
expect(LocalStorageManager.instance.getDisplayName()).toBe(MOCK_DISPlAY_NAME);
});

test("when the stored display name was modified, then returns the last modification", () => {
LocalStorageManager.instance.setDisplayName(MOCK_DISPlAY_NAME);
const modifiedName = 'new-display-name';
localStorage.setItem(key, modifiedName);
expect(LocalStorageManager.instance.getDisplayName()).toBe(modifiedName);
});

test("when display name is saved in the local storage, then it does not bleed into the session storage", () => {
LocalStorageManager.instance.setDisplayName(MOCK_DISPlAY_NAME);
expect(sessionStorage.getItem(key)).toBeNull();
});

test("when local storage is cleaned, then display name becomes underfined", () => {
LocalStorageManager.instance.setDisplayName(MOCK_DISPlAY_NAME);
LocalStorageManager.instance.clearCredentials();
expect(LocalStorageManager.instance.getDisplayName()).toBeUndefined();
});

});
15 changes: 15 additions & 0 deletions react/features/base/meet/LocalStorageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export class LocalStorageManager {
MNEMONIC: "xMnemonic",
USER: "xUser",
SUBSCRIPTION: "xSubscription",
DISPLAY_NAME: "xMeetDisplayName",
};

private constructor() {}
Expand Down Expand Up @@ -169,6 +170,19 @@ export class LocalStorageManager {
this.remove(LocalStorageManager.KEYS.SUBSCRIPTION);
}

/**
* Gets the user display name
*/
public getDisplayName(): string | null | undefined {
return this.get<string>(LocalStorageManager.KEYS.DISPLAY_NAME);
}

/**
* Sets the user display name
*/
public setDisplayName(displayName: string): void {
this.set(LocalStorageManager.KEYS.DISPLAY_NAME, displayName);
}

/**
* Saves the session credentials
Expand All @@ -195,6 +209,7 @@ export class LocalStorageManager {
this.remove(LocalStorageManager.KEYS.MNEMONIC);
this.remove(LocalStorageManager.KEYS.USER);
this.remove(LocalStorageManager.KEYS.SUBSCRIPTION);
this.remove(LocalStorageManager.KEYS.DISPLAY_NAME);
}

public clearStorage(): void {
Expand Down
10 changes: 5 additions & 5 deletions react/features/base/meet/SessionStorageManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,31 @@ describe("SessionStorageManager tests", () => {
});

it("returns null when no anonymous UUID is stored", () => {
expect(SessionStorageManager.instance.getAnonymousUUID()).toBeNull();
expect(SessionStorageManager.instance.getUserID()).toBeNull();
});

it("returns the stored anonymous UUID when one exists", () => {
sessionStorage.setItem(ANON_UUID_KEY, "existing-uuid");
expect(SessionStorageManager.instance.getAnonymousUUID()).toBe("existing-uuid");
expect(SessionStorageManager.instance.getUserID()).toBe("existing-uuid");
});

it("generates and stores a UUID when none exists", () => {
const result = SessionStorageManager.instance.getOrCreateAnonymousUUID();
const result = SessionStorageManager.instance.getOrCreateUserID();
expect(result).toBe(MOCK_UUID);
expect(sessionStorage.getItem(ANON_UUID_KEY)).toBe(MOCK_UUID);
});

it("returns existing UUID without generating a new one", async () => {
sessionStorage.setItem(ANON_UUID_KEY, "pre-existing-uuid");

const result = SessionStorageManager.instance.getOrCreateAnonymousUUID();
const result = SessionStorageManager.instance.getOrCreateUserID();

expect(result).toBe("pre-existing-uuid");
expect(v4).not.toHaveBeenCalled();
});

it("does not bleed into localStorage", () => {
SessionStorageManager.instance.getOrCreateAnonymousUUID();
SessionStorageManager.instance.getOrCreateUserID();
expect(localStorage.getItem(ANON_UUID_KEY)).toBeNull();
});

Expand Down
15 changes: 10 additions & 5 deletions react/features/base/meet/SessionStorageManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { v4 } from "uuid";

export const ANON_UUID_KEY = "xAnonymousUserUUID";
export const ANON_UUID_KEY = "xMeetUserUUID";

export class SessionStorageManager {
private static _instance: SessionStorageManager;
Expand All @@ -14,20 +14,25 @@ export class SessionStorageManager {
return SessionStorageManager._instance;
}

public getOrCreateAnonymousUUID(): string {
let uuid = this.getAnonymousUUID();
public getOrCreateUserID(): string {
let uuid = this.getUserID();
if (!uuid) {
uuid = v4();
sessionStorage.setItem(ANON_UUID_KEY, uuid);
this.setUserID(uuid);
}

return uuid;
}

public getAnonymousUUID(): string | null {
public getUserID(): string | null {
return sessionStorage.getItem(ANON_UUID_KEY);
}

public setUserID(userId: string): void {
sessionStorage.setItem(ANON_UUID_KEY, userId);
}


}

export default SessionStorageManager.instance;
4 changes: 2 additions & 2 deletions react/features/base/meet/general/components/ErrorMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { WarningCircle } from "@phosphor-icons/react";
import { WarningCircleIcon } from "@phosphor-icons/react";
import React from "react";

interface ErrorMessageProps {
Expand All @@ -8,7 +8,7 @@ interface ErrorMessageProps {
export const ErrorMessage: React.FC<ErrorMessageProps> = ({ message }) => (
<div className="flex flex-row items-start pt-1">
<div className="flex h-5 flex-row items-center">
<WarningCircle weight="fill" className="mr-1 h-4 text-red" />
<WarningCircleIcon weight="fill" className="mr-1 h-4 text-red" />
</div>
<span className="font-base w-full text-sm text-red">{message}</span>
</div>
Expand Down
18 changes: 12 additions & 6 deletions react/features/base/meet/general/components/MediaControls.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { CircleButton } from "@internxt/ui";
import { ExclamationMark, Microphone, MicrophoneSlash, VideoCamera, VideoCameraSlash } from "@phosphor-icons/react";
import {
ExclamationMarkIcon,
MicrophoneIcon,
MicrophoneSlashIcon,
VideoCameraIcon,
VideoCameraSlashIcon
} from "@phosphor-icons/react";
import React, { useState } from "react";

import MeetAudioSettingsPopUp from "../containers/MeetAudioSettingsPopup";
Expand All @@ -16,7 +22,7 @@ interface MediaControlsProps {
onAudioOptionsClick: () => void;
}
const indicatorProps = {
icon: <ExclamationMark size={12} color="white" weight="bold" />,
icon: <ExclamationMarkIcon size={12} color="white" weight="bold" />,
className: "bg-orange",
};
const MediaControls: React.FC<MediaControlsProps> = ({
Expand Down Expand Up @@ -54,9 +60,9 @@ const MediaControls: React.FC<MediaControlsProps> = ({
}}
>
{hasVideoPermissions && !isVideoMuted ? (
<VideoCamera size={22} color="black" weight="fill" />
<VideoCameraIcon size={22} color="black" weight="fill" />
) : (
<VideoCameraSlash size={22} color="white" weight="fill" />
<VideoCameraSlashIcon size={22} color="white" weight="fill" />
)}
</CircleButton>
<CircleButton
Expand All @@ -77,9 +83,9 @@ const MediaControls: React.FC<MediaControlsProps> = ({
}}
>
{isAudioMuted || !hasAudioPermissions ? (
<MicrophoneSlash size={20} color="white" weight="fill" />
<MicrophoneSlashIcon size={20} color="white" weight="fill" />
) : (
<Microphone size={22} color="black" weight="fill" />
<MicrophoneIcon size={22} color="black" weight="fill" />
)}
</CircleButton>
</div>
Expand Down
10 changes: 5 additions & 5 deletions react/features/base/meet/general/components/PermissionModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TransparentModal } from "@internxt/ui";
import { Microphone, VideoCamera } from "@phosphor-icons/react";
import { MicrophoneIcon, VideoCameraIcon } from "@phosphor-icons/react";
import React from "react";
import ReactDOM from "react-dom";
import { createPortal } from "react-dom";

export interface PermissionModalProps {
translate: (key: string) => string;
Expand Down Expand Up @@ -30,10 +30,10 @@ const PermissionModal = ({
<div className="bg-orange/50 rounded-[20px] p-5 mb-4">
<div className="flex justify-center gap-2 mb-4">
<div className="w-12 h-12 bg-white/25 rounded-full flex items-center justify-center">
<VideoCamera className="text-white" size={24} weight="fill" />
<VideoCameraIcon className="text-white" size={24} weight="fill" />
</div>
<div className="w-12 h-12 bg-white/25 rounded-full flex items-center justify-center">
<Microphone className="text-white" size={24} weight="fill" />
<MicrophoneIcon className="text-white" size={24} weight="fill" />
</div>
</div>
<p
Expand All @@ -53,7 +53,7 @@ const PermissionModal = ({
</TransparentModal>
);

return ReactDOM.createPortal(modalContent, bodyElement);
return createPortal(modalContent, bodyElement);
};

export default PermissionModal;
Loading
Loading