Criar um app do Google Chat protegido por um firewall com o Pub/Sub

Nesta página, explicamos como criar um app do Chat usando o Pub/Sub. Esse tipo de arquitetura para um app do Chat é útil se sua organização tiver um firewall, que possa impedir o Chat de enviar mensagens para o app do Chat, ou se o app do Chat usar a API Google Workspace Events. No entanto, essa arquitetura tem as limitações abaixo, porque esses apps do Chat só podem enviar e receber mensagens assíncronas:

  • Não é possível usar caixas de diálogo em mensagens. Em vez disso, use uma mensagem de cartão.
  • Não é possível atualizar cards individuais com uma resposta síncrona. Em vez disso, atualize toda a mensagem chamando o método patch.

O diagrama a seguir mostra a arquitetura de um app do Chat criado com o Pub/Sub:

Arquitetura de um app de chat implementado com o Pub/Sub.

No diagrama anterior, um usuário que interage com um app do Chat do Pub/Sub tem o seguinte fluxo de informações:

  1. Um usuário envia uma mensagem no Chat para um app do Chat, seja em uma mensagem direta ou em um espaço do Chat, ou um evento acontece em um espaço do Chat em que o app do Chat tem uma assinatura ativa.

  2. O Chat envia a mensagem para um tópico do Pub/Sub.

  3. Um servidor de aplicativos, que é um sistema na nuvem ou no local que contém a lógica do app do Chat, assina o tópico do Pub/Sub para receber a mensagem pelo firewall.

  4. Opcionalmente, o app do Chat pode chamar a API Chat para postar mensagens de forma assíncrona ou realizar outras operações.

Pré-requisitos

Java

Configurar o ambiente

Antes de usar as APIs do Google, é preciso ativá-las em um projeto do Google Cloud. É possível ativar uma ou mais APIs em um único projeto do Google Cloud.
  • No console do Google Cloud, ative as APIs Google Chat e Pub/Sub.

    Ativar as APIs

Configurar o Pub/Sub

  1. Crie um tópico do Pub/Sub que receberá mensagens da API do Chat. Recomendamos usar um único tópico por app do Chat.

  2. Conceda ao Chat permissão para publicar no tópico atribuindo o papel Editor do Pub/Sub à seguinte conta de serviço:

    chat-api-push@system.gserviceaccount.com
    
  3. Crie uma conta de serviço para o app do Chat autorizar com o Pub/Sub e o Chat e salve o arquivo da chave privada no diretório de trabalho.

  4. Crie uma assinatura de pull para o tópico.

  5. Atribua o papel de assinante do Pub/Sub na assinatura da conta de serviço que você criou anteriormente.

Escrever o script

Java

  1. Em uma CLI, forneça as credenciais da conta de serviço:

    export GOOGLE_APPLICATION_CREDENTIALS=SERVICE_ACCOUNT_FILE_PATH
    
  2. No diretório de trabalho, crie um arquivo chamado pom.xml.

  3. No arquivo pom.xml, cole o seguinte código:

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.google.chat.pubsub</groupId>
    <artifactId>java-pubsub-app</artifactId>
    <version>0.1.0</version>
    
    <name>java-pubsub-app</name>
    
    <properties>
      <maven.compiler.target>11</maven.compiler.target>
      <maven.compiler.source>11</maven.compiler.source>
    </properties>
    
    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>com.google.cloud</groupId>
          <artifactId>libraries-bom</artifactId>
          <version>26.26.0</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
      </dependencies>
    </dependencyManagement>
    
    <dependencies>
      <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.9.1</version>
      </dependency>
      <dependency>
        <groupId>com.google.api-client</groupId>
        <artifactId>google-api-client</artifactId>
        <version>1.32.1</version>
      </dependency>
      <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>google-cloud-pubsub</artifactId>
      </dependency>
      <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.14.2</version>
      </dependency>
    </dependencies>
    
    <build>
      <pluginManagement>
        <plugins>
          <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.0</version>
          </plugin>
        </plugins>
      </pluginManagement>
    </build>
    </project>
    
  4. No diretório de trabalho, crie a estrutura de diretórios src/main/java.

  5. No diretório src/main/java, crie um arquivo chamado Main.java.

  6. Em Main.java, cole o seguinte código:

    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.node.JsonNodeFactory;
    import com.fasterxml.jackson.databind.node.ObjectNode;
    import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
    import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
    import com.google.api.client.http.ByteArrayContent;
    import com.google.api.client.http.GenericUrl;
    import com.google.api.client.http.HttpContent;
    import com.google.api.client.http.HttpRequest;
    import com.google.api.client.http.HttpRequestFactory;
    import com.google.api.client.http.HttpTransport;
    import com.google.cloud.pubsub.v1.AckReplyConsumer;
    import com.google.cloud.pubsub.v1.MessageReceiver;
    import com.google.cloud.pubsub.v1.Subscriber;
    import com.google.pubsub.v1.PubsubMessage;
    import com.google.pubsub.v1.ProjectSubscriptionName;
    import java.io.FileInputStream;
    import java.util.Collections;
    
    public class Main {
    
      public static final String CREDENTIALS_PATH_ENV_PROPERTY = "GOOGLE_APPLICATION_CREDENTIALS";
    
      // Google Cloud Project ID
      public static final String PROJECT_ID = PROJECT_ID;
    
      // Cloud Pub/Sub Subscription ID
      public static final String SUBSCRIPTION_ID = SUBSCRIPTION_ID
    
      public static void main(String[] args) throws Exception {
        ProjectSubscriptionName subscriptionName =
            ProjectSubscriptionName.of(PROJECT_ID, SUBSCRIPTION_ID);
    
        // Instantiate app, which implements an asynchronous message receiver.
        EchoApp echoApp = new EchoApp();
    
        // Create a subscriber for <var>SUBSCRIPTION_ID</var> bound to the message receiver
        final Subscriber subscriber =
            Subscriber.newBuilder(subscriptionName, echoApp).build();
        System.out.println("Starting subscriber...");
        subscriber.startAsync();
    
        // Wait for termination
        subscriber.awaitTerminated();
      }
    }
    
    / **
    * A demo app which implements {@link MessageReceiver} to receive messages. It simply echoes the
    * incoming messages.
    */
    class EchoApp implements MessageReceiver {
    
      // Path to the private key JSON file of the service account to be used for posting response
      // messages to Google Chat.
      // In this demo, we are using the same service account for authorizing with Cloud Pub/Sub to
      // receive messages and authorizing with Google Chat to post messages. If you are using
      // different service accounts, please set the path to the private key JSON file of the service
      // account used to post messages to Google Chat here.
      private static final String SERVICE_ACCOUNT_KEY_PATH =
          System.getenv(Main.CREDENTIALS_PATH_ENV_PROPERTY);
    
      // Developer code for Google Chat API scope.
      private static final String GOOGLE_CHAT_API_SCOPE = "https://www.googleapis.com/auth/chat.bot";
    
      // Response URL Template with placeholders for space id.
      private static final String RESPONSE_URL_TEMPLATE =
          "https://chat.googleapis.com/v1/__SPACE_ID__/messages";
    
      // Response echo message template.
      private static final String RESPONSE_TEMPLATE = "You said: `__MESSAGE__`";
    
      private static final String ADDED_RESPONSE = "Thank you for adding me!";
    
      GoogleCredential credential;
      HttpTransport httpTransport;
      HttpRequestFactory requestFactory;
    
      EchoApp() throws Exception {
        credential =
            GoogleCredential.fromStream(new FileInputStream(SERVICE_ACCOUNT_KEY_PATH))
                .createScoped(Collections.singleton(GOOGLE_CHAT_API_SCOPE));
        httpTransport = GoogleNetHttpTransport.newTrustedTransport();
        requestFactory = httpTransport.createRequestFactory(credential);
      }
    
      // Called when a message is received by the subscriber.
      @Override
      public void receiveMessage(PubsubMessage pubsubMessage, AckReplyConsumer consumer) {
        System.out.println("Id : " + pubsubMessage.getMessageId());
        // handle incoming message, then ack/nack the received message
        try {
          ObjectMapper mapper = new ObjectMapper();
          JsonNode dataJson = mapper.readTree(pubsubMessage.getData().toStringUtf8());
          System.out.println("Data : " + dataJson.toString());
          handle(dataJson);
          consumer.ack();
        } catch (Exception e) {
          System.out.println(e);
          consumer.nack();
        }
      }
    
      public void handle(JsonNode eventJson) throws Exception {
        JsonNodeFactory jsonNodeFactory = new JsonNodeFactory(false);
        ObjectNode responseNode = jsonNodeFactory.objectNode();
    
        // Construct the response depending on the event received.
    
        String eventType = eventJson.get("type").asText();
        switch (eventType) {
          case "ADDED_TO_SPACE":
            responseNode.put("text", ADDED_RESPONSE);
            // An app can also be added to a space by @mentioning it in a message. In that case, we fall
            // through to the MESSAGE case and let the app respond. If the app was added using the
            // invite flow, we just post a thank you message in the space.
            if(!eventJson.has("message")) {
              break;
            }
          case "MESSAGE":
            responseNode.put("text",
                RESPONSE_TEMPLATE.replaceFirst(
                    "__MESSAGE__", eventJson.get("message").get("text").asText()));
            // In case of message, post the response in the same thread.
            ObjectNode threadNode = jsonNodeFactory.objectNode();
            threadNode.put("name", eventJson.get("message").get("thread").get("name").asText());
            responseNode.put("thread", threadNode);
            break;
          case "REMOVED_FROM_SPACE":
          default:
            // Do nothing
            return;
        }
    
        // Post the response to Google Chat.
    
        String URI =
            RESPONSE_URL_TEMPLATE.replaceFirst(
                "__SPACE_ID__", eventJson.get("space").get("name").asText());
        GenericUrl url = new GenericUrl(URI);
    
        HttpContent content =
            new ByteArrayContent("application/json", responseNode.toString().getBytes("UTF-8"));
        HttpRequest request = requestFactory.buildPostRequest(url, content);
        com.google.api.client.http.HttpResponse response = request.execute();
      }
    }
    

    Substitua:

    • PROJECT_ID: o ID do projeto do Google Cloud.
    • SUBSCRIPTION_ID: o ID da assinatura do Pub/Sub que você criou anteriormente.

Publique o app no Chat

  1. No console do Google Cloud, acesse Menu > APIs e serviços > APIs e serviços ativados > API Google Chat > Configuração.

    Acessar as configurações

  2. Configure o app do Chat para o Pub/Sub:

    1. Em Nome do app, insira Quickstart App.
    2. No URL do avatar, digite https://developers.google.com/chat/images/quickstart-app-avatar.png.
    3. Em Descrição, digite Quickstart app.
    4. Em Funcionalidade, selecione Receber mensagens 1:1 e Participar de espaços e conversas em grupo.
    5. Em Configurações de conexão, selecione Cloud Pub/Sub e cole o nome do tópico do Pub/Sub criado anteriormente.
    6. Em Visibilidade, selecione Disponibilizar este app do Google Chat para pessoas e grupos específicos no seu domínio e digite seu endereço de e-mail.
    7. Em Registros, selecione Registrar erros no Logging.
  3. Clique em Salvar.

O app está pronto para receber e responder a mensagens no Chat.

Executar o script

Em uma CLI, alterne para seu diretório de trabalho e execute o script:

Java

mvn compile exec:java -Dexec.mainClass=Main

Quando você executa o código, o aplicativo começa a detectar as mensagens publicadas no tópico do Pub/Sub.

Testar o app do Chat

Para testar o app do Chat, envie uma mensagem direta a ele:

  1. Abra o Google Chat.
  2. Para enviar uma mensagem direta ao app, clique em Iniciar um chat e, na janela exibida, clique em Encontrar apps.
  3. Na caixa de diálogo Localizar apps, procure por "App de início rápido".
  4. Para abrir uma mensagem direta com o app, encontre o App de início rápido e clique em Adicionar > Chat.
  5. Na mensagem direta, digite Hello e pressione enter. O app Chat ecoa a mensagem para você.

Para adicionar trusted testers e saber mais sobre como testar recursos interativos, consulte Testar recursos interativos de apps do Google Chat.

Resolver problemas

Quando um app ou card do Google Chat retorna um erro, a interface do Chat mostra a mensagem "Ocorreu um erro" ou "Não foi possível processar sua solicitação". Às vezes, a interface do Chat não mostra nenhuma mensagem de erro, mas o app ou card do Chat produz um resultado inesperado. Por exemplo, uma mensagem de card pode não aparecer.

Embora uma mensagem de erro não apareça na interface do Chat, mensagens de erro descritivas e dados de registro estão disponíveis para ajudar a corrigir erros quando o registro de erros dos apps do Chat está ativado. Se precisar de ajuda para visualizar, depurar e corrigir erros, consulte Resolver problemas e corrigir erros do Google Chat.