Похоже, эта ошибка указывает на то, что Jitsi в вашем браузере не имеет доступа к локальному хранилищу. Используете ли вы браузер с ограничительными настройками безопасности, которые могут блокировать использование локального хранилища фреймами (iframes)?
что-то «забавное» в моём консоли, также несколько ошибок, например:
Content Security Policy: настройки страницы заблокировали загрузку ресурса по адресу eval («script-src»). Источник: (function injected(eventName, injectedIntoContentWindow)
{
…
хотя всё работает ![]()
вы можете попробовать любые из этих инстансов:
https://framatalk.org/accueil/fr/info/
Да — в Chrome у меня настроено запрещение сторонних файлов cookie. Отключение этой настройки решает проблему. Было бы неплохо иметь возможность получать уведомление о такой причине, но, полагаю, это довольно редкий случай.
Есть ли в Jitsi способ открыть видеозвонок в новой вкладке или отдельном всплывающем окне? Интересно, будут ли пользователи переключаться во время звонка, чтобы продолжать просматривать форум во время разговора или добавить ответ к теме.
Возможно, нет, так как это усложнит csp_extensions в about.json
Нет, текущая версия компонента темы уже добавляет путь к API Jitsi в источники CSP. Это сделано в этой строке файла about.json, как отметил @Benjamin_D:
@pmusaraj Возможно ли настроить этот новый шаг непосредственно в Discourse?
да, я разбираюсь по ходу дела…
Я всё ещё не понимаю, почему эта функция: injected(eventName, injectedIntoContentWindow) и т.д. не проходит проверку CSP
консоль
Политика безопасности контента: настройки страницы заблокировали загрузку ресурса по адресу eval (“script-src”). Источник: (function injected(eventName, injectedIntoContentWindow)
{
let checkRequest;
/*
- Обёртка для контекста фрейма
- В некоторых крайних случаях Chrome не запускает скрипты контента внутри фреймов.
- Сайты начали злоупотреблять этим фактом для доступа к API без обёрток через
- contentWindow фрейма (#4586, 5207). Поэтому до тех пор, пока Chrome не начнёт
- последовательно запускать скрипты контента для всех фреймов, мы должны
- позаботиться о (повторной) внедрении наших обёрток при доступе к contentWindow.
*/
let injectedToString = Function.prototype.toString.bind(injected);
let injectedFrames = new WeakSet();
let injectedFramesAdd = WeakSet.prototype.add.bind(injectedFrames);
let injectedFramesHas = WeakSet.prototype.has.bind(injectedFrames);
function injectIntoContentWindow(contentWindow)
{
if (contentWindow && !injectedFramesHas(contentWindow))
{
injectedFramesAdd(contentWindow);
try
{
contentWindow[eventName] = checkRequest;
contentWindow.eval(
“(” + injectedToString() + “)('” + eventName + “', true);”
);
delete contentWindow[eventName];
}
catch (e) {}
}
}
for (let element of [HTMLFrameElement, HTMLIFrameElement, HTMLObjectElement])
{
let contentDocumentDesc = Object.getOwnPropertyDescriptor(
element.prototype, “contentDocument”
);
let contentWindowDesc = Object.getOwnPropertyDescriptor(
element.prototype, “contentWindow”
);
// Оказывается, в HTMLObjectElement.prototype.contentWindow не существует
// в старых версиях Chrome, таких как 51.
if (!contentWindowDesc)
continue;
let getContentDocument = Function.prototype.call.bind(
contentDocumentDesc.get
);
let getContentWindow = Function.prototype.call.bind(
contentWindowDesc.get
);
contentWindowDesc.get = function()
{
let contentWindow = getContentWindow(this);
injectIntoContentWindow(contentWindow);
return contentWindow;
};
contentDocumentDesc.get = function()
{
injectIntoContentWindow(getContentWindow(this));
return getContentDocument(this);
};
Object.defineProperty(element.prototype, "contentWindow",
contentWindowDesc);
Object.defineProperty(element.prototype, "contentDocument",
contentDocumentDesc);
}
/*
- Обёртка для RTCPeerConnection
- API webRequest в Chrome пока не позволяет блокировать соединения WebRTC.
- См. https://bugs.chromium.org/p/chromium/issues/detail?id=707683
*/
let RealCustomEvent = window.CustomEvent;
// Если мы были внедрены во фрейм через contentWindow, мы можем просто
// взять копию checkRequest, оставленную нам родительским документом. В противном
// случае нам нужно настроить её сейчас, вместе с функциями обработки событий.
if (injectedIntoContentWindow)
checkRequest = window[eventName];
else
{
let addEventListener = document.addEventListener.bind(document);
let dispatchEvent = document.dispatchEvent.bind(document);
let removeEventListener = document.removeEventListener.bind(document);
checkRequest = (url, callback) =>
{
let incomingEventName = eventName + “-” + url;
function listener(event)
{
callback(event.detail);
removeEventListener(incomingEventName, listener);
}
addEventListener(incomingEventName, listener);
dispatchEvent(new RealCustomEvent(eventName, {detail: {url}}));
};
}
// Вызывается только до выполнения кода страницы, не защищённая.
function copyProperties(src, dest, properties)
{
for (let name of properties)
{
if (Object.prototype.hasOwnProperty.call(src, name))
{
Object.defineProperty(dest, name,
Object.getOwnPropertyDescriptor(src, name));
}
}
}
let RealRTCPeerConnection = window.RTCPeerConnection ||
window.webkitRTCPeerConnection;
// В Firefox есть опция (media.peerconnection.enabled) для отключения WebRTC,
// в этом случае RealRTCPeerConnection будет undefined.
if (typeof RealRTCPeerConnection != “undefined”)
{
let closeRTCPeerConnection = Function.prototype.call.bind(
RealRTCPeerConnection.prototype.close
);
let RealArray = Array;
let RealString = String;
let {create: createObject, defineProperty} = Object;
let normalizeUrl = url =>
{
if (typeof url != "undefined")
return RealString(url);
};
let safeCopyArray = (originalArray, transform) =>
{
if (originalArray == null || typeof originalArray != "object")
return originalArray;
let safeArray = RealArray(originalArray.length);
for (let i = 0; i < safeArray.length; i++)
{
defineProperty(safeArray, i, {
configurable: false, enumerable: false, writable: false,
value: transform(originalArray[i])
});
}
defineProperty(safeArray, "length", {
configurable: false, enumerable: false, writable: false,
value: safeArray.length
});
return safeArray;
};
// Было бы гораздо проще использовать метод .getConfiguration для получения
// нормализованной и безопасной конфигурации из экземпляра RTCPeerConnection.
// К сожалению, он не реализован в Chrome unstable 59.
// См. https://www.chromestatus.com/feature/5271355306016768
let protectConfiguration = configuration =>
{
if (configuration == null || typeof configuration != "object")
return configuration;
let iceServers = safeCopyArray(
configuration.iceServers,
iceServer =>
{
let {url, urls} = iceServer;
// RTCPeerConnection не перебирает псевдо-массивы urls.
if (typeof urls != "undefined" && !(urls instanceof RealArray))
urls = [urls];
return createObject(iceServer, {
url: {
configurable: false, enumerable: false, writable: false,
value: normalizeUrl(url)
},
urls: {
configurable: false, enumerable: false, writable: false,
value: safeCopyArray(urls, normalizeUrl)
}
});
}
);
return createObject(configuration, {
iceServers: {
configurable: false, enumerable: false, writable: false,
value: iceServers
}
});
};
let checkUrl = (peerconnection, url) =>
{
checkRequest(url, blocked =>
{
if (blocked)
{
// Вызов .close() выбрасывает ошибку, если соединение уже закрыто.
try
{
closeRTCPeerConnection(peerconnection);
}
catch (e) {}
}
});
};
let checkConfiguration = (peerconnection, configuration) =>
{
if (configuration && configuration.iceServers)
{
for (let i = 0; i < configuration.iceServers.length; i++)
{
let iceServer = configuration.iceServers[i];
if (iceServer)
{
if (iceServer.url)
checkUrl(peerconnection, iceServer.url);
if (iceServer.urls)
{
for (let j = 0; j < iceServer.urls.length; j++)
checkUrl(peerconnection, iceServer.urls[j]);
}
}
}
}
};
// Chrome unstable (тестировался с версией 59) уже реализовал
// setConfiguration, поэтому нам нужно обернуть и его, если он существует.
// https://www.chromestatus.com/feature/5596193748942848
if (RealRTCPeerConnection.prototype.setConfiguration)
{
let realSetConfiguration = Function.prototype.call.bind(
RealRTCPeerConnection.prototype.setConfiguration
);
RealRTCPeerConnection.prototype.setConfiguration = function(configuration)
{
configuration = protectConfiguration(configuration);
// Сначала вызываем реальный метод, чтобы он проверил конфигурацию за нас.
// Кроме того, это имеет смысл, поскольку checkRequest всё равно асинхронен.
realSetConfiguration(this, configuration);
checkConfiguration(this, configuration);
};
}
let WrappedRTCPeerConnection = function(...args)
{
if (!(this instanceof WrappedRTCPeerConnection))
return RealRTCPeerConnection();
let configuration = protectConfiguration(args[0]);
// Поскольку старый конструктор webkitRTCPeerConnection принимает необязательный
// второй аргумент, нам нужно позаботиться о его передаче. Это необходимо
// для старых версий Chrome, таких как 51.
let constraints = undefined;
if (args.length > 1)
constraints = args[1];
let peerconnection = new RealRTCPeerConnection(configuration,
constraints);
checkConfiguration(peerconnection, configuration);
return peerconnection;
};
WrappedRTCPeerConnection.prototype = RealRTCPeerConnection.prototype;
let boundWrappedRTCPeerConnection = WrappedRTCPeerConnection.bind();
copyProperties(RealRTCPeerConnection, boundWrappedRTCPeerConnection,
["generateCertificate", "name", "prototype"]);
RealRTCPeerConnection.prototype.constructor = boundWrappedRTCPeerConnection;
if ("RTCPeerConnection" in window)
window.RTCPeerConnection = boundWrappedRTCPeerConnection;
if ("webkitRTCPeerConnection" in window)
window.webkitRTCPeerConnection = boundWrappedRTCPeerConnection;
}
})(‘abp-request-o6i81ij12x’, true);. bd833f87-4c58-41b0-a0cd-15b978834599:27:22
Вы включили политику безопасности контента?
Также, пожалуйста, напишите мне в личные сообщения, как вы добавили этот стильный выпадающий список console. Gracias.
Понял!
Это блокировщик рекламы, к Jitsi это не имеет никакого отношения… ![]()
@sunjam нового шага нет, компонент автоматически добавляет в белый список необходимый ему скрипт.
В настоящее время нет, но это было бы хорошим улучшением. Я займусь этим, как только появится возможность.
Очень круто! Несколько быстрых замечаний —
На мобильных устройствах после нажатия кнопки для начала события ссылка «Перейти в приложение» иногда неактивна! Она работает при просмотре напрямую в Safari, но не работает в Chrome и не работает в приложении Discourse Hub для iOS…
Поддерживаю эту идею. Быстрый способ автоматически сгенерировать ID комнаты был бы весьма полезен. Я думаю, особенно для тех, кто не очень хорошо знаком с Jitsi, не совсем понятно, нужно ли вводить существующую комнату в это поле или можно генерировать её на лету, используя любую произвольную случайную строку.
Отличное замечание! Это связано с тем, что ссылка «Перейти в приложение» выполняет запрос к пользовательскому URL, начинающемуся с org.jitsi.meet://. Я добавил этот пользовательский схему URL в белый список в DiscourseHub, что должно исправить проблему в следующем обновлении приложения. (К сожалению, я не думаю, что для этого есть исправление в Chrome.)
Я добавил опцию случайной генерации: пользователям достаточно оставить поле ID пустым, и ID будет сгенерирован автоматически. Также я добавил поясняющий текст:
Обратите внимание, что ID не будет содержать слов; это случайная комбинация букв и цифр.
Есть ли шанс, что вы сможете это приоритизировать? Мы уже пробовали использовать Jitsi для наших внутренних чатов.
Проблема в следующем:
- Мы создаём тему как событие, используя плагин Календаря, и включаем ссылку на звонок Jitsi, запланированный на это время. Иногда делаем это после опроса в стиле Doodle в той же теме. Ссылка на Jitsi размещается в первом сообщении.
- Звонок запускается, всё отлично.
- Кто-то использует ту же вкладку для поиска или ответа в теме (например, чтобы вести протокол в реальном времени) — и звонок прерывается.
Должно быть несложно сделать так, чтобы ссылка открывалась в новой вкладке, а не внутри iframe, верно? Хотя у меня нет таких навыков!
Что ж, @nathank, этот компонент немного избыточен, если вам нужен просто ссылка на комнату Jitsi. Вы можете добавить https://meet.jit.si/ROOMID в качестве ссылки, и это должно сработать без использования сложного компонента темы.
Однако я добавил эту опцию в компонент: теперь вы можете выбрать, хотите ли вы отображать iframe для мобильных устройств, для настольных компьютеров или для обоих:
По умолчанию видеоконференция загружается в iframe. Если снять галочку, переход будет осуществляться на видеозвонок в полном окне. С видеоконференцией BigBlueButton это было особенно удобно на мобильных устройствах, поэтому я поступил так же здесь.
Обратите внимание также, что ссылка на комнату Jitsi в полном окне не открывается в новой вкладке. Если вам нужно это, используйте тег якоря с атрибутом target="_blank".
Для меня это отлично, но для моих менее технически подкованных пользователей — не очень! Спасибо за улучшение с iframe, это очень полезно. Не уверен, что имеет смысл делать iframe по умолчанию для мобильных устройств; не могли бы вы это изменить?
Я обнаружил в другом месте, что можно обойти страницу «Скачать приложение Jitsi», которая появляется на мобильных устройствах, добавив #config.disableDeepLinking=true к ID комнаты:
https://meet.jit.si/YourMeetingNameHere#config.disableDeepLinking=true
Только что попробовал добавить этот компонент темы к нашему экземпляру Discourse 2.7.0.beta3, но что бы я ни делал, я не вижу дополнительной иконки в панели композера для ссылки на идентификатор конференции.
В конфигурации компонента темы опция «Показывать в выпадающем списке параметров» отмечена, поэтому она должна быть видна. Есть ли какие-либо идеи, где можно поискать ошибку?
Да, проблема решена, возможно, это было связано с кэшированием.
Тем не менее, спасибо за ваш ответ.
Мне очень нравится, как работает этот компонент темы. В остальном всё должно было работать гладко, но я всё ещё борюсь с проблемами звука в Jitsi. Я хотел бы узнать, использует ли кто-нибудь сейчас Jitsi на своём форуме и не сталкивается ли с жалобами на него. Возможно, мои проблемы со звуком связаны с WebRTC — у кого-нибудь был похожий опыт?



