5252
5353
5454def _get_firebase_db_url (_memo = {}):
55- """Grabs the databaseURL from the Firebase config snippet."""
56- # Memoize the value, to avoid parsing the code snippet every time
55+ """Grabs the databaseURL from the Firebase config snippet. Regex looks
56+ scary, but all it is doing is pulling the 'databaseURL' field from the
57+ Firebase javascript snippet"""
5758 if 'dburl' not in _memo :
59+ # Memoize the value, to avoid parsing the code snippet every time
5860 regex = re .compile (r'\bdatabaseURL\b.*?["\']([^"\']+)' )
5961 cwd = os .path .dirname (__file__ )
6062 with open (os .path .join (cwd , 'templates' , _FIREBASE_CONFIG )) as f :
6163 url = next (regex .search (line ) for line in f if regex .search (line ))
6264 _memo ['dburl' ] = url .group (1 )
6365 return _memo ['dburl' ]
6466
65-
67+ # [START authed_http]
6668def _get_http (_memo = {}):
6769 """Provides an authed http object."""
6870 if 'http' not in _memo :
@@ -75,17 +77,24 @@ def _get_http(_memo={}):
7577 creds .authorize (http )
7678 _memo ['http' ] = http
7779 return _memo ['http' ]
80+ # [END authed_http]
7881
79-
82+ # [START send_msg]
8083def _send_firebase_message (u_id , message = None ):
84+ """Updates data in firebase. If a message is provided, then it updates
85+ the data at /channels/<channel_id> with the message using the PATCH
86+ http method. If no message is provided, then the data at this location
87+ is deleted using the DELETE http method
88+ """
8189 url = '{}/channels/{}.json' .format (_get_firebase_db_url (), u_id )
8290
8391 if message :
8492 return _get_http ().request (url , 'PATCH' , body = message )
8593 else :
8694 return _get_http ().request (url , 'DELETE' )
95+ # [END send_msg]
8796
88-
97+ # [START create_token]
8998def create_custom_token (uid , valid_minutes = 60 ):
9099 """Create a secure token for the given id.
91100
@@ -94,25 +103,29 @@ def create_custom_token(uid, valid_minutes=60):
94103 security rules to prevent unauthorized access. In this case, the uid will
95104 be the channel id which is a combination of user_id and game_key
96105 """
97- header = base64 .b64encode (json .dumps ({'typ' : 'JWT' , 'alg' : 'RS256' }))
98106
107+ # use the app_identity service from google.appengine.api to get the
108+ # project's service account email automatically
99109 client_email = app_identity .get_service_account_name ()
110+
100111 now = int (time .time ())
112+ # encode the required claims
113+ # per https://firebase.google.com/docs/auth/server/create-custom-tokens
101114 payload = base64 .b64encode (json .dumps ({
102115 'iss' : client_email ,
103116 'sub' : client_email ,
104117 'aud' : _IDENTITY_ENDPOINT ,
105- 'uid' : uid ,
118+ 'uid' : uid , # this is the important parameter as it will be the channel id
106119 'iat' : now ,
107120 'exp' : now + (valid_minutes * 60 ),
108121 }))
109-
122+ # add standard header to identify this as a JWT
123+ header = base64 .b64encode (json .dumps ({'typ' : 'JWT' , 'alg' : 'RS256' }))
110124 to_sign = '{}.{}' .format (header , payload )
111-
112- # Sign the jwt
125+ # Sign the jwt using the built in app_identity service
113126 return '{}.{}' .format (to_sign , base64 .b64encode (
114127 app_identity .sign_blob (to_sign )[1 ]))
115-
128+ # [END create_token]
116129
117130class Game (ndb .Model ):
118131 """All the data we store for a game"""
@@ -128,6 +141,7 @@ def to_json(self):
128141 d ['winningBoard' ] = d .pop ('winning_board' )
129142 return json .dumps (d , default = lambda user : user .user_id ())
130143
144+ # [START send_update]
131145 def send_update (self ):
132146 """Updates Firebase's copy of the board."""
133147 message = self .to_json ()
@@ -140,6 +154,7 @@ def send_update(self):
140154 _send_firebase_message (
141155 self .userO .user_id () + self .key .id (),
142156 message = message )
157+ # [END send_update]
143158
144159 def _check_win (self ):
145160 if self .moveX :
@@ -161,6 +176,7 @@ def _check_win(self):
161176 if ' ' not in self .board :
162177 self .winner = 'Noone'
163178
179+ # [START make_move]
164180 def make_move (self , position , user ):
165181 # If the user is a player, and it's their move
166182 if (user in (self .userX , self .userO )) and (
@@ -175,8 +191,9 @@ def make_move(self, position, user):
175191 self .put ()
176192 self .send_update ()
177193 return
194+ # [END make_move]
178195
179-
196+ # [START move_route]
180197@app .route ('/move' , methods = ['POST' ])
181198def move ():
182199 game = Game .get_by_id (request .args .get ('g' ))
@@ -185,8 +202,9 @@ def move():
185202 return 'Game not found, or invalid position' , 400
186203 game .make_move (position , users .get_current_user ())
187204 return ''
205+ # [END move_route]
188206
189-
207+ # [START route_delete]
190208@app .route ('/delete' , methods = ['POST' ])
191209def delete ():
192210 game = Game .get_by_id (request .args .get ('g' ))
@@ -196,6 +214,7 @@ def delete():
196214 _send_firebase_message (
197215 user .user_id () + game .key .id (), message = None )
198216 return ''
217+ # [END route_delete]
199218
200219
201220@app .route ('/opened' , methods = ['POST' ])
@@ -226,6 +245,7 @@ def main_page():
226245 game .userO = user
227246 game .put ()
228247
248+ # [START pass_token]
229249 # choose a unique identifier for channel_id
230250 channel_id = user .user_id () + game_key
231251 # encrypt the channel_id and send it as a custom token to the
@@ -236,6 +256,8 @@ def main_page():
236256 _send_firebase_message (
237257 channel_id , message = game .to_json ())
238258
259+ # game_link is a url that you can open in another browser to play
260+ # against this player
239261 game_link = '{}?g={}' .format (request .base_url , game_key )
240262
241263 # push all the data to the html template so the client will
@@ -250,3 +272,4 @@ def main_page():
250272 }
251273
252274 return flask .render_template ('fire_index.html' , ** template_values )
275+ # [END pass_token]
0 commit comments