This page explains how to create a Chat app using
Pub/Sub
. This type of
architecture for a Chat app is useful
if your organization has a firewall, which can prevent Chat from
sending messages to your Chat app, or if the Chat app
uses the
Google Workspace Events API
. However, this
architecture has the following limitations due to the fact that these
Chat apps can only send and receive
asynchronous messages
:
- Can't use
dialogs
in messages. Instead, use a
card message
.
- Can't update individual cards with a synchronous response. Instead, update
the entire message by calling the
patch
method.
The following diagram shows the architecture of a
Chat app built with Pub/Sub:
![Architecture of a Chat app implemented with Pub/Sub.](/static/workspace/chat/images/design-patterns/secured-firewall.svg)
In the preceding diagram, a user interacting with a Pub/Sub
Chat app has the following flow of information:
A user sends a message in Chat to a
Chat app, either in a direct message or in a
Chat space, or an event happens in a Chat space
for which the Chat app has an active
subscription
.
Chat sends the message to a Pub/Sub topic.
An application server, that is either a cloud or on-premises system that
contains the Chat app logic, subscribes to the
Pub/Sub topic in order to receive the message through the firewall.
Optionally, the Chat app can call the
Chat API to asynchronously post messages or perform other
operations.
Prerequisites
Set up the environment
Before using Google APIs, you need to turn them on in a Google Cloud project.
You can turn on one or more APIs in a single Google Cloud project.
Set up Pub/Sub
Create a Pub/Sub topic
that the Chat API can send messages to. We recommend that you use
a single topic per Chat app.
Grant Chat permission to publish
to the topic by assigning the
Pub/Sub Publisher
role to the following
service account:
chat-api-push@system.gserviceaccount.com
Create a service account
for the Chat app to authorize with Pub/Sub and
Chat and save the private key file to your working directory.
Create a pull subscription
to the topic.
Assign the
Pub/Sub Subscriber Role
on the subscription
for the service account that you previously created.
Write the script
Java
In a CLI,
provide service account credentials
:
export GOOGLE_APPLICATION_CREDENTIALS=
SERVICE_ACCOUNT_FILE_PATH
In your working directory, create a file named
pom.xml
.
In the
pom.xml
file, paste the following code:
<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>
In your working directory, create the directory structure
src/main/java
.
In the
src/main/java
directory, create a file named
Main.java
.
In
Main.java
, paste the following code:
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();
}
}
Replace the following:
PROJECT_ID
: the Google Cloud project ID.
SUBSCRIPTION_ID
: the subscription ID
for the Pub/Sub subscription that you previously created.
Publish the app to Chat
In the Google Cloud console, go to
Menu
menu
>
APIs & Services
>
Enabled APIs & Services
>
Google Chat API
>
Configuration
.
Go to Configuration
Configure the Chat app for Pub/Sub:
- In
App name
, enter
Quickstart App
.
- In
Avatar URL
, enter
https://developers.google.com/chat/images/quickstart-app-avatar.png
.
- In
Description
, enter
Quickstart app
.
- Under
Functionality
, select
Receive 1:1 messages
and
Join spaces and group conversations
.
- Under
Connection settings
, select
Cloud Pub/Sub
and paste the
name of the Pub/Sub topic that you previously created.
- Under
Visibility
, select
Make this Google Chat app available to specific people and groups
in your domain and enter your email address.
- Under
Logs
, select
Log errors to Logging
.
Click
Save
.
The app is ready to receive and respond to messages on Chat.
Run the script
In a CLI, switch into your working directory and run the script:
Java
mvn compile exec:java -Dexec.mainClass=Main
When you run the code, the application starts listening to messages published
to the Pub/Sub topic.
Test your Chat app
To test your Chat app, open a direct message space with
the Chat app and send a message:
Open Google Chat using the Google Workspace account that you
provided when you added yourself as a trusted tester.
Go to Google Chat
- Click
add
New chat
.
- In the
Add 1 or more people
field, type the name of your
Chat app.
Select your Chat app from the results. A direct
message opens.
- In the new direct message with the app, type
Hello
and press
enter
.
To add trusted testers and learn more about testing interactive features, see
Test interactive features for
Google Chat apps
.
Troubleshoot
When a Google Chat app or
card
returns an error, the
Chat interface surfaces a message saying "Something went wrong."
or "Unable to process your request." Sometimes the Chat UI
doesn't display any error message, but the Chat app or
card produces an unexpected result; for example, a card message might not
appear.
Although an error message might not display in the Chat UI,
descriptive error messages and log data are available to help you fix errors
when error logging for Chat apps is turned on. For help viewing,
debugging, and fixing errors, see
Troubleshoot and fix Google Chat errors
.
Clean up
To avoid incurring charges to your Google Cloud account for the
resources used in this tutorial, we recommend that you delete the
Cloud project.
-
In the Google Cloud console, go to the
Manage resources
page. Click
Menu
menu
>
IAM & Admin
>
Manage Resources
.
Go to Resource Manager
-
In the project list, select the project you want to delete and then click
Delete
delete
.
-
In the dialog, type the project ID and then click
Shut down
to delete
the project.