Conferencia de Video Jitsi

Ese error parece indicar que Jitsi en tu navegador no tiene acceso al almacenamiento local. ¿Estás utilizando un navegador con configuraciones de seguridad restrictivas que podrían bloquear que los iframes usen el almacenamiento local?

2 Me gusta

algo “divertido” en mi consola, también algunos errores, como:

Política de Seguridad de Contenido: La configuración de la página bloqueó la carga de un recurso en eval (“script-src”). Origen: (function injected(eventName, injectedIntoContentWindow)
{

aunque funciona :thinking:

puedes probar cualquiera de estas instancias:
https://framatalk.org/accueil/fr/info/

1 me gusta

Sí, tengo Chrome configurado para no permitir cookies de terceros. Desactivar esa configuración soluciona el problema. Sería útil tener alguna forma de ser alertado sobre esa causa, pero supongo que es un problema lo suficientemente poco frecuente.

¿Existe alguna forma en Jitsi de abrir una videollamada en una nueva pestaña o en una ventana emergente separada? Me pregunto si la gente hará clic fuera durante la llamada para seguir navegando en el foro mientras habla o para agregar una respuesta al tema.

2 Me gusta

Quizás no, ya que complicaría las csp_extensions en about.json

1 me gusta

No, la versión actual del componente del tema ya agrega la ruta de la API de Jitsi a las fuentes de CSP. Esto se realiza mediante esta línea de about.json, como señaló @Benjamin_D:

1 me gusta

@pmusaraj ¿Es posible configurar ese paso más reciente dentro del propio Discourse?

:smiley: Sí, lo estoy aprendiendo sobre la marcha…
Todavía me pregunto por qué esta función: injected(eventName, injectedIntoContentWindow), etc., no pasa la CSP.

consola

Política de Seguridad de Contenido: La configuración de la página bloqueó la carga de un recurso en eval (“script-src”). Origen: (function injected(eventName, injectedIntoContentWindow)
{
let checkRequest;

/*

  • Envoltorio del contexto del marco
  • En algunos casos extremos, Chrome no ejecuta scripts de contenido dentro de marcos.
  • Algunos sitios web han comenzado a abusar de este hecho para acceder a APIs no envueltas
  • a través del contentWindow de un marco (#4586, 5207). Por lo tanto, hasta que Chrome
  • ejecute scripts de contenido de manera consistente para todos los marcos, debemos
  • asegurarnos de (re)inyectar nuestros envoltorios cuando se acceda al 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”
);

// Aparentemente, en HTMLObjectElement.prototype.contentWindow no existe
// en versiones antiguas de Chrome como la 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);

}

/*

// Si hemos sido inyectados en un marco a través de contentWindow, entonces
// podemos simplemente tomar la copia de checkRequest que nos dejó el documento padre.
// De lo contrario, necesitamos configurarla ahora, junto con las funciones de manejo de eventos.
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}}));
};

}

// Solo debe llamarse antes del código de la página, no está endurecido.
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 tiene la opción (media.peerconnection.enabled) para deshabilitar WebRTC,
// en cuyo caso RealRTCPeerConnection es 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;
};

// Sería mucho más fácil usar el método .getConfiguration para obtener
// la configuración normalizada y segura desde la instancia de RTCPeerConnection.
// Desafortunadamente, no está implementado en Chrome unstable 59.
// Véase 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 no itera sobre pseudo Arrays de 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)
    {
      // Llamar a .close() lanza un error si ya está cerrado.
      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 (probado con 59) ya ha implementado
// setConfiguration, por lo que también necesitamos envolverlo si existe.
// 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);

    // Llama al método real primero, para que valide la configuración por nosotros.
    // Además, podemos hacerlo ya que checkRequest es asíncrono de todos modos.
    realSetConfiguration(this, configuration);
    checkConfiguration(this, configuration);
  };
}

let WrappedRTCPeerConnection = function(...args)
{
  if (!(this instanceof WrappedRTCPeerConnection))
    return RealRTCPeerConnection();

  let configuration = protectConfiguration(args[0]);

  // Dado que el constructor antiguo de webkitRTCPeerConnection toma un segundo
  // argumento opcional, debemos asegurarnos de pasarlo. Necesario
  // para versiones antiguas de Chrome como la 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

¿Activaste tu política de seguridad de contenido? Además, por favor envíame un mensaje privado sobre cómo agregaste ese elegante menú desplegable de console. Gracias.

¡Entendido!
Es un bloqueador de anuncios, no tiene nada que ver con Jitsi… :sweat_smile:

1 me gusta

@sunjam no hay ningún nuevo paso; el componente añade automáticamente a la lista blanca el script que necesita.

1 me gusta

Actualmente no, pero sería una buena mejora. Lo trabajaré cuando tenga oportunidad.

4 Me gusta

¡Muy genial! Un par de comentarios rápidos:

En dispositivos móviles, después de hacer clic en el botón para iniciar el evento, el enlace “Continuar a la aplicación” a veces no es clicable. Funciona al verlo directamente en Safari, pero no en Chrome, ni tampoco en la aplicación iOS de Discourse Hub…

También apoyo esta idea; una forma rápida de generar automáticamente un ID de sala podría ser muy útil. Creo que, especialmente para quienes no están muy familiarizados con Jitsi, no queda del todo claro si deben ingresar una sala existente en ese campo o si están generando una sobre la marcha simplemente usando cualquier cadena aleatoria arbitraria.

2 Me gusta

¡Buen hallazgo! Esto se debe a que el enlace “Continuar a la aplicación” realiza una solicitud de URL personalizada que comienza con org.jitsi.meet://. He añadido ese esquema de consulta personalizado a la lista de permitidos en DiscourseHub, lo que debería solucionar el problema en la próxima versión de la aplicación. (Por desgracia, no creo que exista una solución para esto en Chrome.)

He añadido una opción de generación aleatoria; los usuarios solo necesitan dejar el campo de ID vacío y se generará un ID. También he añadido un texto explicativo:

Ten en cuenta que el ID no contendrá palabras, sino una combinación aleatoria de letras y números.

4 Me gusta

¿Existe alguna posibilidad de priorizar esto? Hemos estado probando Jitsi para nuestros chats internos.

El problema es el siguiente:

  1. Iniciamos un hilo como Evento usando el plugin del Calendario e incluimos una llamada de Jitsi programada para esa hora. A veces hacemos esto después de una encuesta similar a Doodle en el mismo hilo. El enlace de Jitsi se coloca en el mensaje original (OP).
  2. La llamada se inicia, todo bien.
  3. Alguien usa esa misma pestaña para buscar algo o responder en el hilo (por ejemplo, para tomar actas sobre la marcha) y se desconecta de la llamada.

No debería ser muy difícil hacer que el enlace se abra en una pestaña en blanco en lugar de estar incrustado en un iframe, ¿verdad? ¡Aunque no tengo las habilidades para hacerlo!

2 Me gusta

Bueno, @nathank, este componente es un poco excesivo si solo quieres un enlace a una sala de Jitsi. Puedes agregar https://meet.jit.si/ROOMID a un enlace y eso debería funcionar sin necesidad de un componente de tema sofisticado.

Sin embargo, sí añadí esta opción al componente; ahora puedes elegir si quieres el iframe para móvil, escritorio o ambos:

Por defecto, la videoconferencia se carga en un iframe. Si se desmarca, se abrirá la llamada de video en la ventana completa. Con BigBlueButton video conference esto era especialmente deseable en móviles, así que he hecho lo mismo aquí.

Ten en cuenta también que el enlace a la sala completa de Jitsi no se abre en una pestaña nueva. Si deseas que lo haga, utiliza un ancla con target="_blank".

8 Me gusta

Para mí está bien, pero menos para mis usuarios menos técnicos. Gracias por la mejora del iframe, es muy útil. No estoy seguro de que tenga sentido que el iframe sea el predeterminado en móviles; ¿considerarías cambiarlo?

He descubierto en otro lugar que puedes omitir la página de “descargar la aplicación Jitsi” que aparece en móviles añadiendo #config.disableDeepLinking=true al ID de la sala:
https://meet.jit.si/TuNombreDeReunionAqui#config.disableDeepLinking=true

4 Me gusta

Acabo de intentar agregar este componente de tema a nuestra instancia de Discourse 2.7.0.beta3, pero, sin importar lo que haga, no veo un icono adicional en la barra del editor para enlazar al ID de la conferencia.

La opción “mostrar en el menú desplegable de opciones” en la configuración del componente del tema está marcada, por lo que debería ser visible. ¿Alguna idea de dónde podría buscar el error?

Esto es lo que veo en un sitio de Discourse nuevo con esa opción marcada:

1 me gusta

Sí, el problema ya está resuelto, quizás fue un problema de caché.
De todos modos, gracias por tu respuesta.

1 me gusta

Me encanta cómo funciona este componente temático. Todavía estoy lidiando con problemas de audio de Jitsi, de lo contrario, todo habría funcionado sin problemas. Me preguntaba si alguien está usando Jitsi actualmente en su foro y no tiene quejas al respecto. Mis problemas de audio se deben quizás a WebRTC ---- ¿alguien más tiene una experiencia similar?

1 me gusta