52
52
53
53
54
54
def _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"""
57
58
if 'dburl' not in _memo :
59
+ # Memoize the value, to avoid parsing the code snippet every time
58
60
regex = re .compile (r'\bdatabaseURL\b.*?["\']([^"\']+)' )
59
61
cwd = os .path .dirname (__file__ )
60
62
with open (os .path .join (cwd , 'templates' , _FIREBASE_CONFIG )) as f :
61
63
url = next (regex .search (line ) for line in f if regex .search (line ))
62
64
_memo ['dburl' ] = url .group (1 )
63
65
return _memo ['dburl' ]
64
66
65
-
67
+ # [START authed_http]
66
68
def _get_http (_memo = {}):
67
69
"""Provides an authed http object."""
68
70
if 'http' not in _memo :
@@ -75,17 +77,24 @@ def _get_http(_memo={}):
75
77
creds .authorize (http )
76
78
_memo ['http' ] = http
77
79
return _memo ['http' ]
80
+ # [END authed_http]
78
81
79
-
82
+ # [START send_msg]
80
83
def _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
+ """
81
89
url = '{}/channels/{}.json' .format (_get_firebase_db_url (), u_id )
82
90
83
91
if message :
84
92
return _get_http ().request (url , 'PATCH' , body = message )
85
93
else :
86
94
return _get_http ().request (url , 'DELETE' )
95
+ # [END send_msg]
87
96
88
-
97
+ # [START create_token]
89
98
def create_custom_token (uid , valid_minutes = 60 ):
90
99
"""Create a secure token for the given id.
91
100
@@ -94,25 +103,29 @@ def create_custom_token(uid, valid_minutes=60):
94
103
security rules to prevent unauthorized access. In this case, the uid will
95
104
be the channel id which is a combination of user_id and game_key
96
105
"""
97
- header = base64 .b64encode (json .dumps ({'typ' : 'JWT' , 'alg' : 'RS256' }))
98
106
107
+ # use the app_identity service from google.appengine.api to get the
108
+ # project's service account email automatically
99
109
client_email = app_identity .get_service_account_name ()
110
+
100
111
now = int (time .time ())
112
+ # encode the required claims
113
+ # per https://firebase.google.com/docs/auth/server/create-custom-tokens
101
114
payload = base64 .b64encode (json .dumps ({
102
115
'iss' : client_email ,
103
116
'sub' : client_email ,
104
117
'aud' : _IDENTITY_ENDPOINT ,
105
- 'uid' : uid ,
118
+ 'uid' : uid , # this is the important parameter as it will be the channel id
106
119
'iat' : now ,
107
120
'exp' : now + (valid_minutes * 60 ),
108
121
}))
109
-
122
+ # add standard header to identify this as a JWT
123
+ header = base64 .b64encode (json .dumps ({'typ' : 'JWT' , 'alg' : 'RS256' }))
110
124
to_sign = '{}.{}' .format (header , payload )
111
-
112
- # Sign the jwt
125
+ # Sign the jwt using the built in app_identity service
113
126
return '{}.{}' .format (to_sign , base64 .b64encode (
114
127
app_identity .sign_blob (to_sign )[1 ]))
115
-
128
+ # [END create_token]
116
129
117
130
class Game (ndb .Model ):
118
131
"""All the data we store for a game"""
@@ -128,6 +141,7 @@ def to_json(self):
128
141
d ['winningBoard' ] = d .pop ('winning_board' )
129
142
return json .dumps (d , default = lambda user : user .user_id ())
130
143
144
+ # [START send_update]
131
145
def send_update (self ):
132
146
"""Updates Firebase's copy of the board."""
133
147
message = self .to_json ()
@@ -140,6 +154,7 @@ def send_update(self):
140
154
_send_firebase_message (
141
155
self .userO .user_id () + self .key .id (),
142
156
message = message )
157
+ # [END send_update]
143
158
144
159
def _check_win (self ):
145
160
if self .moveX :
@@ -161,6 +176,7 @@ def _check_win(self):
161
176
if ' ' not in self .board :
162
177
self .winner = 'Noone'
163
178
179
+ # [START make_move]
164
180
def make_move (self , position , user ):
165
181
# If the user is a player, and it's their move
166
182
if (user in (self .userX , self .userO )) and (
@@ -175,8 +191,9 @@ def make_move(self, position, user):
175
191
self .put ()
176
192
self .send_update ()
177
193
return
194
+ # [END make_move]
178
195
179
-
196
+ # [START move_route]
180
197
@app .route ('/move' , methods = ['POST' ])
181
198
def move ():
182
199
game = Game .get_by_id (request .args .get ('g' ))
@@ -185,8 +202,9 @@ def move():
185
202
return 'Game not found, or invalid position' , 400
186
203
game .make_move (position , users .get_current_user ())
187
204
return ''
205
+ # [END move_route]
188
206
189
-
207
+ # [START route_delete]
190
208
@app .route ('/delete' , methods = ['POST' ])
191
209
def delete ():
192
210
game = Game .get_by_id (request .args .get ('g' ))
@@ -196,6 +214,7 @@ def delete():
196
214
_send_firebase_message (
197
215
user .user_id () + game .key .id (), message = None )
198
216
return ''
217
+ # [END route_delete]
199
218
200
219
201
220
@app .route ('/opened' , methods = ['POST' ])
@@ -226,6 +245,7 @@ def main_page():
226
245
game .userO = user
227
246
game .put ()
228
247
248
+ # [START pass_token]
229
249
# choose a unique identifier for channel_id
230
250
channel_id = user .user_id () + game_key
231
251
# encrypt the channel_id and send it as a custom token to the
@@ -236,6 +256,8 @@ def main_page():
236
256
_send_firebase_message (
237
257
channel_id , message = game .to_json ())
238
258
259
+ # game_link is a url that you can open in another browser to play
260
+ # against this player
239
261
game_link = '{}?g={}' .format (request .base_url , game_key )
240
262
241
263
# push all the data to the html template so the client will
@@ -250,3 +272,4 @@ def main_page():
250
272
}
251
273
252
274
return flask .render_template ('fire_index.html' , ** template_values )
275
+ # [END pass_token]
0 commit comments