testapp: add functionality to delete all rooms and add core tests
This commit is contained in:
parent
be7a37a004
commit
64789951d3
58
.github/workflows/wc-e2e-test.yaml
vendored
58
.github/workflows/wc-e2e-test.yaml
vendored
@ -5,6 +5,64 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
|
e2e-test-core:
|
||||||
|
name: E2E Tests Core
|
||||||
|
runs-on: ov-actions-runner
|
||||||
|
steps:
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
- name: Setup OpenVidu Local Deployment
|
||||||
|
uses: OpenVidu/actions/start-openvidu-local-deployment@main
|
||||||
|
- name: Setup OpenVidu Meet
|
||||||
|
uses: OpenVidu/actions/start-openvidu-meet@main
|
||||||
|
env:
|
||||||
|
MEET_WEBHOOK_ENABLED: true
|
||||||
|
- name: Start testapp
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cd testapp
|
||||||
|
npm run start > ../testapp.log 2>&1 &
|
||||||
|
- name: Wait for testapp to Start
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "Waiting for testapp to start on http://localhost:5080..."
|
||||||
|
for i in {1..30}; do
|
||||||
|
if curl -s http://localhost:5080 >/dev/null 2>&1; then
|
||||||
|
echo "Testapp is ready!"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Attempt $i/30: Testapp not ready yet, waiting 1 second..."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo "Timeout: Testapp failed to start within 30 seconds"
|
||||||
|
exit 1
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
cd frontend/webcomponent
|
||||||
|
# Install Playwright browsers
|
||||||
|
mkdir -p /tmp/ms-playwright
|
||||||
|
PLAYWRIGHT_BROWSERS_PATH=/tmp/ms-playwright npx playwright install --with-deps chromium
|
||||||
|
npm run test:e2e-core
|
||||||
|
env:
|
||||||
|
RUN_MODE: CI
|
||||||
|
PLAYWRIGHT_BROWSERS_PATH: /tmp/ms-playwright
|
||||||
|
- name: Upload failed test videos
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: test-videos
|
||||||
|
path: |
|
||||||
|
frontend/webcomponent/test-results/*/*.webm
|
||||||
|
retention-days: 2
|
||||||
|
- name: Clean up
|
||||||
|
if: always()
|
||||||
|
uses: OpenVidu/actions/cleanup@main
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
webcomponent-e2e-test:
|
webcomponent-e2e-test:
|
||||||
name: WebComponent E2E Tests
|
name: WebComponent E2E Tests
|
||||||
runs-on: ov-actions-runner
|
runs-on: ov-actions-runner
|
||||||
|
|||||||
278
frontend/webcomponent/tests/e2e/core/room.test.ts
Normal file
278
frontend/webcomponent/tests/e2e/core/room.test.ts
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import { PNG } from 'pngjs';
|
||||||
|
import pixelmatch from 'pixelmatch';
|
||||||
|
import {
|
||||||
|
applyVirtualBackground,
|
||||||
|
interactWithElementInIframe,
|
||||||
|
joinRoomAs,
|
||||||
|
leaveRoom,
|
||||||
|
prepareForJoiningRoom,
|
||||||
|
saveScreenshot,
|
||||||
|
startScreenSharing,
|
||||||
|
stopScreenSharing,
|
||||||
|
waitForElementInIframe
|
||||||
|
} from '../../helpers/function-helpers.js';
|
||||||
|
|
||||||
|
let subscribedToAppErrors = false;
|
||||||
|
|
||||||
|
test.describe('Room Functionality Tests', () => {
|
||||||
|
const testAppUrl = 'http://localhost:5080';
|
||||||
|
const testRoomPrefix = 'testing-room';
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// SETUP & TEARDOWN
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
test.beforeAll(async ({ browser }) => {
|
||||||
|
// Create a test room before all tests
|
||||||
|
const tempContext = await browser.newContext();
|
||||||
|
const tempPage = await tempContext.newPage();
|
||||||
|
await tempPage.goto(testAppUrl);
|
||||||
|
await tempPage.waitForSelector('.create-room');
|
||||||
|
await tempPage.fill('#room-id-prefix', testRoomPrefix);
|
||||||
|
await tempPage.click('.create-room-btn');
|
||||||
|
await tempPage.waitForSelector(`#${testRoomPrefix}`);
|
||||||
|
await tempPage.close();
|
||||||
|
await tempContext.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
if (!subscribedToAppErrors) {
|
||||||
|
page.on('console', (msg) => {
|
||||||
|
const type = msg.type();
|
||||||
|
const tag = type === 'error' ? 'ERROR' : type === 'warning' ? 'WARNING' : 'LOG';
|
||||||
|
console.log('[' + tag + ']', msg.text());
|
||||||
|
});
|
||||||
|
subscribedToAppErrors = true;
|
||||||
|
}
|
||||||
|
await prepareForJoiningRoom(page, testAppUrl, testRoomPrefix);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterEach(async ({ context }) => {
|
||||||
|
// Save storage state after each test
|
||||||
|
await context.storageState({ path: 'test_localstorage_state.json' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll(async ({ browser }) => {
|
||||||
|
// Cleanup: delete the test room
|
||||||
|
const tempContext = await browser.newContext();
|
||||||
|
const tempPage = await tempContext.newPage();
|
||||||
|
await tempPage.goto(testAppUrl);
|
||||||
|
await tempPage.waitForSelector('#delete-all-rooms');
|
||||||
|
await tempPage.click('#delete-all-rooms');
|
||||||
|
await tempPage.close();
|
||||||
|
await tempContext.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// BASIC FUNCTIONALITY TESTS
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
test.describe('Basic Room Features', () => {
|
||||||
|
test('should show the toolbar and media buttons', async ({ page }) => {
|
||||||
|
await joinRoomAs('publisher', page);
|
||||||
|
await waitForElementInIframe(page, '#toolbar');
|
||||||
|
|
||||||
|
// Check media buttons are present
|
||||||
|
await waitForElementInIframe(page, '#camera-btn');
|
||||||
|
await waitForElementInIframe(page, '#mic-btn');
|
||||||
|
|
||||||
|
await leaveRoom(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should start a videoconference and display video elements', async ({ page, browser }) => {
|
||||||
|
// First participant joins
|
||||||
|
await joinRoomAs('publisher', page);
|
||||||
|
await waitForElementInIframe(page, 'ov-session');
|
||||||
|
|
||||||
|
// Check local video element
|
||||||
|
const localVideo = await waitForElementInIframe(page, '.OV_stream.local');
|
||||||
|
await expect(localVideo).toBeVisible();
|
||||||
|
|
||||||
|
// Second participant (moderator) joins
|
||||||
|
const context = await browser.newContext();
|
||||||
|
const moderatorPage = await context.newPage();
|
||||||
|
await prepareForJoiningRoom(moderatorPage, testAppUrl, testRoomPrefix);
|
||||||
|
|
||||||
|
await joinRoomAs('moderator', moderatorPage);
|
||||||
|
await waitForElementInIframe(moderatorPage, 'ov-participant-name-form');
|
||||||
|
|
||||||
|
// Set moderator name
|
||||||
|
await interactWithElementInIframe(moderatorPage, '#participant-name-input', {
|
||||||
|
action: 'fill',
|
||||||
|
value: 'Moderator'
|
||||||
|
});
|
||||||
|
await interactWithElementInIframe(moderatorPage, '#participant-name-submit', { action: 'click' });
|
||||||
|
|
||||||
|
// Verify session established and remote video appears
|
||||||
|
await waitForElementInIframe(moderatorPage, 'ov-session');
|
||||||
|
await waitForElementInIframe(moderatorPage, '.OV_stream.remote');
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await leaveRoom(page);
|
||||||
|
await leaveRoom(moderatorPage);
|
||||||
|
await context.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// SCREEN SHARING TESTS
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
test.describe('Screen Sharing', () => {
|
||||||
|
test('should be able to share and stop screen sharing', async ({ page }) => {
|
||||||
|
await joinRoomAs('publisher', page);
|
||||||
|
await waitForElementInIframe(page, '#toolbar');
|
||||||
|
|
||||||
|
// Initial state: only camera video
|
||||||
|
let videoCount = await page.frameLocator('iframe').locator('video').count();
|
||||||
|
expect(videoCount).toBe(1);
|
||||||
|
|
||||||
|
// Enable screen share
|
||||||
|
await startScreenSharing(page);
|
||||||
|
videoCount = await page.frameLocator('iframe').locator('video').count();
|
||||||
|
expect(videoCount).toBe(2);
|
||||||
|
|
||||||
|
// Disable screen share
|
||||||
|
await stopScreenSharing(page);
|
||||||
|
videoCount = await page.frameLocator('iframe').locator('video').count();
|
||||||
|
expect(videoCount).toBe(1);
|
||||||
|
|
||||||
|
// Test toggle functionality
|
||||||
|
await startScreenSharing(page);
|
||||||
|
videoCount = await page.frameLocator('iframe').locator('video').count();
|
||||||
|
expect(videoCount).toBe(2);
|
||||||
|
|
||||||
|
await stopScreenSharing(page);
|
||||||
|
videoCount = await page.frameLocator('iframe').locator('video').count();
|
||||||
|
expect(videoCount).toBe(1);
|
||||||
|
|
||||||
|
await leaveRoom(page);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// UI PANELS TESTS
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
test.describe('UI Panels and Components', () => {
|
||||||
|
test('should show and interact with chat panel', async ({ page }) => {
|
||||||
|
await joinRoomAs('publisher', page);
|
||||||
|
await waitForElementInIframe(page, 'ov-session');
|
||||||
|
|
||||||
|
// Open chat panel
|
||||||
|
await waitForElementInIframe(page, '#chat-panel-btn');
|
||||||
|
await interactWithElementInIframe(page, '#chat-panel-btn', { action: 'click' });
|
||||||
|
|
||||||
|
// Send a message
|
||||||
|
await waitForElementInIframe(page, '#chat-input');
|
||||||
|
await interactWithElementInIframe(page, '#chat-input', {
|
||||||
|
action: 'fill',
|
||||||
|
value: 'Hello world'
|
||||||
|
});
|
||||||
|
await interactWithElementInIframe(page, '#send-btn', { action: 'click' });
|
||||||
|
|
||||||
|
// Verify message appears
|
||||||
|
const chatMessage = await waitForElementInIframe(page, '.chat-message');
|
||||||
|
await expect(chatMessage).toBeVisible();
|
||||||
|
|
||||||
|
await leaveRoom(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show activities panel', async ({ page }) => {
|
||||||
|
await joinRoomAs('publisher', page);
|
||||||
|
await waitForElementInIframe(page, 'ov-session');
|
||||||
|
|
||||||
|
// Open activities panel
|
||||||
|
await waitForElementInIframe(page, '#activities-panel-btn');
|
||||||
|
await interactWithElementInIframe(page, '#activities-panel-btn', { action: 'click' });
|
||||||
|
|
||||||
|
// Verify panel is visible
|
||||||
|
const activitiesPanel = await waitForElementInIframe(page, 'ov-activities-panel');
|
||||||
|
await expect(activitiesPanel).toBeVisible();
|
||||||
|
|
||||||
|
await leaveRoom(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show participants panel', async ({ page }) => {
|
||||||
|
await joinRoomAs('publisher', page);
|
||||||
|
await waitForElementInIframe(page, 'ov-session');
|
||||||
|
|
||||||
|
// Open participants panel
|
||||||
|
await waitForElementInIframe(page, '#participants-panel-btn');
|
||||||
|
await interactWithElementInIframe(page, '#participants-panel-btn', { action: 'click' });
|
||||||
|
|
||||||
|
// Verify panel is visible
|
||||||
|
const participantsPanel = await waitForElementInIframe(page, 'ov-participants-panel');
|
||||||
|
await expect(participantsPanel).toBeVisible();
|
||||||
|
|
||||||
|
await leaveRoom(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show settings panel', async ({ page }) => {
|
||||||
|
await joinRoomAs('publisher', page);
|
||||||
|
await waitForElementInIframe(page, 'ov-session');
|
||||||
|
|
||||||
|
// Open more options menu
|
||||||
|
await interactWithElementInIframe(page, '#more-options-btn', { action: 'click' });
|
||||||
|
await page.waitForTimeout(500); // Wait for menu animation
|
||||||
|
|
||||||
|
// Open settings panel
|
||||||
|
await interactWithElementInIframe(page, '#toolbar-settings-btn', { action: 'click' });
|
||||||
|
|
||||||
|
// Verify panel is visible
|
||||||
|
const settingsPanel = await waitForElementInIframe(page, 'ov-settings-panel');
|
||||||
|
await expect(settingsPanel).toBeVisible();
|
||||||
|
|
||||||
|
await leaveRoom(page);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// ADVANCED FEATURES TESTS
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
test.describe('Advanced Features', () => {
|
||||||
|
test('should apply virtual background and detect visual changes', async ({ page }) => {
|
||||||
|
await joinRoomAs('publisher', page);
|
||||||
|
await waitForElementInIframe(page, 'ov-session');
|
||||||
|
|
||||||
|
// Wait for video element to be ready
|
||||||
|
await waitForElementInIframe(page, '.OV_video-element');
|
||||||
|
|
||||||
|
// Capture baseline screenshot
|
||||||
|
await saveScreenshot(page, 'before.png', '.OV_video-element');
|
||||||
|
|
||||||
|
// Apply virtual background
|
||||||
|
await applyVirtualBackground(page, '2');
|
||||||
|
await page.waitForTimeout(1000); // Allow background processing time
|
||||||
|
|
||||||
|
// Capture post-change screenshot
|
||||||
|
await saveScreenshot(page, 'after.png', '.OV_video-element');
|
||||||
|
|
||||||
|
// Compare images to detect changes
|
||||||
|
const img1 = PNG.sync.read(fs.readFileSync('before.png'));
|
||||||
|
const img2 = PNG.sync.read(fs.readFileSync('after.png'));
|
||||||
|
const { width, height } = img1;
|
||||||
|
const diff = new PNG({ width, height });
|
||||||
|
|
||||||
|
const numDiffPixels = pixelmatch(img1.data, img2.data, diff.data, width, height, {
|
||||||
|
threshold: 0.4
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save diff for debugging purposes
|
||||||
|
fs.writeFileSync('diff.png', PNG.sync.write(diff));
|
||||||
|
|
||||||
|
// Verify significant visual change occurred
|
||||||
|
expect(numDiffPixels).toBeGreaterThan(500);
|
||||||
|
|
||||||
|
// Cleanup test artifacts
|
||||||
|
fs.unlinkSync('before.png');
|
||||||
|
fs.unlinkSync('after.png');
|
||||||
|
fs.unlinkSync('diff.png');
|
||||||
|
|
||||||
|
await leaveRoom(page);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { Page, Locator, FrameLocator } from '@playwright/test';
|
import { Page, Locator, FrameLocator } from '@playwright/test';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a FrameLocator for an iframe inside a Shadow DOM
|
* Gets a FrameLocator for an iframe inside a Shadow DOM
|
||||||
@ -54,3 +55,86 @@ export async function waitForElementInIframe(
|
|||||||
|
|
||||||
return elementLocator;
|
return elementLocator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Interacti with an element inside an iframe within Shadow DOM
|
||||||
|
export async function interactWithElementInIframe(
|
||||||
|
page: Page,
|
||||||
|
elementSelector: string,
|
||||||
|
options: {
|
||||||
|
action: 'click' | 'fill' | 'type';
|
||||||
|
value?: string; // Only needed for 'fill' or 'type' actions
|
||||||
|
timeout?: number;
|
||||||
|
} = {
|
||||||
|
action: 'click',
|
||||||
|
value: '',
|
||||||
|
timeout: 30000
|
||||||
|
}
|
||||||
|
): Promise<void> {
|
||||||
|
const { action, value = '', timeout = 30000 } = options;
|
||||||
|
const element = await waitForElementInIframe(page, elementSelector);
|
||||||
|
// Perform the specified action
|
||||||
|
switch (action) {
|
||||||
|
case 'click':
|
||||||
|
await element.click();
|
||||||
|
break;
|
||||||
|
case 'fill':
|
||||||
|
await element.fill(value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported action: ${action}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const prepareForJoiningRoom = async (page: Page, url: string, roomPrefix: string) => {
|
||||||
|
await page.goto(url);
|
||||||
|
await page.waitForSelector('.rooms-container');
|
||||||
|
await page.waitForSelector(`#${roomPrefix}`);
|
||||||
|
await page.click('.dropdown-button');
|
||||||
|
await page.waitForSelector('#join-as-moderator');
|
||||||
|
await page.waitForSelector('#join-as-publisher');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const joinRoomAs = async (role: 'moderator' | 'publisher', page: Page) => {
|
||||||
|
await page.click('#join-as-' + role);
|
||||||
|
const component = page.locator('openvidu-meet');
|
||||||
|
await expect(component).toBeVisible();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const leaveRoom = async (page: Page) => {
|
||||||
|
const button = await waitForElementInIframe(page, '#leave-btn');
|
||||||
|
await button.click();
|
||||||
|
await page.waitForSelector('.event-LEFT');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const startScreenSharing = async (page: Page) => {
|
||||||
|
await interactWithElementInIframe(page, '#screenshare-btn', { action: 'click' });
|
||||||
|
await waitForElementInIframe(page, '#local-element-screen_share', { state: 'visible' });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stopScreenSharing = async (page: Page) => {
|
||||||
|
await interactWithElementInIframe(page, '#screenshare-btn', { action: 'click' });
|
||||||
|
await page.waitForTimeout(200); // Wait for screen menu
|
||||||
|
await interactWithElementInIframe(page, '#disable-screen-button', { action: 'click' });
|
||||||
|
await page.waitForTimeout(500); // Wait for screen to stop sharing
|
||||||
|
};
|
||||||
|
|
||||||
|
export const applyVirtualBackground = async (page: Page, backgroundId: string) => {
|
||||||
|
await interactWithElementInIframe(page, '#more-options-btn', { action: 'click' });
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
await interactWithElementInIframe(page, '#virtual-bg-btn', { action: 'click' });
|
||||||
|
await waitForElementInIframe(page, 'ov-background-effects-panel', { state: 'visible' });
|
||||||
|
await interactWithElementInIframe(page, `#effect-${backgroundId}`, { action: 'click' });
|
||||||
|
await interactWithElementInIframe(page, '.panel-close-button', { action: 'click' });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeVirtualBackground = async (page: Page) => {
|
||||||
|
await interactWithElementInIframe(page, '#more-options-btn', { action: 'click' });
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
await interactWithElementInIframe(page, '#virtual-bg-btn', { action: 'click' });
|
||||||
|
await interactWithElementInIframe(page, '#no_effect-btn', { action: 'click' });
|
||||||
|
await page.waitForTimeout(500); // Wait for background to be removed
|
||||||
|
};
|
||||||
|
export const saveScreenshot = async (page: Page, filename: string, selector: string) => {
|
||||||
|
const element = await waitForElementInIframe(page, selector);
|
||||||
|
await element.screenshot({ path: filename });
|
||||||
|
};
|
||||||
|
|||||||
4856
testapp/package-lock.json
generated
4856
testapp/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -21,11 +21,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rooms-list">
|
<div class="rooms-list">
|
||||||
|
<div class="mb-3 text-center">
|
||||||
|
<form action="/delete-all-rooms" method="post">
|
||||||
|
<button type="submit" class="btn btn-danger btn-sm" id="delete-all-rooms">
|
||||||
|
Delete All Rooms
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
{{#rooms.length}}
|
{{#rooms.length}}
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
{{#rooms}}
|
{{#rooms}}
|
||||||
<li
|
<li
|
||||||
id="{{roomIdPrefix}}"
|
id="{{ roomIdPrefix }}"
|
||||||
class="list-group-item d-flex justify-content-between align-items-center"
|
class="list-group-item d-flex justify-content-between align-items-center"
|
||||||
>
|
>
|
||||||
<span>{{ roomId }}</span>
|
<span>{{ roomId }}</span>
|
||||||
@ -50,13 +57,13 @@
|
|||||||
name="participantRole"
|
name="participantRole"
|
||||||
value="moderator"
|
value="moderator"
|
||||||
/>
|
/>
|
||||||
<input
|
<input type="hidden" name="roomId" value="{{ roomId }}" />
|
||||||
type="hidden"
|
|
||||||
name="roomId"
|
|
||||||
value="{{ roomId }}"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<button type="submit" id="join-as-moderator" class="dropdown-item">
|
<button
|
||||||
|
type="submit"
|
||||||
|
id="join-as-moderator"
|
||||||
|
class="dropdown-item"
|
||||||
|
>
|
||||||
Moderator
|
Moderator
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@ -73,13 +80,13 @@
|
|||||||
name="participantRole"
|
name="participantRole"
|
||||||
value="publisher"
|
value="publisher"
|
||||||
/>
|
/>
|
||||||
<input
|
<input type="hidden" name="roomId" value="{{ roomId }}" />
|
||||||
type="hidden"
|
|
||||||
name="roomId"
|
|
||||||
value="{{ roomId }}"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<button type="submit" id="join-as-publisher" class="dropdown-item">
|
<button
|
||||||
|
type="submit"
|
||||||
|
id="join-as-publisher"
|
||||||
|
class="dropdown-item"
|
||||||
|
>
|
||||||
Publisher
|
Publisher
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@ -97,13 +104,13 @@
|
|||||||
name="participantRole"
|
name="participantRole"
|
||||||
value="publisher"
|
value="publisher"
|
||||||
/>
|
/>
|
||||||
<input
|
<input type="hidden" name="roomId" value="{{ roomId }}" />
|
||||||
type="hidden"
|
|
||||||
name="roomId"
|
|
||||||
value="{{ roomId }}"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<button type="submit" id="join-as-publisher" class="dropdown-item">
|
<button
|
||||||
|
type="submit"
|
||||||
|
id="join-as-publisher"
|
||||||
|
class="dropdown-item"
|
||||||
|
>
|
||||||
View Recordings
|
View Recordings
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { getAllRooms, createRoom, deleteRoom } from '../services/roomService';
|
import {
|
||||||
|
getAllRooms,
|
||||||
|
createRoom,
|
||||||
|
deleteRoom,
|
||||||
|
deleteAllRooms,
|
||||||
|
} from '../services/roomService';
|
||||||
|
|
||||||
export const getHome = async (req: Request, res: Response) => {
|
export const getHome = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
@ -33,7 +38,7 @@ export const postCreateRoom = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const postDeleteRoom = async (req: Request, res: Response) => {
|
export const deleteRoomCtrl = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { roomId } = req.body;
|
const { roomId } = req.body;
|
||||||
await deleteRoom(roomId);
|
await deleteRoom(roomId);
|
||||||
@ -44,3 +49,21 @@ export const postDeleteRoom = async (req: Request, res: Response) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const deleteAllRoomsCtrl = async (_req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const allRooms = await getAllRooms();
|
||||||
|
if (allRooms.rooms.length === 0) {
|
||||||
|
console.log('No rooms to delete');
|
||||||
|
res.render('index', { rooms: [] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const roomIds = allRooms.rooms.map((room) => room.roomId);
|
||||||
|
await deleteAllRooms(roomIds);
|
||||||
|
res.render('index', { rooms: [] });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting all rooms:', error);
|
||||||
|
res.status(500).send('Internal Server Error ' + JSON.stringify(error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@ -5,7 +5,8 @@ import path from 'path';
|
|||||||
import {
|
import {
|
||||||
getHome,
|
getHome,
|
||||||
postCreateRoom,
|
postCreateRoom,
|
||||||
postDeleteRoom,
|
deleteRoomCtrl,
|
||||||
|
deleteAllRoomsCtrl,
|
||||||
} from './controllers/homeController';
|
} from './controllers/homeController';
|
||||||
import { handleWebhook, joinRoom } from './controllers/roomController';
|
import { handleWebhook, joinRoom } from './controllers/roomController';
|
||||||
import { configService } from './services/configService';
|
import { configService } from './services/configService';
|
||||||
@ -31,7 +32,8 @@ app.use(express.json());
|
|||||||
app.get('/', getHome);
|
app.get('/', getHome);
|
||||||
app.get('/room', joinRoom);
|
app.get('/room', joinRoom);
|
||||||
app.post('/room', postCreateRoom);
|
app.post('/room', postCreateRoom);
|
||||||
app.post('/room/delete', postDeleteRoom);
|
app.post('/room/delete', deleteRoomCtrl);
|
||||||
|
app.post('/delete-all-rooms', deleteAllRoomsCtrl);
|
||||||
app.post('/join-room', joinRoom);
|
app.post('/join-room', joinRoom);
|
||||||
app.post('/webhook', (req, res) => {
|
app.post('/webhook', (req, res) => {
|
||||||
handleWebhook(req, res, io);
|
handleWebhook(req, res, io);
|
||||||
|
|||||||
@ -3,9 +3,12 @@ import { configService } from './configService';
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { MeetRoom, MeetRoomOptions } from '../../../typings/src/room';
|
import { MeetRoom, MeetRoomOptions } from '../../../typings/src/room';
|
||||||
|
|
||||||
export async function getAllRooms(): Promise<{pagination:any, rooms: MeetRoom[]}> {
|
export async function getAllRooms(): Promise<{
|
||||||
|
pagination: any;
|
||||||
|
rooms: MeetRoom[];
|
||||||
|
}> {
|
||||||
const url = `${configService.meetApiUrl}/rooms`;
|
const url = `${configService.meetApiUrl}/rooms`;
|
||||||
return get<{pagination:any, rooms: MeetRoom[]}>(url, {
|
return get<{ pagination: any; rooms: MeetRoom[] }>(url, {
|
||||||
headers: { 'x-api-key': configService.apiKey },
|
headers: { 'x-api-key': configService.apiKey },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -13,7 +16,8 @@ export async function getAllRooms(): Promise<{pagination:any, rooms: MeetRoom[]}
|
|||||||
export async function createRoom(roomData: MeetRoomOptions): Promise<MeetRoom> {
|
export async function createRoom(roomData: MeetRoomOptions): Promise<MeetRoom> {
|
||||||
const url = `${configService.meetApiUrl}/rooms`;
|
const url = `${configService.meetApiUrl}/rooms`;
|
||||||
// Default to 1 hour if autoDeletionDate is not provided
|
// Default to 1 hour if autoDeletionDate is not provided
|
||||||
if(!roomData.autoDeletionDate) roomData.autoDeletionDate = new Date(Date.now() + 60 * 61 * 1000).getTime();
|
if (!roomData.autoDeletionDate)
|
||||||
|
roomData.autoDeletionDate = new Date(Date.now() + 60 * 61 * 1000).getTime();
|
||||||
console.log('Creating room with options:', roomData);
|
console.log('Creating room with options:', roomData);
|
||||||
return post<MeetRoom>(url, {
|
return post<MeetRoom>(url, {
|
||||||
headers: { 'x-api-key': configService.apiKey },
|
headers: { 'x-api-key': configService.apiKey },
|
||||||
@ -27,3 +31,11 @@ export async function deleteRoom(roomId: string): Promise<void> {
|
|||||||
headers: { 'x-api-key': configService.apiKey },
|
headers: { 'x-api-key': configService.apiKey },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteAllRooms(roomIds: string[]): Promise<void> {
|
||||||
|
const url = `${configService.meetApiUrl}/rooms?roomIds=${roomIds.join(',')}`;
|
||||||
|
|
||||||
|
await del<void>(url, {
|
||||||
|
headers: { 'x-api-key': configService.apiKey },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -1,42 +1,63 @@
|
|||||||
export interface RequestOptions {
|
export interface RequestOptions {
|
||||||
headers?: Record<string, string>;
|
headers?: Record<string, string>;
|
||||||
queryParams?: Record<string, string>;
|
queryParams?: Record<string, string>;
|
||||||
body?: any;
|
body?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildUrl(url: string, queryParams?: Record<string, string>): Promise<string> {
|
async function buildUrl(
|
||||||
if (!queryParams) return url;
|
url: string,
|
||||||
const params = new URLSearchParams(queryParams as Record<string, string>).toString();
|
queryParams?: Record<string, string>
|
||||||
return `${url}?${params}`;
|
): Promise<string> {
|
||||||
|
if (!queryParams) return url;
|
||||||
|
const params = new URLSearchParams(
|
||||||
|
queryParams as Record<string, string>
|
||||||
|
).toString();
|
||||||
|
return `${url}?${params}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function request<T>(method: string, url: string, options: RequestOptions = {}): Promise<T> {
|
async function request<T>(
|
||||||
const fullUrl = await buildUrl(url, options.queryParams);
|
method: string,
|
||||||
const fetchOptions: RequestInit = {
|
url: string,
|
||||||
method,
|
options: RequestOptions = {}
|
||||||
headers: { 'Content-Type': 'application/json', ...(options.headers || {}) },
|
): Promise<T> {
|
||||||
body: options.body ? JSON.stringify(options.body) : undefined
|
const fullUrl = await buildUrl(url, options.queryParams);
|
||||||
};
|
const fetchOptions: RequestInit = {
|
||||||
const response = await fetch(fullUrl, fetchOptions);
|
method,
|
||||||
if (!response.ok) {
|
headers: { 'Content-Type': 'application/json', ...(options.headers || {}) },
|
||||||
const text = await response.text();
|
body: options.body ? JSON.stringify(options.body) : undefined,
|
||||||
throw new Error(`HTTP ${response.status}: ${text}`);
|
};
|
||||||
}
|
const response = await fetch(fullUrl, fetchOptions);
|
||||||
return response.json() as Promise<T>;
|
if (!response.ok) {
|
||||||
|
const text = await response.text();
|
||||||
|
throw new Error(`HTTP ${response.status}: ${text}`);
|
||||||
|
}
|
||||||
|
return response.json() as Promise<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get<T>(url: string, options?: Omit<RequestOptions, 'body'>): Promise<T> {
|
export function get<T>(
|
||||||
return request<T>('GET', url, options || {});
|
url: string,
|
||||||
|
options?: Omit<RequestOptions, 'body'>
|
||||||
|
): Promise<T> {
|
||||||
|
return request<T>('GET', url, options || {});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function post<T>(url: string, options?: Omit<RequestOptions, 'body'> & { body: any }): Promise<T> {
|
export function post<T>(
|
||||||
return request<T>('POST', url, options as RequestOptions);
|
url: string,
|
||||||
|
options?: Omit<RequestOptions, 'body'> & { body: any }
|
||||||
|
): Promise<T> {
|
||||||
|
return request<T>('POST', url, options as RequestOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function put<T>(url: string, options?: Omit<RequestOptions, 'body'> & { body: any }): Promise<T> {
|
export function put<T>(
|
||||||
return request<T>('PUT', url, options as RequestOptions);
|
url: string,
|
||||||
|
options?: Omit<RequestOptions, 'body'> & { body: any }
|
||||||
|
): Promise<T> {
|
||||||
|
return request<T>('PUT', url, options as RequestOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function del<T>(url: string, options?: Omit<RequestOptions, 'body'>): Promise<T> {
|
export function del<T>(
|
||||||
return request<T>('DELETE', url, options || {});
|
url: string,
|
||||||
}
|
options?: Omit<RequestOptions, 'body'>
|
||||||
|
): Promise<T> {
|
||||||
|
return request<T>('DELETE', url, options || {});
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user