Não consigo fazer o embedding funcionar

Olá, estou tentando incorporar comentários do Discourse no meu site, seguindo o guia de incorporação, e esbarrei em um muro :frowning:

Sintomas

Tentei no Firefox e no Chrome. Em ambos, ele carrega o iframe do Discourse com a mensagem “Carregando discussão…”, mas fica travado ali, com erros recorrentes de JavaScript no console do desenvolvedor.

No Firefox, recebo um erro sobre o cabeçalho X-Frame-Options:

Cabeçalho X-Frame-Options inválido encontrado ao carregar “https://discourse.29th.local/embed/comments?embed_url=https%3A%2F%2Fpersonnel.29th.local%2F%23enlistments%2F11927”: “ALLOWALL” não é uma diretiva válida.

Em seguida, um erro DOMException em embed-application.js:7:

Uncaught DOMException: Uma string inválida ou ilegal foi especificada

Esses dois erros se repetem a cada 30 segundos ou mais. Não há requisições falhas na aba de rede.

No Chrome, não recebo o erro de X-Frame-Options. Após alguns segundos, recebo um erro sobre a origem de destino não corresponder à origem da janela receptora:

Falha ao executar 'postMessage' em 'DOMWindow': A origem de destino fornecida ('https://discourse.29th.local') não corresponde à origem da janela receptora ('https://personnel.29th.local').

Vi muitos tópicos no meta sobre esse erro e tentei todas as etapas de solução de problemas, sem sucesso.

Meu ambiente

Siga o guia de configuração do Discourse para Mac com uma pequena exceção: em vez de instalar o postgres, o redis e o mailcatcher globalmente no meu laptop, tenho eles rodando em contêineres Docker, com as portas expostas publicamente. O Discourse não tem ideia de que estão rodando em contêineres Docker em vez de diretamente no sistema. O Rails/Discourse está instalado globalmente e não está rodando em um contêiner Docker.

Totalmente separado disso, meu aplicativo web personalizado está rodando em um stack do Docker Compose. Parte desse stack inclui um servidor nginx que roteia personnel.29th.local para o contêiner upstream apropriado e discourse.29th.local para host.docker.internal:3000 (esse é o nome de host mágico que os contêineres Docker podem usar para acessar o localhost do host).

(Como menciono abaixo, removi a camada do nginx da equação e terminei com o mesmo erro)

Um possível problema aqui é que meu aplicativo web é uma aplicação de página única (SPA) em JavaScript. A página onde os comentários do Discourse estão sendo incorporados é https://personnel.29th.local/#enlistments/1234 e não há renderização do lado do servidor. Se isso fosse um problema, eu esperaria um erro com o rastreador (crawler), momento em que eu aceitaria que o Discourse simplesmente fizesse um link para meu aplicativo, em vez de tentar rastrear. Mas os erros que ele mostra não parecem estar relacionados a falhas de rastreamento.

Solução de problemas

Defini o host incorporável em Admin > Personalizar > Incorporação como personnel.29th.local. No início, o código de exemplo de incorporação mostrava http://localhost:3000/ para o discourseUrl, então abri o rails console e executei:

SiteSetting.force_hostname = "discourse.29th.local"
SiteSetting.port = 443

E ativei a opção “forçar https” no painel administrativo. Isso corrigiu a URL no código de exemplo de incorporação.

Também adicionei https://personnel.29th.local como um domínio CORS na seção origens cors das configurações.

Agora estou iniciando o Discourse com o seguinte comando:

DISCOURSE_DEV_HOSTS=discourse.29th.local,host.docker.internal DISCOURSE_ENABLE_CORS=true bundle exec rails server

Também tentei desativar a Política de Segurança de Conteúdo (CSP) no painel de configurações.

Olhei em https://discourse.29th.local/logs/, mas não vi erros e nada sobre o Sidekiq.

Sobre o Sidekiq, tenho uma mensagem no painel administrativo sobre atualizações:

Uma verificação de atualizações não foi realizada. Certifique-se de que o Sidekiq esteja rodando.

Então executei Sidekiq.redis { |r| puts r.flushall } no console do Rails e obtive OK, reiniciei o servidor Rails e não houve mudança na mensagem nem no problema geral. Explorarei o cache do Redis e não vi nada relacionado a essa página.

Também tentei simplificar as coisas removendo a camada do nginx da equação: revertendo SiteSetting.force_hostname e SiteSetting.port para nil, desativando o forçar https, acessando meu aplicativo web e o Discourse via localhost, e adicionando meu aplicativo web aos hosts incorporáveis e nomes de host CORS do Discourse (http://localhost:8080), mas obtive o mesmo erro, apenas com hosts diferentes:

Falha ao executar 'postMessage' em 'DOMWindow': A origem de destino fornecida ('http://localhost:3000') não corresponde à origem da janela receptora ('http://localhost:8080').

Estou rodando a versão 2.6.0.beta6 ( 60bc38e6a8 ), que obtive clonando a branch master conforme o guia de configuração do Discourse para Mac algumas semanas atrás e executando git pull origin master hoje.

Também removi o diretório tmp e reiniciei o servidor.

Também saí para caminhar, gritei em um travesseiro e chorei debaixo da minha mesa.

Espero que isso cubra todas as bases. Espero que alguém possa ajudar!

Sinto muito em saber que você está tendo tantas dificuldades na configuração.

O Discourse não é inteligente o suficiente para rastrear um SPA, então isso me parece o mais suspeito. Você pode tentar reproduzir o problema com um site que contenha conteúdo estático?

É impossível para nós darmos suporte a cada instalação personalizada, então recomendo que você tente simplificar ainda mais sua pilha tecnológica até que funcione, e depois adicione componentes a partir daí.

Obrigado pela sua resposta, e sem problemas, sei que valerá a pena!

Simplifiquei as coisas para usar um site renderizado no servidor (rails) e omitir totalmente a camada do nginx. Tenho meu aplicativo rodando na porta 3001 e o Discourse rodando na porta 3000.

Meu código de incorporação é renderizado assim:

<script type="text/javascript">
      DiscourseEmbed = { discourseUrl: 'http://localhost:3000/',
                         discourseEmbedUrl: 'http://localhost:3001/enlistments/1' };
    
      (function() {
        var d = document.createElement('script'); d.type = 'text/javascript'; d.async = true;
        d.src = DiscourseEmbed.discourseUrl + 'javascripts/embed.js';
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(d);
      })();
</script>

Adicionei localhost:3001 aos hosts incorporáveis em Admin > Personalizar > Incorporação e http://localhost:3001 aos nomes de host em Admin > Configurações > cors.

O erro é o mesmo, mas com os nomes de host atualizados:

Falha ao executar 'postMessage' no 'DOMWindow': A origem de destino fornecida ('http://localhost:3000') não corresponde à origem da janela de destino ('http://localhost:3001').

A pilha está tão simples quanto possível agora :thinking: Acredito que isso signifique que seja algum tipo de problema de configuração? Alguma ideia?

Algumas descobertas adicionais de depuração:

Criei manualmente um tópico e substituí discourseEmbedUrl: 'http://localhost:3001/enlistments/<%= @enlistment.id %>' por topicId: 14 no meu snippet de JavaScript, e os comentários foram carregados. Isso sugere que não se trata de um problema de CORS ou X-Frame, mas sim (a) algo relacionado ao scraping e (b) possivelmente um problema na forma como os erros são tratados durante a incorporação.

Para investigar o problema de scraping, visitei uma nova página que eu nunca havia tentado acessar antes (e, portanto, nenhum scraping deveria ter sido tentado nela). Fiquei observando o console do Rails do meu aplicativo e carreguei a página. Vi /enlistments/6 nos logs uma vez. Esperei até que a mensagem de erro fosse lançada no console do JavaScript, momento em que o Discourse deveria ter tentado fazer o scraping, mas o console do Rails do meu aplicativo não mostrou nenhum outro log de tentativa de acesso.

Não houve erros no endpoint /logs do Discourse, nem nada que eu pudesse identificar no log do Rails do Discourse.

Pensei que talvez o Discourse não conseguisse acessar meu site, então fiz login no console do Rails do meu aplicativo Discourse e executei:

± |master U:3 ?:2 ✗| → rails c
Loading development environment (Rails 6.0.3.3)
[1] pry(main)> require "net/http"
=> false
[2] pry(main)> url = URI.parse("http://localhost:3001/enlistments/6")
=> #<URI::HTTP http://localhost:3001/enlistments/6>
[3] pry(main)> req = Net::HTTP.new(url.host, url.port)
=> #<Net::HTTP localhost:3001 open=false>
[4] pry(main)> res = req.request_head(url.path)
4=> #<Net::HTTPOK 200 OK readbody=true>
[5] pry(main)>

Enquanto fazia isso, vi o log de acesso no servidor Rails do meu aplicativo. Isso confirmou que o Discourse consegue acessar meu aplicativo.

Agora estou pensando que deve ser um problema com o Sidekiq ou com o agendamento de jobs? :man_shrugging: Não tenho certeza de como depurar isso, porém. Nunca usei o Sidekiq antes.

Dêi outra olhada nos dados do Redis (usando o TablePlus, uma interface gráfica de banco de dados que funciona com Redis) e estou vendo cerca de 3 linhas com uma key de valores como default:logster-env-96404aef1da0c422fc32e3bb82d85fbc e um value com valores como

[
  {
    "hostname": "myhostname",
    "process_id": 7188,
    "application_version": "60bc38e6a8914a10341a32ff9909e69faa65ffef",
    "params": {
      "embed_url": "http: //localhost:3001/enlistments/11927"
    },
    "HTTP_HOST": "localhost:3000",
    "REQUEST_URI": "/embed/comments?embed_url=http%3A%2F%2Flocalhost%3A3001%2Fenlistments%2F11927",
    "REQUEST_METHOD": "GET",
    "HTTP_USER_AGENT": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.60 Safari/537.36",
    "HTTP_ACCEPT": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
    "HTTP_REFERER": "http://localhost:3000/embed/comments?embed_url=http%3A%2F%2Flocalhost%3A3001%2Fenlistments%2F11927",
    "time": 1606253787041
  }
]

o type é LIST e o ttl é -1. Acredito que isso signifique que o job está sendo iniciado?

Explorei um pouco /sidekiq, mas não vejo menção a esse job nem a nenhum tipo de fila chamada RetrieveTopic :frowning:

Definitivamente estou estreitando as possibilidades, mas poderia usar uma mão se algo vier à mente!

Ei @eviltrout, tem alguma ideia para mais troubleshooting, agora que simplifiquei a configuração para o básico?

É quase certo que seja um problema de configuração. Talvez o seu Discourse em localhost:3000 ache que tem um hostname diferente. Você pode verificar no console usando:

Discourse.base_url

Outra coisa para verificar são os logs do sidekiq em /sidekiq.

@eviltrout Adicionei algumas linhas de depuração na biblioteca TopicRetriever e confirmei que invalid_url? é false (sugerindo que é válida). Discourse.base_url está de fato definido como http://localhost:3000. Acredito que o job RetrieveTopic esteja falhando silenciosamente em algum lugar, e estou tentando localizar onde. Não há logs de erro em /logs, e /sidekiq não tem nenhuma referência à recuperação de tópicos ou logs de qualquer tipo.

Desculpe, não tenho mais ideias neste momento. Sei que o código está funcionando atualmente em produção, então deve ser algo relacionado ao ambiente, a um plugin ou a uma configuração.

Olá, obrigado pela investigação. Acredito que tenho exatamente o mesmo problema (o embed funciona para tópicos existentes, mas falha na criação do tópico). Isso funciona no meu ambiente de produção, mas não na minha máquina de desenvolvimento.

Minha configuração é um stack Docker, e garanti que tudo esteja visível tanto para o Discourse quanto para o Sidekiq. Neste ponto, estou começando a achar que, quando o Discourse tenta analisar uma URL (também falha quando o onebox tenta buscar uma prévia de um link em uma postagem), ele de alguma forma depende de um serviço externo que não consegue enxergar instâncias locais… Seria isso possível?

@wilson29thid, você encontrou algo do seu lado desde então?

Encontrei o mesmo problema na minha máquina de produção, não na minha máquina de desenvolvimento. Algo me faz pensar que não importa exatamente qual delas você está usando.

Não, receio que nunca consegui resolver isso e acabei usando a API do Discourse para criar um tópico manualmente :pensive_face:

Eu também posso fazer isso. A boa notícia é que você pode controlar a quantidade de threads criadas antes que as pessoas comecem a comentar seus artigos…