snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
This commit is contained in:
13
static/404.html
Normal file
13
static/404.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<!--#include virtual="/base.html" -->
|
||||
<link rel="stylesheet" href="css/all.css"/>
|
||||
<!--#include virtual="/title.html" -->
|
||||
</head>
|
||||
<body>
|
||||
<div class="error_page">
|
||||
<h2>404 Not Found</h2>
|
||||
<p class="error_page__message">You can create a new conversation <a class="link" onclick="window.location = window.location.protocol + '//' + window.location.hostname">here</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
23
static/close.html
Normal file
23
static/close.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<html>
|
||||
<head>
|
||||
<!--#include virtual="/base.html" -->
|
||||
<link rel="stylesheet" href="css/all.css"/>
|
||||
<!--#include virtual="/title.html" -->
|
||||
<script><!--#include virtual="/interface_config.js" --></script>
|
||||
<script src="static/close.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="redirectPageMessage">
|
||||
<div class="thanks-msg">
|
||||
<p>Thank you for your feedback!</p>
|
||||
</div>
|
||||
<div class="hint-msg">
|
||||
<p>
|
||||
<span id="hintQuestion">Did you know?</span>
|
||||
<span class="hint-msg__holder" id="hintMessage"></span>
|
||||
</p>
|
||||
<div class="happy-software"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
67
static/close.js
Normal file
67
static/close.js
Normal file
@@ -0,0 +1,67 @@
|
||||
/* global interfaceConfig */
|
||||
// list of tips
|
||||
const hints = [
|
||||
'You can pin participants by clicking on their thumbnails.',
|
||||
'You can tell others you have something to say by using the "Raise Hand" '
|
||||
+ 'feature',
|
||||
'You can learn about key shortcuts by pressing Shift+?',
|
||||
'You can learn more about the state of everyone\'s connection by hovering '
|
||||
+ 'on the bars in their thumbnail',
|
||||
'You can hide all thumbnails by using the button in the bottom right corner'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get a random hint message from hint array.
|
||||
*
|
||||
* @return {string} the hint message.
|
||||
*/
|
||||
function getHint() {
|
||||
const l = hints.length - 1;
|
||||
const n = Math.round(Math.random() * l);
|
||||
|
||||
return hints[n];
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts text message
|
||||
* into DOM element
|
||||
*
|
||||
* @param id {string} element identificator
|
||||
* @param msg {string} text message
|
||||
*/
|
||||
function insertTextMsg(id, msg) {
|
||||
const el = document.getElementById(id);
|
||||
|
||||
if (el) {
|
||||
el.textContent = msg;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the hint and thanks messages. Will be executed on load event.
|
||||
*/
|
||||
function onLoad() {
|
||||
// Intentionally use string concatenation as this file does not go through
|
||||
// babel but IE11 is still supported.
|
||||
// eslint-disable-next-line prefer-template
|
||||
const thankYouMessage = 'Thank you for using ' + interfaceConfig.APP_NAME;
|
||||
|
||||
// Works only for close2.html because close.html doesn't have this element.
|
||||
insertTextMsg('thanksMessage', thankYouMessage);
|
||||
|
||||
// If there is a setting show a special message only for the guests
|
||||
if (interfaceConfig.CLOSE_PAGE_GUEST_HINT) {
|
||||
if (window.sessionStorage.getItem('guest') === 'true') {
|
||||
const element = document.getElementById('hintQuestion');
|
||||
|
||||
element.classList.add('hide');
|
||||
insertTextMsg('hintMessage', interfaceConfig.CLOSE_PAGE_GUEST_HINT);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
insertTextMsg('hintMessage', getHint());
|
||||
}
|
||||
|
||||
window.onload = onLoad;
|
||||
23
static/close2.html
Normal file
23
static/close2.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<html>
|
||||
<head>
|
||||
<!--#include virtual="/base.html" -->
|
||||
<link rel="stylesheet" href="css/all.css"/>
|
||||
<!--#include virtual="/title.html" -->
|
||||
<script><!--#include virtual="/interface_config.js" --></script>
|
||||
<script src="static/close.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="redirectPageMessage">
|
||||
<div class="thanks-msg">
|
||||
<p id="thanksMessage"></p>
|
||||
</div>
|
||||
<div class="hint-msg">
|
||||
<p>
|
||||
<span id="hintQuestion">Did you know?</span>
|
||||
<span class="hint-msg__holder" id="hintMessage"></span>
|
||||
</p>
|
||||
<div class="happy-software"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
0
static/close3.html
Normal file
0
static/close3.html
Normal file
0
static/close3.js
Normal file
0
static/close3.js
Normal file
29
static/dialInInfo.html
Normal file
29
static/dialInInfo.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<html xmlns="http://www.w3.org/1999/html">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!--#include virtual="/base.html" -->
|
||||
<link rel="stylesheet" href="css/all.css">
|
||||
<script>
|
||||
window.EXCALIDRAW_ASSET_PATH = 'libs/excalidraw/';
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!JitsiMeetJS.app) {
|
||||
return;
|
||||
}
|
||||
|
||||
JitsiMeetJS.app.renderEntryPoint({
|
||||
Component: JitsiMeetJS.app.entryPoints.DIALIN
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!--#include virtual="/title.html" -->
|
||||
<script><!--#include virtual="/config.js" --></script>
|
||||
<script><!--#include virtual="/interface_config.js" --></script>
|
||||
<script src="libs/lib-jitsi-meet.min.js?v=139"></script>
|
||||
<script src="libs/app.bundle.min.js?v=139"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="react" role="main"></div>
|
||||
</body>
|
||||
</html>
|
||||
43
static/logout.html
Normal file
43
static/logout.html
Normal file
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Logged Out</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.success {
|
||||
color: #27ae60;
|
||||
font-size: 48px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="success">✓</div>
|
||||
<p>You have been logged out successfully.</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Notify parent window that logout is complete
|
||||
if (window.parent) {
|
||||
window.parent.postMessage({
|
||||
type: 'logout-success'
|
||||
}, window.location.origin);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
12
static/msredirect.html
Normal file
12
static/msredirect.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<script>
|
||||
window.opener
|
||||
&& window.opener.postMessage({
|
||||
type: 'ms-login',
|
||||
url: window.location.href
|
||||
}, window.location.origin);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
21
static/oauth.html
Normal file
21
static/oauth.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<html itemscope itemtype="http://schema.org/Product" prefix="og: http://ogp.me/ns#" xmlns="http://www.w3.org/1999/html">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!--#include virtual="/base.html" -->
|
||||
<!--#include virtual="/title.html" -->
|
||||
<script>
|
||||
(function() {
|
||||
var windowName = window.name;
|
||||
window.opener && window.opener.postMessage({
|
||||
type: 'dropbox-login',
|
||||
windowName,
|
||||
url: window.location.href
|
||||
}, window.location.origin);
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
64
static/offline.html
Normal file
64
static/offline.html
Normal file
@@ -0,0 +1,64 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<head>
|
||||
<!--#include virtual="/head.html" -->
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!--#include virtual="/base.html" -->
|
||||
|
||||
<link rel="apple-touch-icon" href="images/apple-touch-icon.png">
|
||||
<!--#include virtual="/title.html" -->
|
||||
</head>
|
||||
<style>
|
||||
body,
|
||||
.content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.content {
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.125rem;
|
||||
margin: auto;
|
||||
max-width: 376px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
line-height: 2rem;
|
||||
margin: 24px auto;
|
||||
}
|
||||
|
||||
.icon {
|
||||
background-color: #FDD13A;
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
height: 56px;
|
||||
padding: 16px;
|
||||
width: 56px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<div class="icon">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M21.8799 19.5904L12.8501 3.49284C12.7614 3.33481 12.6288 3.20474 12.4676 3.11782C11.9982 2.86467 11.4083 3.03257 11.1502 3.49284L2.1202 19.5904C2.04143 19.7309 2.00012 19.8885 2.00012 20.0488C2.00012 20.5741 2.43443 20.9999 2.97017 20.9999H21.0299C21.1934 20.9999 21.3542 20.9594 21.4974 20.8822C21.9668 20.629 22.138 20.0507 21.8799 19.5904ZM4.61072 19.0976L12 5.92477L19.3892 19.0976H4.61072ZM11.0302 16.2445C11.0302 15.7192 11.456 15.2934 11.9813 15.2934H12.0191C12.5444 15.2934 12.9702 15.7192 12.9702 16.2445C12.9702 16.7698 12.5444 17.1956 12.0191 17.1956H11.9813C11.456 17.1956 11.0302 16.7698 11.0302 16.2445ZM12.0002 10.5378C11.4645 10.5378 11.0302 10.9721 11.0302 11.5078V13.3722C11.0302 13.9079 11.4645 14.3422 12.0002 14.3422C12.5359 14.3422 12.9702 13.9079 12.9702 13.3722V11.5078C12.9702 10.9721 12.5359 10.5378 12.0002 10.5378Z"
|
||||
fill="#131519" fill-opacity="0.87" />
|
||||
</svg>
|
||||
</div>
|
||||
<h4>Connection error</h4>
|
||||
Your device may be offline or our servers may be experiencing problems.
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
0
static/planLimit.html
Normal file
0
static/planLimit.html
Normal file
36
static/prejoin.html
Normal file
36
static/prejoin.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<html xmlns="http://www.w3.org/1999/html">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!--#include virtual="/base.html" -->
|
||||
<link rel="stylesheet" href="css/all.css">
|
||||
<script>
|
||||
window.EXCALIDRAW_ASSET_PATH = 'libs/excalidraw/';
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!JitsiMeetJS.app) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
const params = new URLSearchParams(url.search);
|
||||
const styleType = params.get('styleType');
|
||||
|
||||
JitsiMeetJS.app.renderEntryPoint({
|
||||
Component: JitsiMeetJS.app.entryPoints.PREJOIN,
|
||||
props: {
|
||||
styleType
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<!--#include virtual="/title.html" -->
|
||||
<script><!--#include virtual="/config.js" --></script>
|
||||
<script><!--#include virtual="/interface_config.js" --></script>
|
||||
<script src="libs/lib-jitsi-meet.min.js?v=139"></script>
|
||||
<script src="libs/app.bundle.min.js?v=139"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="react" role="main"></div>
|
||||
</body>
|
||||
</html>
|
||||
BIN
static/pwa/icons/icon192.png
Normal file
BIN
static/pwa/icons/icon192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
BIN
static/pwa/icons/icon512.png
Normal file
BIN
static/pwa/icons/icon512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
BIN
static/pwa/icons/iconMask.png
Normal file
BIN
static/pwa/icons/iconMask.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
23
static/recommendedBrowsers.html
Normal file
23
static/recommendedBrowsers.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<html>
|
||||
<head>
|
||||
<!--#include virtual="/base.html" -->
|
||||
<link rel="stylesheet" href="css/all.css"/>
|
||||
<!--#include virtual="/title.html" -->
|
||||
</head>
|
||||
<body>
|
||||
<div class = 'unsupported-desktop-browser'>
|
||||
<h2 class = 'unsupported-desktop-browser__title'>
|
||||
It looks like you're using a browser we don't fully support.
|
||||
</h2>
|
||||
<p class ='unsupported-desktop-browser__description'>
|
||||
We recommend to try with the latest version of
|
||||
<a
|
||||
className = 'unsupported-desktop-browser__link'
|
||||
href = 'https://www.google.com/chrome/' >Chrome</a> or
|
||||
<a
|
||||
class = 'unsupported-desktop-browser__link'
|
||||
href = 'https://www.chromium.org/'>Chromium</a>
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
30
static/secondScreen.html
Normal file
30
static/secondScreen.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html style="height: 100%;">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!--
|
||||
Marks this document as the second-screen shell. The meeting window checks
|
||||
for it before building anything in the popup, because a load event only
|
||||
means that something was served: a 404 body, a redirect landing or a
|
||||
dev-server proxy response would otherwise be treated as this page and
|
||||
silently painted over. Keep in sync with SECOND_SCREEN_MARKER in
|
||||
react/features/multi-screen/functions.web.ts.
|
||||
-->
|
||||
<meta name="jitsi-second-screen" content="1">
|
||||
<title>Jitsi Meet</title>
|
||||
</head>
|
||||
|
||||
<body style="margin: 0; height: 100%; background: #000; overflow: hidden;">
|
||||
<!--
|
||||
Intentionally empty: this page is the shell for a second-screen window
|
||||
(react/features/multi-screen). The meeting window portals the window's
|
||||
content into it. It exists so the popup has a real same-origin URL
|
||||
instead of about:blank: embeds rendered inside the window (e.g. the
|
||||
YouTube shared-video player) require the page to send a valid referrer,
|
||||
which an about:blank window cannot (YouTube fails with error 153).
|
||||
-->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1
static/settingsToolbarAdditionalContent.html
Normal file
1
static/settingsToolbarAdditionalContent.html
Normal file
@@ -0,0 +1 @@
|
||||
<template id="settings-toolbar-additional-content-template"></template>
|
||||
364
static/sso.html
Normal file
364
static/sso.html
Normal file
@@ -0,0 +1,364 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>SSO Authentication</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
.spinner {
|
||||
display: none;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #3498db;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 20px auto;
|
||||
}
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
.error {
|
||||
display: none;
|
||||
color: #e74c3c;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.success {
|
||||
display: none;
|
||||
color: #27ae60;
|
||||
font-size: 48px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div id="spinner" class="spinner"></div>
|
||||
<p id="spinner-msg" style="display: none;">Completing authentication...</p>
|
||||
<div id="success" class="success">✓</div>
|
||||
<p id="message-for-opener" style="display: none;">Authentication successful! You can close this window.</p>
|
||||
<div id="error" class="error"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<!--#include virtual="/config.js" -->
|
||||
(function() {
|
||||
/**
|
||||
* Decodes a JWT token and returns the payload
|
||||
* @param {string} token - The JWT token to decode
|
||||
* @returns {object} The decoded payload
|
||||
*/
|
||||
function decodeJWT(token) {
|
||||
try {
|
||||
const base64Url = token.split('.')[1];
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
|
||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
||||
}).join(''));
|
||||
return JSON.parse(jsonPayload);
|
||||
} catch (error) {
|
||||
throw new Error('Failed to decode JWT: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the nonce in the ID token matches the stored nonce
|
||||
* @param {string} idToken - The ID token to validate
|
||||
* @param {string} expectedNonce - The nonce stored in sessionStorage
|
||||
*/
|
||||
function validateNonce(idToken, expectedNonce) {
|
||||
const payload = decodeJWT(idToken);
|
||||
if (!payload.nonce) {
|
||||
throw new Error('ID token does not contain a nonce claim');
|
||||
}
|
||||
if (payload.nonce !== expectedNonce) {
|
||||
throw new Error('Nonce validation failed: ID token nonce does not match stored nonce');
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAuthorizationCode() {
|
||||
try {
|
||||
// Parse query parameters (Authorization Code Flow uses query params, not hash)
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
// Get state parameter first to check for refresh token
|
||||
const stateParam = urlParams.get('state');
|
||||
if (!stateParam) {
|
||||
throw new Error('No state parameter received');
|
||||
}
|
||||
|
||||
const state = JSON.parse(decodeURIComponent(stateParam));
|
||||
|
||||
// Get SSO configuration from config
|
||||
const { sso } = config;
|
||||
if (!sso || !sso.tokenService || !sso.clientId) {
|
||||
throw new Error('Missing SSO configuration (tokenService or clientId)');
|
||||
}
|
||||
|
||||
// Check if we have a refresh token in state - use it to refresh tokens
|
||||
if (state.refreshToken) {
|
||||
console.log('Using refresh token to get new tokens');
|
||||
|
||||
// Show spinner while refreshing tokens
|
||||
document.getElementById('spinner').style.display = 'block';
|
||||
document.getElementById('spinner-msg').style.display = 'block';
|
||||
|
||||
// Use refresh token to get new tokens
|
||||
const tokenResponse = await fetch(`https://${sso.tokenService}/token`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: state.refreshToken,
|
||||
client_id: sso.clientId
|
||||
})
|
||||
});
|
||||
|
||||
if (!tokenResponse.ok) {
|
||||
const errorData = await tokenResponse.json().catch(() => ({}));
|
||||
throw new Error(errorData.error_description || errorData.error || 'Token refresh failed');
|
||||
}
|
||||
|
||||
const tokens = await tokenResponse.json();
|
||||
const accessToken = tokens.access_token;
|
||||
const idToken = tokens.id_token;
|
||||
const refreshToken = tokens.refresh_token || state.refreshToken; // Use new refresh token if provided, otherwise keep old one
|
||||
|
||||
if (!accessToken || !idToken) {
|
||||
throw new Error('Missing tokens in refresh response');
|
||||
}
|
||||
|
||||
// Validate nonce if available in sessionStorage (optional for refresh flows)
|
||||
const refreshNonce = sessionStorage.getItem('oauth_nonce');
|
||||
if (refreshNonce) {
|
||||
try {
|
||||
validateNonce(idToken, refreshNonce);
|
||||
} catch (nonceError) {
|
||||
console.warn('Nonce validation failed during refresh:', nonceError.message);
|
||||
// Don't fail the refresh flow if nonce validation fails
|
||||
}
|
||||
}
|
||||
|
||||
const message = {
|
||||
type: 'oauth-success',
|
||||
accessToken: accessToken,
|
||||
idToken: idToken,
|
||||
refreshToken: refreshToken
|
||||
};
|
||||
|
||||
// Send message to parent window
|
||||
if (window.opener) {
|
||||
document.getElementById('spinner').style.display = 'none';
|
||||
document.getElementById('spinner-msg').style.display = 'none';
|
||||
document.getElementById('success').style.display = 'block';
|
||||
document.getElementById('message-for-opener').style.display = 'block';
|
||||
|
||||
window.opener.postMessage(message, window.location.origin);
|
||||
} else if (window.parent && window.parent !== window) {
|
||||
console.log('Sending refreshed tokens to parent (iframe)');
|
||||
window.parent.postMessage(message, window.location.origin);
|
||||
} else {
|
||||
// Standalone page - redirect to room
|
||||
const tenant = state.tenant || '';
|
||||
const roomSafe = state.roomSafe || state.room || '';
|
||||
|
||||
if (!roomSafe) {
|
||||
throw new Error('No room specified in state');
|
||||
}
|
||||
|
||||
const protocol = window.location.protocol;
|
||||
const host = window.location.host;
|
||||
const path = tenant ? `/${tenant}/${roomSafe}` : `/${roomSafe}`;
|
||||
const hashParams = new URLSearchParams();
|
||||
|
||||
for (const [key, value] of Object.entries(state)) {
|
||||
if (key.startsWith('config.')) {
|
||||
hashParams.append(key, String(value));
|
||||
}
|
||||
}
|
||||
|
||||
hashParams.append('jwt', `"${accessToken}"`);
|
||||
const redirectUrl = `${protocol}//${host}${path}#${hashParams.toString()}`;
|
||||
window.location.href = redirectUrl;
|
||||
}
|
||||
|
||||
return; // Exit after handling refresh token
|
||||
}
|
||||
|
||||
// No refresh token in state - proceed with authorization code flow
|
||||
// Check for OAuth errors
|
||||
const error = urlParams.get('error');
|
||||
if (error) {
|
||||
const errorDescription = urlParams.get('error_description');
|
||||
console.error('OAuth error:', error, errorDescription);
|
||||
|
||||
if (window.opener) {
|
||||
window.opener.postMessage({
|
||||
type: 'oauth-error',
|
||||
error: errorDescription || error
|
||||
}, window.location.origin);
|
||||
} else if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage({
|
||||
type: 'oauth-error',
|
||||
error: errorDescription || error
|
||||
}, window.location.origin);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Get authorization code
|
||||
const code = urlParams.get('code');
|
||||
if (!code) {
|
||||
throw new Error('No authorization code received');
|
||||
}
|
||||
|
||||
// Retrieve code_verifier and oauth_nonce from sessionStorage (set during login initiation)
|
||||
const codeVerifier = sessionStorage.getItem('code_verifier');
|
||||
if (!codeVerifier) {
|
||||
throw new Error('No PKCE code verifier found in session');
|
||||
}
|
||||
|
||||
const oauthNonce = sessionStorage.getItem('oauth_nonce');
|
||||
if (!oauthNonce) {
|
||||
throw new Error('No OAuth nonce found in session');
|
||||
}
|
||||
|
||||
// Show spinner while exchanging code for tokens
|
||||
document.getElementById('spinner').style.display = 'block';
|
||||
document.getElementById('spinner-msg').style.display = 'block';
|
||||
|
||||
// Exchange authorization code for tokens
|
||||
const tokenResponse = await fetch(`https://${sso.tokenService}/token`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
code: code,
|
||||
redirect_uri: window.location.origin + window.location.pathname,
|
||||
client_id: sso.clientId,
|
||||
code_verifier: codeVerifier
|
||||
})
|
||||
});
|
||||
|
||||
if (!tokenResponse.ok) {
|
||||
const errorData = await tokenResponse.json().catch(() => ({}));
|
||||
throw new Error(errorData.error_description || errorData.error || 'Token exchange failed');
|
||||
}
|
||||
|
||||
const tokens = await tokenResponse.json();
|
||||
const accessToken = tokens.access_token;
|
||||
const idToken = tokens.id_token;
|
||||
const refreshToken = tokens.refresh_token;
|
||||
|
||||
if (!accessToken || !idToken) {
|
||||
throw new Error('Missing tokens in response');
|
||||
}
|
||||
|
||||
// Validate nonce in ID token
|
||||
validateNonce(idToken, oauthNonce);
|
||||
|
||||
const message = {
|
||||
type: 'oauth-success',
|
||||
accessToken: accessToken,
|
||||
idToken: idToken,
|
||||
refreshToken: refreshToken
|
||||
};
|
||||
|
||||
// Send message to parent window
|
||||
if (window.opener) {
|
||||
// Opened via window.open() - popup
|
||||
document.getElementById('spinner').style.display = 'none';
|
||||
document.getElementById('spinner-msg').style.display = 'none';
|
||||
document.getElementById('success').style.display = 'block';
|
||||
document.getElementById('message-for-opener').style.display = 'block';
|
||||
|
||||
window.opener.postMessage(message, window.location.origin);
|
||||
} else if (window.parent && window.parent !== window) {
|
||||
// Loaded in iframe
|
||||
console.log('Sending message to parent (iframe)');
|
||||
window.parent.postMessage(message, window.location.origin);
|
||||
} else {
|
||||
// Standalone page - redirect to room
|
||||
// Extract required values
|
||||
const tenant = state.tenant || '';
|
||||
const roomSafe = state.roomSafe || state.room || '';
|
||||
|
||||
if (!roomSafe) {
|
||||
throw new Error('No room specified in state');
|
||||
}
|
||||
|
||||
// Build the new URL
|
||||
const protocol = window.location.protocol;
|
||||
const host = window.location.host;
|
||||
|
||||
// Construct path: /tenant/roomSafe
|
||||
const path = tenant ? `/${tenant}/${roomSafe}` : `/${roomSafe}`;
|
||||
|
||||
// Build hash parameters - only config.* values and jwt
|
||||
const hashParams = new URLSearchParams();
|
||||
|
||||
// Add only config.* parameters from state
|
||||
for (const [key, value] of Object.entries(state)) {
|
||||
if (key.startsWith('config.')) {
|
||||
hashParams.append(key, String(value));
|
||||
}
|
||||
}
|
||||
|
||||
// Add access_token as jwt with quotes
|
||||
hashParams.append('jwt', `"${accessToken}"`);
|
||||
|
||||
// Build final URL
|
||||
const redirectUrl = `${protocol}//${host}${path}#${hashParams.toString()}`;
|
||||
|
||||
console.log('Redirecting to:', redirectUrl);
|
||||
|
||||
// Redirect
|
||||
window.location.href = redirectUrl;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('SSO Error:', error);
|
||||
const errorMessage = {
|
||||
type: 'oauth-error',
|
||||
error: error.message
|
||||
};
|
||||
if (window.opener) {
|
||||
window.opener.postMessage(errorMessage, window.location.origin);
|
||||
} else if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage(errorMessage, window.location.origin);
|
||||
} else {
|
||||
document.getElementById('error').textContent = `Error: ${error.message}`;
|
||||
document.getElementById('error').style.display = 'block';
|
||||
document.getElementById('spinner').style.display = 'none';
|
||||
document.getElementById('spinner-msg').style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start authentication flow
|
||||
handleAuthorizationCode();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
17
static/webrtcUnsupported.html
Normal file
17
static/webrtcUnsupported.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<html>
|
||||
<head>
|
||||
<!--#include virtual="/base.html" -->
|
||||
<link rel="stylesheet" href="css/all.css"/>
|
||||
<!--#include virtual="/title.html" -->
|
||||
</head>
|
||||
<body>
|
||||
<div class = 'unsupported-desktop-browser'>
|
||||
<h2 class = 'unsupported-desktop-browser__title'>
|
||||
WebRTC is not available in your browser
|
||||
</h2>
|
||||
<p class ='unsupported-desktop-browser__description'>
|
||||
It looks like your browser does not support WebRTC. Please enable it or try another browser.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
1
static/welcomePageAdditionalCard.html
Normal file
1
static/welcomePageAdditionalCard.html
Normal file
@@ -0,0 +1 @@
|
||||
<template id = "welcome-page-additional-card-template"></template>
|
||||
1
static/welcomePageAdditionalContent.html
Normal file
1
static/welcomePageAdditionalContent.html
Normal file
@@ -0,0 +1 @@
|
||||
<template id = "welcome-page-additional-content-template"></template>
|
||||
33
static/whiteboard.html
Normal file
33
static/whiteboard.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<html xmlns="http://www.w3.org/1999/html">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!--#include virtual="/base.html" -->
|
||||
<link rel="stylesheet" href="css/all.css">
|
||||
<script>
|
||||
window.EXCALIDRAW_ASSET_PATH = 'libs/excalidraw/';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
if (!JitsiMeetJS.app) {
|
||||
return;
|
||||
}
|
||||
|
||||
JitsiMeetJS.app.renderEntryPoint({
|
||||
Component: JitsiMeetJS.app.entryPoints.WHITEBOARD
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!--#include virtual="/title.html" -->
|
||||
<script><!--#include virtual="/config.js" --></script>
|
||||
<script><!--#include virtual="/interface_config.js" --></script>
|
||||
<script src="libs/lib-jitsi-meet.min.js?v=139"></script>
|
||||
<script src="libs/app.bundle.min.js?v=139"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="react" role="main"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user