Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e94c649
VIDSOL-134: add route to assetlinks json file (#202)
manolovn Aug 13, 2025
bb15256
VIDSOL-105: Background Replacement Support for Web VERA (#190)
OscarFava Aug 19, 2025
0f31f10
VIDSOL-136: Fix vulnerabilities (#204)
dwivedisachin Aug 25, 2025
643efd7
VIDSOL-152: Bump VERA version to 1.2.2 (#207)
behei-vonage Aug 28, 2025
c3e958b
VIDSOL-149: Camera light stays on after toggling video off (#206)
behei-vonage Sep 3, 2025
fbde556
VIDSOL-28: On the participants list, on muting the publisher the mute…
behei-vonage Sep 10, 2025
4470ab5
VIDSOL-10: VERA does not display special characters properly in Parti…
behei-vonage Sep 10, 2025
dcb32ef
VIDSOL-25: Record meeting shows incorrect option for the user who has…
behei-vonage Sep 10, 2025
1c25238
VIDSOL-51: Incorrect running instructions for ngrok (#212)
behei-vonage Sep 15, 2025
c7914c1
VIDSOL-191: Fix axios security issue (#218)
behei-vonage Sep 15, 2025
0250eb2
VIDSOL-144: The mic and camera are toggled back to on when changing B…
OscarFava Sep 16, 2025
47e0286
VIDSOL-192: Bump up OT version to 2.31.0 (#219)
behei-vonage Sep 17, 2025
280b225
VIDSOL-175: Version bump to 1.2.3 (#222)
behei-vonage Sep 17, 2025
f1f8e5a
VIDSOL-16: Localization Support (Implement internationalization with …
arnaud-lebreton-rofim Sep 18, 2025
e333189
VIDSOL-200: Inconsistent use of useTranslation (#230)
behei-vonage Sep 19, 2025
c44fd31
VIDSOL-81: Deep links iOS (#229)
VZaphod Sep 24, 2025
2b366f5
VIDSOL-143: Implement configuration file, VIDSOL-195: Resolve camera …
cpettet Sep 30, 2025
1752798
VIDSOL-162: JIRA component IDs for native (#232)
VZaphod Oct 1, 2025
0404b36
VIDSOL-198: Add languages Json files (#228)
dwivedisachin Oct 1, 2025
059a159
VIDSOL-105: Background Replacement Support for Web VERA - Custom Imag…
OscarFava Oct 1, 2025
8f4d7de
VIDSOL-81: Remove web credentials (#237)
VZaphod Oct 7, 2025
4a734f2
VIDSOL-218: Clean audio/video device label (#239)
OscarFava Oct 9, 2025
cfae016
VIDSOL-205: update assetlinks file (#244)
manolovn Oct 22, 2025
40eca76
VIDSOL-212: background replacement translations (#238)
OscarFava Oct 22, 2025
3f48e8a
VIDSOL-34: Scroll/Zoom on ScreenShare (#203)
cpettet Oct 22, 2025
b4d927b
VIDSOL-208: Language selector (#242)
OscarFava Oct 23, 2025
1a694e0
VIDSOL-246: I was in call but other people can't see me and I was abl…
OscarFava Oct 23, 2025
fbbec4e
VIDSOL-165: web documentation for configurable features (#248)
johnny-quesada-developer Oct 29, 2025
c5e0e3a
VIDSOL-255: Add subscription error handling (#246)
OscarFava Oct 29, 2025
96d0ff7
VIDSOL-279: Add aliases and configuration updates (#249)
johnny-quesada-developer Oct 30, 2025
67ef2fc
Merge remote-tracking branch 'origin/main' into release-1.3.0
OscarFava Oct 31, 2025
bf5f06d
Bump version 1.3.0
OscarFava Oct 31, 2025
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
5 changes: 4 additions & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"

yarn lint-staged
# Exit on error and unset variables
set -eu

yarn --silent quality-check
3 changes: 0 additions & 3 deletions .lintstagedrc

This file was deleted.

24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,31 @@ This application provides features for common conferencing use cases, such as:
<summary>Input and output device selectors.</summary>
<img src="docs/assets/DeviceSelector.png" alt="Screenshot of audio devices selector">
</details>
- Background blur and noise suppression toggles.
- <details>
<summary>Noise suppression toggles in meeting room</summary>
<img src="docs/assets/NoiseSupression.png" alt="Screenshot of noise supression toggle">
</details>
- <details>
<summary>
Background effects in meeting and waiting room. You can set predefined images, custom image or slight/strong background blur. Images can be uploaded from local device or URL in these formats: JPG, PNG, GIF or BMP. Background effects are not supported in non-Chromium-based browsers or on iOS.

Please see [OT.hasMediaProcessorSupport](https://vonage.github.io/video-docs/video-js-reference/latest/OT.html#hasMediaProcessorSupport) for more information.
</summary>

<img src="docs/assets/BGEffects.png" alt="Screenshot of background effects">
</details>
- <details>
<summary>
Configurable features: adapt the app to your specific use cases and roles.
Configuration is handled through a <em>config.json</em> file that can be moved to the <em>frontend/public</em> folder; there's an example in the base directory, <em>config.example.json</em>. Changes to the config file will be reflected immediately in most cases, but some rooms will need to be re-joined to take effect (For example: "setting the default layout" or "audio on join").
</summary>
<img src="docs/assets/configFile.png" alt="Screenshot of a config.json">
- <details>
<summary>Composed archiving capabilities to record your meetings.</summary>
<img src="docs/assets/Archiving.png" alt="Screenshot of archiving dialog box">
</details>
- <details>
<summary>In-call tools such as screen sharing, group chat function, and emoji reactions.</summary>
<summary>In-call tools such as screen sharing (subscriber can zoom in/out if hasMediaProcessorSupport), group chat function, and emoji reactions.</summary>
<img src="docs/assets/Emojis.png" alt="Screenshot of emojis">
</details>
- Active speaker detection.
Expand Down Expand Up @@ -99,6 +117,8 @@ The Vonage Video API Reference App for React is currently supported on the lates
- ![Safari icon](/docs/assets/safari.svg) Safari
- ![Electron icon](/docs/assets/electron.svg) Electron

*Note:* Some browsers such as Firefox or Safari do not support media processors like video and audio filters (e.g background effects): Please see [OT.hasMediaProcessorSupport](https://vonage.github.io/video-docs/video-js-reference/latest/OT.html#hasMediaProcessorSupport) for more information.

*Note:* Mobile web views have limited supported at the moment. The minimum supported device width is `360px`.

## Requirements
Expand Down
2 changes: 2 additions & 0 deletions backend/helpers/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const loadConfig = (): Config => {
token: process.env.JIRA_TOKEN,
key: process.env.JIRA_PROJECT_KEY,
componentId: process.env.JIRA_COMPONENT_ID,
iOSComponentId: process.env.JIRA_iOS_COMPONENT_ID,
androidComponentId: process.env.JIRA_ANDROID_COMPONENT_ID,
epicLink: process.env.JIRA_EPIC_LINK,
epicUrl: process.env.JIRA_EPIC_URL,
};
Expand Down
2 changes: 2 additions & 0 deletions backend/jest/setEnvVars.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ process.env.JIRA_API_URL = 'https://example.com';
process.env.JIRA_URL = 'https://example.com';
process.env.JIRA_TOKEN = 'JIRA';
process.env.JIRA_COMPONENT_ID = 'componentId';
process.env.JIRA_iOS_COMPONENT_ID = 'iOSComponentId';
process.env.JIRA_ANDROID_COMPONENT_ID = 'androidComponentId';
process.env.JIRA_EPIC_LINK = 'jiraEpicLink';
process.env.JIRA_EPIC_URL = 'jiraEpicUrl';
process.env.VONAGE_APP_ID = 'vonageAppId';
Expand Down
10 changes: 10 additions & 0 deletions backend/jest/setEnvVars.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ describe('Environment Variables', () => {
expect(process.env.JIRA_COMPONENT_ID).toBe(jiraComponentId);
});

test('should have the correct JIRA_iOS_COMPONENT_ID', () => {
const jiraComponentId = 'iOSComponentId';
expect(process.env.JIRA_iOS_COMPONENT_ID).toBe(jiraComponentId);
});

test('should have the correct JIRA_ANDROID_COMPONENT_ID', () => {
const jiraComponentId = 'androidComponentId';
expect(process.env.JIRA_ANDROID_COMPONENT_ID).toBe(jiraComponentId);
});

test('should have the correct JIRA_EPIC_LINK', () => {
const jiraEpicLink = 'jiraEpicLink';
expect(process.env.JIRA_EPIC_LINK).toBe(jiraEpicLink);
Expand Down
11 changes: 6 additions & 5 deletions backend/package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"name": "backend",
"version": "1.2.3",
"version": "1.3.0",
"description": "Express-based backend with authenticated routes.",
"main": "index.js",
"type": "module",
"scripts": {
"dev": "tsx watch index.ts",
"start": "node --import tsx index.ts",
"debug": "node --inspect --watch --import tsx index.ts",
"dev": "nodemon --watch . --ext ts,tsx,json --ignore dist --exec \"sh -c 'yarn ts-check && node --import tsx index.ts'\"",
"start": "yarn ts-check && node --import tsx index.ts",
"debug": "nodemon --watch . --ext ts,tsx,json --ignore dist --exec \"sh -c 'yarn ts-check && node --inspect --import tsx index.ts'\"",
"test": "NODE_OPTIONS=\"--experimental-vm-modules\" jest --maxWorkers=1 --coverage",
"test:watch": "yarn test --watch",
"ts-check": "tsc -p tsconfig.json"
"ts-check": "tsc --noEmit -p tsconfig.json --pretty true"
},
"author": "",
"license": "MIT",
Expand Down Expand Up @@ -41,6 +41,7 @@
"@types/supertest": "^6.0.2",
"@types/validator": "^13.15.2",
"jest": "^29.7.0",
"nodemon": "^3.1.10",
"supertest": "^7.0.0",
"ts-jest": "^29.1.2"
}
Expand Down
21 changes: 20 additions & 1 deletion backend/routes/feedback.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
import { Request, Response, Router } from 'express';
import getFeedbackService from '../services/getFeedbackService';
import { FeedbackOrigin } from '../types/feedback';

const feedbackRouter = Router();
const feedbackService = getFeedbackService();

feedbackRouter.post('/report', async (req: Request, res: Response) => {
const { headers } = req;
const { title, name, issue, attachment } = req.body;

try {
const feedbackData = await feedbackService.reportIssue({ title, name, issue, attachment });
let origin: FeedbackOrigin = 'web';
const useragent = headers['User-Agent'] || headers['user-agent'];

if (useragent != null) {
if (useragent.includes('VeraNativeiOS')) {
origin = 'iOS';
} else if (useragent.includes('VeraNativeAndroid')) {
origin = 'Android';
}
}

const feedbackData = await feedbackService.reportIssue({
title,
name,
issue,
attachment,
origin,
});
if (feedbackData) {
return res.status(200).json({ feedbackData });
}
Expand Down
2 changes: 2 additions & 0 deletions backend/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { Router } from 'express';
import healthRoute from './health';
import sessionRouter from './session';
import feedbackRouter from './feedback';
import wellKnownRouter from './wellKnown';

const router = Router();

router.use('/_', healthRoute);
router.use('/session', sessionRouter);
router.use('/feedback', feedbackRouter);
router.use('/.well-known', wellKnownRouter);

export default router;
66 changes: 66 additions & 0 deletions backend/routes/wellKnown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Request, Response, Router } from 'express';

const wellKnownRouter = Router();

/**
* Serve the static file needed for Android deep linking
* more info: https://developer.android.com/training/app-links/verify-android-applinks#publish-json
*/
wellKnownRouter.get('/assetlinks.json', (_req: Request, res: Response) => {
res.json([
{
// Grants the target permission to handle all URLs that the source can handle
relation: ['delegate_permission/common.handle_all_urls'],
target: {
namespace: 'android_app',
// Package name for the Android app
package_name: 'com.vonage.android.debug',
// SHA-256 certificate fingerprints for the Android app
sha256_cert_fingerprints: [
'A4:26:72:80:DA:75:99:75:ED:D2:32:ED:0A:DC:2C:7C:27:78:6A:8C:9A:37:22:41:23:CF:9E:DB:03:78:FC:6C',
],
},
},
{
// Grants the target permission to handle all URLs that the source can handle
relation: ['delegate_permission/common.handle_all_urls'],
target: {
namespace: 'android_app',
// Package name for the Android app
package_name: 'com.vonage.android',
// SHA-256 certificate fingerprints for the Android app
sha256_cert_fingerprints: [
'A5:77:34:82:4C:98:13:CF:88:DF:20:46:2C:B7:7E:33:C0:4C:FC:C6:A1:E4:B3:25:5F:43:49:BA:FE:B5:43:27',
],
},
},
]);
});

/**
* Serve the static file needed for iOS app site association
* more info: https://developer.apple.com/documentation/xcode/supporting-associated-domains
*/
wellKnownRouter.get('/apple-app-site-association', (_req: Request, res: Response) => {
res.json({
applinks: {
details: [
{
appIDs: ['PR6C39UQ38.com.vonage.VERA'],
components: [
{
'/': '/waiting-room/*',
comment: 'Matches any waiting room URL',
},
{
'/': '/room/*',
comment: 'Matches any room URL',
},
],
},
],
},
});
});

export default wellKnownRouter;
20 changes: 18 additions & 2 deletions backend/services/jiraFeedbackService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ import FormData from 'form-data';
import { FeedbackService } from './feedbackService';
import loadConfig from '../helpers/config';
import { Config } from '../types/config';
import { FeedbackData, ReportIssueReturn } from '../types/feedback';
import { FeedbackData, FeedbackOrigin, ReportIssueReturn } from '../types/feedback';

class JiraFeedbackService implements FeedbackService {
jiraApiUrl: string;
jiraUrl: string;
jiraToken: string;
jiraKey: string;
jiraComponentId: string;
jiraiOSComponentId: string;
jiraAndroidComponentId: string;
jiraEpicUrl: string;
jiraEpicLink: string;
config: Config;
Expand All @@ -21,6 +23,8 @@ class JiraFeedbackService implements FeedbackService {
this.jiraToken = this.config.token as string;
this.jiraKey = this.config.key as string;
this.jiraComponentId = this.config.componentId as string;
this.jiraiOSComponentId = this.config.iOSComponentId as string;
this.jiraAndroidComponentId = this.config.androidComponentId as string;
this.jiraUrl = this.config.url as string;
this.jiraEpicUrl = this.config.epicUrl as string;
this.jiraEpicLink = this.config.epicLink as string;
Expand All @@ -39,7 +43,7 @@ class JiraFeedbackService implements FeedbackService {
},
components: [
{
id: this.jiraComponentId,
id: this.getComponentIdByOrigin(data.origin),
},
],
[this.jiraEpicLink]: this.jiraEpicUrl,
Expand Down Expand Up @@ -95,5 +99,17 @@ class JiraFeedbackService implements FeedbackService {
screenshotIncluded: true,
};
}

private getComponentIdByOrigin(origin: FeedbackOrigin): string {
switch (origin) {
case 'iOS':
return this.jiraiOSComponentId;
case 'Android':
return this.jiraAndroidComponentId;
case 'web':
default:
return this.jiraComponentId;
}
}
}
export default JiraFeedbackService;
85 changes: 84 additions & 1 deletion backend/services/tests/jiraFeedbackService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import axios from 'axios';
import { describe, expect, it, beforeAll, afterAll, jest } from '@jest/globals';
import FormData from 'form-data';
import JiraFeedbackService from '../jiraFeedbackService';
import { FeedbackData } from '../../types/feedback';
import { FeedbackData, FeedbackOrigin } from '../../types/feedback';

jest.mock('axios');

Expand All @@ -12,6 +12,7 @@ describe('JiraFeedbackService', () => {
title: 'Nothing works.',
name: 'John Doe',
issue: 'This does not even work',
origin: 'web' as FeedbackOrigin,
};

const mockPost = jest.spyOn(axios, 'post');
Expand Down Expand Up @@ -92,4 +93,86 @@ describe('JiraFeedbackService', () => {
const feedbackService = await jiraFeedbackService.reportIssue(feedbackData);
expect(feedbackService).toHaveProperty('screenshotIncluded', true);
});

it('should create a valid iOS issue and return the issue key', async () => {
const feedbackData: FeedbackData = {
...sharedData,
attachment: '',
origin: 'iOS',
};

const mockTicketResponse: { data: { key: string } } = {
data: {
key: '2024',
},
};

mockPost.mockResolvedValue(mockTicketResponse);

await jiraFeedbackService.reportIssue(feedbackData);
expect(axios.post).toHaveBeenCalledWith(
jiraFeedbackService.jiraApiUrl,
{
fields: {
project: { key: jiraFeedbackService.jiraKey },
summary: 'Nothing works.',
description: 'Reported by: John Doe\n\n Issue description:\nThis does not even work',
issuetype: { name: 'Bug' },
components: [
{
id: jiraFeedbackService.jiraiOSComponentId,
},
],
[jiraFeedbackService.jiraEpicLink]: jiraFeedbackService.jiraEpicUrl,
},
},
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${jiraFeedbackService.jiraToken}`,
},
}
);
});

it('should create a valid Android issue and return the issue key', async () => {
const feedbackData: FeedbackData = {
...sharedData,
attachment: '',
origin: 'Android',
};

const mockTicketResponse: { data: { key: string } } = {
data: {
key: '2024',
},
};

mockPost.mockResolvedValue(mockTicketResponse);

await jiraFeedbackService.reportIssue(feedbackData);
expect(axios.post).toHaveBeenCalledWith(
jiraFeedbackService.jiraApiUrl,
{
fields: {
project: { key: jiraFeedbackService.jiraKey },
summary: 'Nothing works.',
description: 'Reported by: John Doe\n\n Issue description:\nThis does not even work',
issuetype: { name: 'Bug' },
components: [
{
id: jiraFeedbackService.jiraAndroidComponentId,
},
],
[jiraFeedbackService.jiraEpicLink]: jiraFeedbackService.jiraEpicUrl,
},
},
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${jiraFeedbackService.jiraToken}`,
},
}
);
});
});
Loading
Loading