Using Firebase for Real-time Events on App Engine

This article shows how to connect App Engine to Firebase, and then use Firebase to send real-time updates for an interactive multiplayer tic-tac-toe game. You can get the sample code in the Tic-tac-toe project for Python or Java.

You can use App Engine in conjunction with the Firebase Realtime Database to send immediate updates to browser and mobile clients without a persistent streaming connection to the server or long polling. This capability is useful for apps that update users about new information in real time. Example use cases include collaborative apps, multiplayer games, and chat rooms.

Using Firebase is a better choice than polling in situations where updates can't be predicted or scripted, such as when relaying information between human users, or when events aren't generated systematically.

This article shows how to complete the following tasks on the server:

  • Set up your server to use the Firebase Realtime Database.
  • Create a unique Firebase database reference for each web client that connects to your service.
  • Send real-time updates to web clients by changing data on a particular database reference.
  • Improve security when accessing messages by creating unique tokens for each web client and using Firebase database security rules.
  • Receive messages from web clients over HTTP.
  • Delete game data from the database after the match has ended.

This article also shows how to complete the following tasks in the web browser:

  • Connect to Firebase using a unique token from the server.
  • Dynamically update the interface when the database reference updates.
  • Send update messages to the server so they can be passed on to remote clients.

Getting started with Firebase Realtime Database

The Firebase Realtime Database lets you build rich, collaborative apps by allowing access to the database directly from client-side code. Data is persisted locally, and real-time events continue to fire even while offline, giving the end user a responsive experience. When the device regains a connection, the Realtime Database synchronizes the local data changes with the remote updates that occurred while the client was offline, merging any conflicts automatically.

Creating a Firebase project

  1. Create a Firebase account or log into an existing account.

  2. Click Add project.

  3. Enter a name in the Project name field.

  4. Follow the remaining setup steps and click Create project.

  5. After the wizard provisions your project, click Continue.

  6. In the Overview page of your project, click the Settings gear and then click Project settings.

  7. Click Add Firebase to your web app.

  8. Make a copy of the initialization code snippet, which is required in the Adding the Firebase SDK to your web page section.

Creating a Realtime Database

  1. From the left menu of the Firebase console, select Database in the Develop group.

  2. In the Database page, go to the Realtime Database section and click Create database.

  3. In the Security rules for Realtime Database dialog, select Start in test mode and click Enable.

Working with data in Firebase

All Firebase Realtime Database data is stored as JSON objects. You can think of the database as a cloud-hosted JSON tree. Unlike a SQL database, there are no tables or records. When you add data to the JSON tree, it becomes a node in the existing JSON structure with an associated key. You can provide your own keys, such as user IDs or semantic names.

This structure makes reading data straightforward because clients need only to navigate to a given path, and the entire object is returned in JSON format. No special processing of database fields is required. For more information, see how to Structure your database in the Firebase documentation.

Java backends can use the REST API or the Firebase Admin SDK. If you use the Firebase Admin SDK for Java on App Engine, make sure to use the Java 8 runtime. The SDK version 6.0.0 and higher depend on the multithreading support available in the App Engine Java 8 runtime.

When using the REST API, Firebase handles standard HTTP PUT, PATCH, POST, GET, and DELETE methods to perform database operations. For details about how Firebase uses these operations, see Saving Data and Retrieving Data in the Firebase documentation.

Writing data and sending messages

To perform authenticated requests, apps must retrieve Application Default Credentials from App Engine with the needed authorization scopes, and use it to create an HTTP object. Requests made using that HTTP object automatically include the Authorization headers needed to make requests to Firebase. For more information about Application Default Credentials, see Setting Up Authentication for Server to Server Production Applications.

You can use the PUT HTTP method to write or replace data at a specific Firebase path, as shown in the following example:

Python

def _get_http():
    """Provides an authed http object."""
    http = httplib2.Http()
    # Use application default credentials to make the Firebase calls
    # https://firebase.google.com/docs/reference/rest/database/user-auth
    creds = GoogleCredentials.get_application_default().create_scoped(
        _FIREBASE_SCOPES)
    creds.authorize(http)
    return http
...
def firebase_put(path, value=None):
    """Writes data to Firebase.

    An HTTP PUT writes an entire object at the given database path. Updates to
    fields cannot be performed without overwriting the entire object

    Args:
        path - the url to the Firebase object to write.
        value - a json string.
    """
    response, content = _get_http().request(path, method='PUT', body=value)
    return json.loads(content)

Java

public HttpResponse firebasePut(String path, Object object) throws IOException {
  // Make requests auth'ed using Application Default Credentials
  Credential credential = GoogleCredential.getApplicationDefault().createScoped(FIREBASE_SCOPES);
  HttpRequestFactory requestFactory = httpTransport.createRequestFactory(credential);

  String json = new Gson().toJson(object);
  GenericUrl url = new GenericUrl(path);

  return requestFactory
      .buildPutRequest(url, new ByteArrayContent("application/json", json.getBytes()))
      .execute();
}

To see sample implementations on GitHub for PATCH and POST, click the View on GitHub button in the previous code example.

To read data, make an HTTP GET request for a particular path. The response contains the JSON object at the requested location.

Python

def firebase_get(path):
    """Read the data at the given path.

    An HTTP GET request allows reading of data at a particular path.
    A successful request will be indicated by a 200 OK HTTP status code.
    The response will contain the data being retrieved.

    Args:
        path - the url to the Firebase object to read.
    """
    response, content = _get_http().request(path, method='GET')
    return json.loads(content)

Java

public HttpResponse firebaseGet(String path) throws IOException {
  // Make requests auth'ed using Application Default Credentials
  Credential credential = GoogleCredential.getApplicationDefault().createScoped(FIREBASE_SCOPES);
  HttpRequestFactory requestFactory = httpTransport.createRequestFactory(credential);

  GenericUrl url = new GenericUrl(path);

  return requestFactory.buildGetRequest(url).execute();
}

See Putting it all together in the sample app for examples that use PATCH and DELETE.

Listening to real-time events from a web browser

To listen to real-time events, you need to:

  • Add the Firebase SDK to your web page.
  • Add a reference to the Firebase path where your data is stored.
  • Add a listener.
  • Implement a callback function.

Adding the Firebase SDK to your web page

To add the Firebase SDK to your web page:

Listening to changes in data

Retrieve Firebase data by attaching an asynchronous listener to a firebase.database.Reference. The listener is triggered once for the initial state of the data, and again anytime the data changes. There are two main types of changes that the client can listen for:

  • Value events: Read and listen for changes to the entire contents of a path. Value events are most commonly used to monitor changes to a single object.
  • Child events: Read and listen for changes to child elements under a particular path. Child events are most commonly used to monitor changes within lists of objects. It is possible to listen for the following child events: child_added, child_changed, child_moved, and child_removed.

This example app keeps a single game-state object in sync between the server and client. For this reason, this article focuses on using listening for value events. Child-event listeners and more advanced data retrieval techniques are described in Retrieve Data on the Web.

The following code shows how to listen for a value-change event and add a callback function to do work when the event is fired. The code first creates a database reference, which is the path for the object to which your app listens. Next the listener is added to the reference using the reference.on() method, where the callback function is passed as an argument.

In this example, a real-time tic-tac-toe app sends game information to clients listening on particular channels, which correspond to the user’s game, and updates the UI when this information changes in the database. The variable firebase is automatically created by the JavaScript snippet that was added in Adding Firebase to your web page.

Python

// setup a database reference at path /channels/channelId
channel = firebase.database().ref('channels/' + channelId);
// add a listener to the path that fires any time the value of the data changes
channel.on('value', function(data) {
  onMessage(data.val());
});

Java

// setup a database reference at path /channels/channelId
channel = firebase.database().ref('channels/' + channelId);
// add a listener to the path that fires any time the value of the data changes
channel.on('value', function(data) {
  onMessage(data.val());
});

The implementation of the callback function, onMessage(), is shown later in this article.

Detaching listeners

You can remove callbacks by calling the off() method of your Firebase database reference.

Posting events back to App Engine

App Engine doesn't currently support bidirectional streaming HTTP connections. If a client needs to update the server, it must send an explicit HTTP request.

Restricting access to your data

Firebase allows you to restrict access to data to specific users by using authorization tokens. We recommend that only your server have write access, while authorizing web clients for read only access. The code below demonstrates how to:

  • Generate credentials for your App Engine server.
  • Generate tokens for each client.
  • Pass tokens to web clients that they can use to read data.

Establishing communication between App Engine and Firebase

You can use App Engine’s built-in Application Default Credentials to make authenticated REST calls to your Firebase database from your App Engine server.

Python

Instantiate a GoogleCredentials object, provided by the oauth2client package, with the proper scopes to grant access to Firebase. Then, use that object to authorize an httplib2.Http object.

def _get_http():
    """Provides an authed http object."""
    http = httplib2.Http()
    # Use application default credentials to make the Firebase calls
    # https://firebase.google.com/docs/reference/rest/database/user-auth
    creds = GoogleCredentials.get_application_default().create_scoped(
        _FIREBASE_SCOPES)
    creds.authorize(http)
    return http

That object automatically includes the proper authentication token, and you can use the httplib2.Http object directly to make REST calls. The following function uses the object returned from _get_http() to send either a PATCH or DELETE request. Note that this example calls _get_http(), as part of its return statement, to retrieve the authorized HTTP object:

def _send_firebase_message(u_id, message=None):
    """Updates data in firebase. If a message is provided, then it updates
     the data at /channels/<channel_id> with the message using the PATCH
     http method. If no message is provided, then the data at this location
     is deleted using the DELETE http method
     """
    url = '{}/channels/{}.json'.format(_get_firebase_db_url(), u_id)

    if message:
        return _get_http().request(url, 'PATCH', body=message)
    else:
        return _get_http().request(url, 'DELETE')

Java

Because App Engine restricts the ability to create background threads, you need to use the Firebase API instead of the Firebase Server SDK directly.

First, retrieve the Application Default Credentials and initialize the UrlFetchTransport object.

credential = GoogleCredential.getApplicationDefault().createScoped(FIREBASE_SCOPES);
      httpTransport = UrlFetchTransport.getDefaultInstance();

Then, use that credential to create an authorized HttpRequestFactory object, using the URL Fetch service that App Engine requires for outbound HTTP requests. To make calls to your Firebase database, you construct a path, and perform the request:

public void sendFirebaseMessage(String channelKey, Game game) throws IOException {
  // Make requests auth'ed using Application Default Credentials
  HttpRequestFactory requestFactory = httpTransport.createRequestFactory(credential);
  GenericUrl url =
      new GenericUrl(String.format("%s/channels/%s.json", firebaseDbUrl, channelKey));
  HttpResponse response = null;

  try {
    if (null == game) {
      response = requestFactory.buildDeleteRequest(url).execute();
    } else {
      String gameJson = new Gson().toJson(game);
      response =
          requestFactory
              .buildPatchRequest(
                  url, new ByteArrayContent("application/json", gameJson.getBytes()))
              .execute();
    }

    if (response.getStatusCode() != 200) {
      throw new RuntimeException(
          "Error code while updating Firebase: " + response.getStatusCode());
    }

  } finally {
    if (null != response) {
      response.disconnect();
    }
  }
}

The examples above use the httplib2 library for Python and the Google HTTP Client Library for Java to handle the REST requests. However, you can use the method of your preference to perform the REST requests.

Generating the Client Authentication Token(s)

Firebase uses JSON Web Tokens (JWTs) to authorize users. To create JWTs, the app takes advantage of App Engine’s built-in App Identity service to sign the claims using Application Default Credentials.

The following method demonstrates how to create custom JWTs:

  1. The App Identity service is used to automatically retrieve the service account email.
  2. The header of the token is constructed.
  3. The claims are added to the token payload according to the Firebase authentication documentation. The most important of these claims is uid, which is the unique channel identifier that restricts access to a single user.
  4. Finally, the App Identity service is used again to sign the token.

Python

def create_custom_token(uid, valid_minutes=60):
    """Create a secure token for the given id.

    This method is used to create secure custom JWT tokens to be passed to
    clients. It takes a unique id (uid) that will be used by Firebase's
    security rules to prevent unauthorized access. In this case, the uid will
    be the channel id which is a combination of user_id and game_key
    """

    # use the app_identity service from google.appengine.api to get the
    # project's service account email automatically
    client_email = app_identity.get_service_account_name()

    now = int(time.time())
    # encode the required claims
    # per https://firebase.google.com/docs/auth/server/create-custom-tokens
    payload = base64.b64encode(json.dumps({
        'iss': client_email,
        'sub': client_email,
        'aud': _IDENTITY_ENDPOINT,
        'uid': uid,  # the important parameter, as it will be the channel id
        'iat': now,
        'exp': now + (valid_minutes * 60),
    }))
    # add standard header to identify this as a JWT
    header = base64.b64encode(json.dumps({'typ': 'JWT', 'alg': 'RS256'}))
    to_sign = '{}.{}'.format(header, payload)
    # Sign the jwt using the built in app_identity service
    return '{}.{}'.format(to_sign, base64.b64encode(
        app_identity.sign_blob(to_sign)[1]))

Java

public String createFirebaseToken(Game game, String userId) {
  final AppIdentityService appIdentity = AppIdentityServiceFactory.getAppIdentityService();
  final BaseEncoding base64 = BaseEncoding.base64();

  String header = base64.encode("{\"typ\":\"JWT\",\"alg\":\"RS256\"}".getBytes());

  // Construct the claim
  String channelKey = game.getChannelKey(userId);
  String clientEmail = appIdentity.getServiceAccountName();
  long epochTime = System.currentTimeMillis() / 1000;
  long expire = epochTime + 60 * 60; // an hour from now

  Map<String, Object> claims = new HashMap<String, Object>();
  claims.put("iss", clientEmail);
  claims.put("sub", clientEmail);
  claims.put("aud", IDENTITY_ENDPOINT);
  claims.put("uid", channelKey);
  claims.put("iat", epochTime);
  claims.put("exp", expire);

  String payload = base64.encode(new Gson().toJson(claims).getBytes());
  String toSign = String.format("%s.%s", header, payload);
  AppIdentityService.SigningResult result = appIdentity.signForApp(toSign.getBytes());
  return String.format("%s.%s", toSign, base64.encode(result.getSignature()));
}

The server executes the preceding function and passes the tokens into the template to be rendered on the client. In this example tic-tac-toe app, the player’s user ID and the game key comprise the uid for Firebase authorization.

Authenticating the JavaScript client

After the tokens have been generated on the server and passed into the client HTML, the tokens can be used to authorize read and write commands by the client. It is only necessary to authenticate the client once, and then all subsequent calls using the firebase object are authenticated. The sample code demonstrates how this is done:

Python

// sign into Firebase with the token passed from the server
firebase.auth().signInWithCustomToken(token).catch(function(error) {
  console.log('Login Failed!', error.code);
  console.log('Error message: ', error.message);
});

Java

// sign into Firebase with the token passed from the server
firebase.auth().signInWithCustomToken(token).catch(function(error) {
  console.log('Login Failed!', error.code);
  console.log('Error message: ', error.message);
});

Updating Firebase database security rules

In the Database section of the Firebase console, select the Rules tab. Now update Firebase’s security rules to restrict access to only authorized connections. Specifically, restrict writes to the server and only allow reads when the user’s unique ID, uid, in the auth payload matches the unique ID in the database path.

{
    "rules": {
      ".read": false,
      ".write": false,
      "channels": {
        "$channelId": {
          // Require auth token ID to match the path the client is trying to read
          ".read": "auth.uid == $channelId",
          ".write": false
        }
      }
    }
}

Putting it all together in the sample app

To better illustrate how to use Firebase to build real-time apps, look at the sample tic-tac-toe game app. The full source code of the Python app is available at the Fire Tac Toe GitHub page. The full source code of the Java app is available at the Fire Tac Toe GitHub page.

The game allows users to create a game, invite another player by sending out a URL, and play the game together in real time. The app updates both players' views of the board, in real time, as soon as the other player makes a move.

The remainder of this article reviews major highlights that illustrate the key capabilities.

Connecting to a Firebase path

When a user visits the game for the first time, two things happen:

  • The game server injects a token into the HTML page sent to the client. The client uses this token to open a connection to Firebase and listen for updates. The unique path that the user listens to is /channels/[USER_ID + GAME_KEY].
  • The game server provides the user with a URL they can share with a friend in order to invite them to join the game.

The following server-side code creates the Firebase path for the game app and injects it into the client’s HTML:

Python

# choose a unique identifier for channel_id
channel_id = user.user_id() + game_key
# encrypt the channel_id and send it as a custom token to the
# client
# Firebase's data security rules will be able to decrypt the
# token and prevent unauthorized access
client_auth_token = create_custom_token(channel_id)
_send_firebase_message(channel_id, message=game.to_json())

# game_link is a url that you can open in another browser to play
# against this player
game_link = '{}?g={}'.format(request.base_url, game_key)

# push all the data to the html template so the client will
# have access
template_values = {
    'token': client_auth_token,
    'channel_id': channel_id,
    'me': user.user_id(),
    'game_key': game_key,
    'game_link': game_link,
    'initial_message': urllib.unquote(game.to_json())
}

return flask.render_template('fire_index.html', **template_values)

Java

// The 'Game' object exposes a method which creates a unique string based on the game's key
// and the user's id.
String token = FirebaseChannel.getInstance().createFirebaseToken(game, userId);
request.setAttribute("token", token);

// 4. More general template values
request.setAttribute("game_key", gameKey);
request.setAttribute("me", userId);
request.setAttribute("channel_id", game.getChannelKey(userId));
request.setAttribute("initial_message", new Gson().toJson(game));
request.setAttribute("game_link", getGameUriWithGameParam(request, gameKey));
request.getRequestDispatcher("/WEB-INF/view/index.jsp").forward(request, response);

Now the web client uses the token to listen to the path /channels/[USER_ID + GAME_KEY]. This happens in the openChannel() function. A listener is added to track changes in the value of the path. The <reference>.on() method from Firebase is used to setup a callback that executes on value changes in this case. When the value of the database path changes, the onMessage() method is called to update the game state and UI for the user.

Python

function openChannel() {
  // sign into Firebase with the token passed from the server
  firebase.auth().signInWithCustomToken(token).catch(function(error) {
    console.log('Login Failed!', error.code);
    console.log('Error message: ', error.message);
  });

  // setup a database reference at path /channels/channelId
  channel = firebase.database().ref('channels/' + channelId);
  // add a listener to the path that fires any time the value of the data changes
  channel.on('value', function(data) {
    onMessage(data.val());
  });
  onOpened();
  // let the server know that the channel is open
}
...
function onOpened() {
  $.post('/opened');
}

Java

function openChannel() {
  // sign into Firebase with the token passed from the server
  firebase.auth().signInWithCustomToken(token).catch(function(error) {
    console.log('Login Failed!', error.code);
    console.log('Error message: ', error.message);
  });

  // setup a database reference at path /channels/channelId
  channel = firebase.database().ref('channels/' + channelId);
  // add a listener to the path that fires any time the value of the data changes
  channel.on('value', function(data) {
    onMessage(data.val());
  });
  onOpened();
  // let the server know that the channel is open
}
...
function onOpened() {
  $.post('/opened');
}

Sending updates to the server

When the user makes a move, the method moveInSquare() is called, which checks to see that the move is valid and then sends a HTTP POST request to the server with the location of the move.

Python

function moveInSquare(e) {
  var id = $(e.currentTarget).index();
  if (isMyMove() && state.board[id] === ' ') {
    $.post('/move', {i: id});
  }
}

Java

function moveInSquare(e) {
  var id = $(e.currentTarget).index();
  if (isMyMove() && state.board[id] === ' ') {
    $.post('/move', {cell: id});
  }
}

The server receives the HTTP POST and the handler for the /move path is called. This handler identifies the user and pulls the location of the square off of the request. It then performs the move by calling the method on the Game object:

Python

def move():
    game = Game.get_by_id(request.args.get('g'))
    position = int(request.form.get('i'))
    if not (game and (0 <= position <= 8)):
        return 'Game not found, or invalid position', 400
    game.make_move(position, users.get_current_user())
    return ''

Java

public class MoveServlet extends HttpServlet {

  @Override
  public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
    String gameId = request.getParameter("gameKey");
    Objectify ofy = ObjectifyService.ofy();
    Game game = ofy.load().type(Game.class).id(gameId).safe();

    UserService userService = UserServiceFactory.getUserService();
    String currentUserId = userService.getCurrentUser().getUserId();

    int cell = new Integer(request.getParameter("cell"));
    if (!game.makeMove(cell, currentUserId)) {
      response.sendError(HttpServletResponse.SC_FORBIDDEN);
    } else {
      ofy.save().entity(game).now();
    }
  }
}

The method that performs the move completes a final round of validation on the user’s move, checks to see if the user has won the game, and writes the game state to the datastore. Finally, it calls a method to update the game state in Firebase.

Python

def make_move(self, position, user):
    # If the user is a player, and it's their move
    if (user in (self.userX, self.userO)) and (
            self.moveX == (user == self.userX)):
        boardList = list(self.board)
        # If the spot you want to move to is blank
        if (boardList[position] == ' '):
            boardList[position] = 'X' if self.moveX else 'O'
            self.board = ''.join(boardList)
            self.moveX = not self.moveX
            self._check_win()
            self.put()
            self.send_update()
            return

Java

public boolean makeMove(int position, String userId) {
  String currentMovePlayer;
  char value;
  if (getMoveX()) {
    value = 'X';
    currentMovePlayer = getUserX();
  } else {
    value = 'O';
    currentMovePlayer = getUserO();
  }

  if (currentMovePlayer.equals(userId)) {
    char[] boardBytes = getBoard().toCharArray();
    boardBytes[position] = value;
    setBoard(new String(boardBytes));
    checkWin();
    setMoveX(!getMoveX());
    try {
      sendUpdateToClients();
    } catch (IOException e) {
      LOGGER.log(Level.SEVERE, "Error sending Game update to Firebase", e);
      throw new RuntimeException(e);
    }
    return true;
  }

  return false;
}

The update method then calls the method you defined above, to perform the HTTP request to Firebase:

Python

def send_update(self):
    """Updates Firebase's copy of the board."""
    message = self.to_json()
    # send updated game state to user X
    _send_firebase_message(
        self.userX.user_id() + self.key.id(), message=message)
    # send updated game state to user O
    if self.userO:
        _send_firebase_message(
            self.userO.user_id() + self.key.id(), message=message)

Java

public String getChannelKey(String userId) {
  return userId + id;
}

/**
 * deleteChannel.
 * @param userId .
 * @throws IOException .
 */
public void deleteChannel(String userId) throws IOException {
  if (userId != null) {
    String channelKey = getChannelKey(userId);
    FirebaseChannel.getInstance().sendFirebaseMessage(channelKey, null);
  }
}

private void sendUpdateToUser(String userId) throws IOException {
  if (userId != null) {
    String channelKey = getChannelKey(userId);
    FirebaseChannel.getInstance().sendFirebaseMessage(channelKey, this);
  }
}

/**
 * sendUpdateToClients.
 * @throws IOException if we had some kind of network issue.
 */
public void sendUpdateToClients() throws IOException {
  sendUpdateToUser(userX);
  sendUpdateToUser(userO);
}

Finally, it makes the HTTP request:

Python

def _send_firebase_message(u_id, message=None):
    """Updates data in firebase. If a message is provided, then it updates
     the data at /channels/<channel_id> with the message using the PATCH
     http method. If no message is provided, then the data at this location
     is deleted using the DELETE http method
     """
    url = '{}/channels/{}.json'.format(_get_firebase_db_url(), u_id)

    if message:
        return _get_http().request(url, 'PATCH', body=message)
    else:
        return _get_http().request(url, 'DELETE')

Java

public void sendFirebaseMessage(String channelKey, Game game) throws IOException {
  // Make requests auth'ed using Application Default Credentials
  HttpRequestFactory requestFactory = httpTransport.createRequestFactory(credential);
  GenericUrl url =
      new GenericUrl(String.format("%s/channels/%s.json", firebaseDbUrl, channelKey));
  HttpResponse response = null;

  try {
    if (null == game) {
      response = requestFactory.buildDeleteRequest(url).execute();
    } else {
      String gameJson = new Gson().toJson(game);
      response =
          requestFactory
              .buildPatchRequest(
                  url, new ByteArrayContent("application/json", gameJson.getBytes()))
              .execute();
    }

    if (response.getStatusCode() != 200) {
      throw new RuntimeException(
          "Error code while updating Firebase: " + response.getStatusCode());
    }

  } finally {
    if (null != response) {
      response.disconnect();
    }
  }
}

After this call to Firebase, the web clients receive the updated game state immediately, without polling the server.

Removing listeners and deleting data

When a player has won the game, there is no additional information to be sent from the server. In this case, you need to remove the listeners and delete the game data from Firebase because storage isn't necessarily free. To do this, call the off() method on the Firebase reference to which the clients have been listening. Finally, the client sends a message to the server to the /delete endpoint, which removes the data stored for the game that ended.

Python

function onMessage(newState) {
  updateGame(newState);

  // now check to see if there is a winner
  if (channel && state.winner && state.winningBoard) {
    channel.off(); //stop listening on this path
    deleteChannel(); //delete the data we wrote
  }
}
...
function deleteChannel() {
  $.post('/delete');
}

Java

function onMessage(newState) {
  updateGame(newState);

  // now check to see if there is a winner
  if (channel && state.winner && state.winningBoard) {
    channel.off(); //stop listening on this path
    deleteChannel(); //delete the data we wrote
  }
}
...
function deleteChannel() {
  $.post('/delete');
}

Delete data in Firebase by hitting the /delete endpoint's handler:

Python

def delete():
    game = Game.get_by_id(request.args.get('g'))
    if not game:
        return 'Game not found', 400
    user = users.get_current_user()
    _send_firebase_message(user.user_id() + game.key.id(), message=None)
    return ''

Java

public class DeleteServlet extends HttpServlet {
  @Override
  public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
    String gameId = request.getParameter("gameKey");
    Objectify ofy = ObjectifyService.ofy();
    Game game = ofy.load().type(Game.class).id(gameId).safe();

    UserService userService = UserServiceFactory.getUserService();
    String currentUserId = userService.getCurrentUser().getUserId();

    // TODO(you): In practice, first validate that the user has permission to delete the Game
    game.deleteChannel(currentUserId);
  }
}

What's next

  • Try out other Google Cloud Platform features for yourself. Have a look at our tutorials.
Was this page helpful? Let us know how we did:

Send feedback about...