| OLD | NEW |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 'use strict'; | 5 'use strict'; |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * @fileoverview The event page for Google Now for Chrome implementation. | 8 * @fileoverview The event page for Google Now for Chrome implementation. |
| 9 * The Google Now event page gets Google Now cards from the server and shows | 9 * The Google Now event page gets Google Now cards from the server and shows |
| 10 * them as Chrome notifications. | 10 * them as Chrome notifications. |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 88 */ | 88 */ |
| 89 var DEFAULT_OPTIN_CHECK_PERIOD_SECONDS = 60 * 60 * 24 * 7; // 1 week | 89 var DEFAULT_OPTIN_CHECK_PERIOD_SECONDS = 60 * 60 * 24 * 7; // 1 week |
| 90 | 90 |
| 91 /** | 91 /** |
| 92 * URL to open when the user clicked on a link for the our notification | 92 * URL to open when the user clicked on a link for the our notification |
| 93 * settings. | 93 * settings. |
| 94 */ | 94 */ |
| 95 var SETTINGS_URL = 'https://support.google.com/chrome/?p=ib_google_now_welcome'; | 95 var SETTINGS_URL = 'https://support.google.com/chrome/?p=ib_google_now_welcome'; |
| 96 | 96 |
| 97 /** | 97 /** |
| 98 * GCM registration URL. |
| 99 */ |
| 100 var GCM_REGISTRATION_URL = |
| 101 'https://android.googleapis.com/gcm/googlenotification'; |
| 102 |
| 103 /** |
| 104 * DevConsole project ID for GCM API use. |
| 105 */ |
| 106 var GCM_PROJECT_ID = '437902709571'; |
| 107 |
| 108 /** |
| 98 * Number of cards that need an explanatory link. | 109 * Number of cards that need an explanatory link. |
| 99 */ | 110 */ |
| 100 var EXPLANATORY_CARDS_LINK_THRESHOLD = 4; | 111 var EXPLANATORY_CARDS_LINK_THRESHOLD = 4; |
| 101 | 112 |
| 102 /** | 113 /** |
| 103 * Names for tasks that can be created by the extension. | 114 * Names for tasks that can be created by the extension. |
| 104 */ | 115 */ |
| 105 var UPDATE_CARDS_TASK_NAME = 'update-cards'; | 116 var UPDATE_CARDS_TASK_NAME = 'update-cards'; |
| 106 var DISMISS_CARD_TASK_NAME = 'dismiss-card'; | 117 var DISMISS_CARD_TASK_NAME = 'dismiss-card'; |
| 107 var RETRY_DISMISS_TASK_NAME = 'retry-dismiss'; | 118 var RETRY_DISMISS_TASK_NAME = 'retry-dismiss'; |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 182 // send dismissals is scheduled. | 193 // send dismissals is scheduled. |
| 183 return true; | 194 return true; |
| 184 } | 195 } |
| 185 | 196 |
| 186 return false; | 197 return false; |
| 187 } | 198 } |
| 188 | 199 |
| 189 var tasks = buildTaskManager(areTasksConflicting); | 200 var tasks = buildTaskManager(areTasksConflicting); |
| 190 | 201 |
| 191 // Add error processing to API calls. | 202 // Add error processing to API calls. |
| 203 wrapper.instrumentChromeApiFunction('gcm.onMessage.addListener', 0); |
| 204 wrapper.instrumentChromeApiFunction('gcm.register', 1); |
| 192 wrapper.instrumentChromeApiFunction('metricsPrivate.getVariationParams', 1); | 205 wrapper.instrumentChromeApiFunction('metricsPrivate.getVariationParams', 1); |
| 193 wrapper.instrumentChromeApiFunction('notifications.clear', 1); | 206 wrapper.instrumentChromeApiFunction('notifications.clear', 1); |
| 194 wrapper.instrumentChromeApiFunction('notifications.create', 2); | 207 wrapper.instrumentChromeApiFunction('notifications.create', 2); |
| 195 wrapper.instrumentChromeApiFunction('notifications.getPermissionLevel', 0); | 208 wrapper.instrumentChromeApiFunction('notifications.getPermissionLevel', 0); |
| 196 wrapper.instrumentChromeApiFunction('notifications.update', 2); | 209 wrapper.instrumentChromeApiFunction('notifications.update', 2); |
| 197 wrapper.instrumentChromeApiFunction('notifications.getAll', 0); | 210 wrapper.instrumentChromeApiFunction('notifications.getAll', 0); |
| 198 wrapper.instrumentChromeApiFunction( | 211 wrapper.instrumentChromeApiFunction( |
| 199 'notifications.onButtonClicked.addListener', 0); | 212 'notifications.onButtonClicked.addListener', 0); |
| 200 wrapper.instrumentChromeApiFunction('notifications.onClicked.addListener', 0); | 213 wrapper.instrumentChromeApiFunction('notifications.onClicked.addListener', 0); |
| 201 wrapper.instrumentChromeApiFunction('notifications.onClosed.addListener', 0); | 214 wrapper.instrumentChromeApiFunction('notifications.onClosed.addListener', 0); |
| 202 wrapper.instrumentChromeApiFunction( | 215 wrapper.instrumentChromeApiFunction( |
| 203 'notifications.onPermissionLevelChanged.addListener', 0); | 216 'notifications.onPermissionLevelChanged.addListener', 0); |
| 204 wrapper.instrumentChromeApiFunction( | 217 wrapper.instrumentChromeApiFunction( |
| 205 'notifications.onShowSettings.addListener', 0); | 218 'notifications.onShowSettings.addListener', 0); |
| 206 wrapper.instrumentChromeApiFunction('permissions.contains', 1); | 219 wrapper.instrumentChromeApiFunction('permissions.contains', 1); |
| 207 wrapper.instrumentChromeApiFunction('pushMessaging.onMessage.addListener', 0); | |
| 208 wrapper.instrumentChromeApiFunction('storage.onChanged.addListener', 0); | |
| 209 wrapper.instrumentChromeApiFunction('runtime.onInstalled.addListener', 0); | 220 wrapper.instrumentChromeApiFunction('runtime.onInstalled.addListener', 0); |
| 210 wrapper.instrumentChromeApiFunction('runtime.onStartup.addListener', 0); | 221 wrapper.instrumentChromeApiFunction('runtime.onStartup.addListener', 0); |
| 222 wrapper.instrumentChromeApiFunction('storage.onChanged.addListener', 0); |
| 211 wrapper.instrumentChromeApiFunction('tabs.create', 1); | 223 wrapper.instrumentChromeApiFunction('tabs.create', 1); |
| 212 | 224 |
| 213 var updateCardsAttempts = buildAttemptManager( | 225 var updateCardsAttempts = buildAttemptManager( |
| 214 'cards-update', | 226 'cards-update', |
| 215 requestCards, | 227 requestCards, |
| 216 INITIAL_POLLING_PERIOD_SECONDS, | 228 INITIAL_POLLING_PERIOD_SECONDS, |
| 217 MAXIMUM_POLLING_PERIOD_SECONDS); | 229 MAXIMUM_POLLING_PERIOD_SECONDS); |
| 218 var optInPollAttempts = buildAttemptManager( | 230 var optInPollAttempts = buildAttemptManager( |
| 219 'optin', | 231 'optin', |
| 220 pollOptedInNoImmediateRecheck, | 232 pollOptedInNoImmediateRecheck, |
| (...skipping 787 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1008 // We don't clear localStorage since those values are still relevant | 1020 // We don't clear localStorage since those values are still relevant |
| 1009 // across Google Now start-stop events. | 1021 // across Google Now start-stop events. |
| 1010 chrome.storage.local.clear(); | 1022 chrome.storage.local.clear(); |
| 1011 } | 1023 } |
| 1012 | 1024 |
| 1013 /** | 1025 /** |
| 1014 * Initializes the event page on install or on browser startup. | 1026 * Initializes the event page on install or on browser startup. |
| 1015 */ | 1027 */ |
| 1016 function initialize() { | 1028 function initialize() { |
| 1017 recordEvent(GoogleNowEvent.EXTENSION_START); | 1029 recordEvent(GoogleNowEvent.EXTENSION_START); |
| 1030 registerForGcm(); |
| 1018 onStateChange(); | 1031 onStateChange(); |
| 1019 } | 1032 } |
| 1020 | 1033 |
| 1021 /** | 1034 /** |
| 1022 * Starts or stops the main pipeline for polling cards. | 1035 * Starts or stops the main pipeline for polling cards. |
| 1023 * @param {boolean} shouldPollCardsRequest true to start and | 1036 * @param {boolean} shouldPollCardsRequest true to start and |
| 1024 * false to stop polling cards. | 1037 * false to stop polling cards. |
| 1025 */ | 1038 */ |
| 1026 function setShouldPollCards(shouldPollCardsRequest) { | 1039 function setShouldPollCards(shouldPollCardsRequest) { |
| 1027 updateCardsAttempts.isRunning(function(currentValue) { | 1040 updateCardsAttempts.isRunning(function(currentValue) { |
| (...skipping 169 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1197 * opt-in state. | 1210 * opt-in state. |
| 1198 */ | 1211 */ |
| 1199 function isGoogleNowEnabled() { | 1212 function isGoogleNowEnabled() { |
| 1200 return fillFromChromeLocalStorage({googleNowEnabled: false}) | 1213 return fillFromChromeLocalStorage({googleNowEnabled: false}) |
| 1201 .then(function(items) { | 1214 .then(function(items) { |
| 1202 return items.googleNowEnabled; | 1215 return items.googleNowEnabled; |
| 1203 }); | 1216 }); |
| 1204 } | 1217 } |
| 1205 | 1218 |
| 1206 /** | 1219 /** |
| 1220 * Ensures the extension is ready to listen for GCM messages. |
| 1221 */ |
| 1222 function registerForGcm() { |
| 1223 // We don't need to use the key yet, just ensure the channel is set up. |
| 1224 getGcmNotificationKey(); |
| 1225 } |
| 1226 |
| 1227 /** |
| 1228 * Returns a Promise resolving to either a cached or new GCM notification key. |
| 1229 * Rejects if registration fails. |
| 1230 * @return {Promise} A Promise that resolves to a potentially-cached GCM key. |
| 1231 */ |
| 1232 function getGcmNotificationKey() { |
| 1233 return fillFromChromeLocalStorage({gcmNotificationKey: undefined}) |
| 1234 .then(function(items) { |
| 1235 if (items.gcmNotificationKey) { |
| 1236 console.log('Reused gcm key from storage.'); |
| 1237 return Promise.resolve(items.gcmNotificationKey); |
| 1238 } |
| 1239 return requestNewGcmNotificationKey(); |
| 1240 }); |
| 1241 } |
| 1242 |
| 1243 /** |
| 1244 * Returns a promise resolving to a GCM Notificaiton Key. May call |
| 1245 * chrome.gcm.register() first if required. Rejects on registration failure. |
| 1246 * @return {Promise} A Promise that resolves to a fresh GCM Notification key. |
| 1247 */ |
| 1248 function requestNewGcmNotificationKey() { |
| 1249 return getGcmRegistrationId().then(function(gcmId) { |
| 1250 authenticationManager.getAuthToken().then(function(token) { |
| 1251 authenticationManager.getLogin().then(function(username) { |
| 1252 return new Promise(function(resolve, reject) { |
| 1253 var xhr = new XMLHttpRequest(); |
| 1254 xhr.responseType = 'application/json'; |
| 1255 xhr.open('POST', GCM_REGISTRATION_URL, true); |
| 1256 xhr.setRequestHeader('Content-Type', 'application/json'); |
| 1257 xhr.setRequestHeader('Authorization', 'Bearer ' + token); |
| 1258 xhr.setRequestHeader('project_id', GCM_PROJECT_ID); |
| 1259 var payload = { |
| 1260 'operation': 'add', |
| 1261 'notification_key_name': username, |
| 1262 'registration_ids': [gcmId] |
| 1263 }; |
| 1264 xhr.onloadend = function() { |
| 1265 if (xhr.status != 200) { |
| 1266 reject(); |
| 1267 } |
| 1268 var obj = JSON.parse(xhr.responseText); |
| 1269 var key = obj && obj.notification_key; |
| 1270 if (!key) { |
| 1271 reject(); |
| 1272 } |
| 1273 console.log('gcm notification key POST: ' + key); |
| 1274 chrome.storage.local.set({gcmNotificationKey: key}); |
| 1275 resolve(key); |
| 1276 }; |
| 1277 xhr.send(JSON.stringify(payload)); |
| 1278 }); |
| 1279 }); |
| 1280 }).catch(function() { |
| 1281 // Couldn't obtain a GCM ID. Ignore and fallback to polling. |
| 1282 }); |
| 1283 }); |
| 1284 } |
| 1285 |
| 1286 /** |
| 1287 * Returns a promise resolving to either a cached or new GCM registration ID. |
| 1288 * Rejects if registration fails. |
| 1289 * @return {Promise} A Promise that resolves to a GCM registration ID. |
| 1290 */ |
| 1291 function getGcmRegistrationId() { |
| 1292 return fillFromChromeLocalStorage({gcmRegistrationId: undefined}) |
| 1293 .then(function(items) { |
| 1294 if (items.gcmRegistrationId) { |
| 1295 console.log('Reused gcm registration id from storage.'); |
| 1296 return Promise.resolve(items.gcmRegistrationId); |
| 1297 } |
| 1298 |
| 1299 return new Promise(function(resolve, reject) { |
| 1300 instrumented.gcm.register([GCM_PROJECT_ID], function(registrationId) { |
| 1301 console.log('gcm.register(): ' + registrationId); |
| 1302 if (registrationId) { |
| 1303 chrome.storage.local.set({gcmRegistrationId: registrationId}); |
| 1304 resolve(registrationId); |
| 1305 } else { |
| 1306 reject(); |
| 1307 } |
| 1308 }); |
| 1309 }); |
| 1310 }); |
| 1311 } |
| 1312 |
| 1313 /** |
| 1207 * Polls the optin state. | 1314 * Polls the optin state. |
| 1208 * Sometimes we get the response to the opted in result too soon during | 1315 * Sometimes we get the response to the opted in result too soon during |
| 1209 * push messaging. We'll recheck the optin state a few times before giving up. | 1316 * push messaging. We'll recheck the optin state a few times before giving up. |
| 1210 */ | 1317 */ |
| 1211 function pollOptedInWithRecheck() { | 1318 function pollOptedInWithRecheck() { |
| 1212 /** | 1319 /** |
| 1213 * Cleans up any state used to recheck the opt-in poll. | 1320 * Cleans up any state used to recheck the opt-in poll. |
| 1214 */ | 1321 */ |
| 1215 function clearPollingState() { | 1322 function clearPollingState() { |
| 1216 localStorage.removeItem('optedInCheckCount'); | 1323 localStorage.removeItem('optedInCheckCount'); |
| (...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1327 | 1434 |
| 1328 // Handles state change notifications for the Google Now enabled bit. | 1435 // Handles state change notifications for the Google Now enabled bit. |
| 1329 instrumented.storage.onChanged.addListener(function(changes, areaName) { | 1436 instrumented.storage.onChanged.addListener(function(changes, areaName) { |
| 1330 if (areaName === 'local') { | 1437 if (areaName === 'local') { |
| 1331 if ('googleNowEnabled' in changes) { | 1438 if ('googleNowEnabled' in changes) { |
| 1332 onStateChange(); | 1439 onStateChange(); |
| 1333 } | 1440 } |
| 1334 } | 1441 } |
| 1335 }); | 1442 }); |
| 1336 | 1443 |
| 1337 instrumented.pushMessaging.onMessage.addListener(function(message) { | 1444 instrumented.gcm.onMessage.addListener(function(message) { |
| 1338 // message.payload will be '' when the extension first starts. | 1445 console.log('gcm.onMessage ' + JSON.stringify(message)); |
| 1339 // Each time after signing in, we'll get latest payload for all channels. | 1446 if (!message || !message.data) { |
| 1340 // So, we need to poll the server only when the payload is non-empty and has | 1447 return; |
| 1341 // changed. | 1448 } |
| 1342 console.log('pushMessaging.onMessage ' + JSON.stringify(message)); | 1449 |
| 1343 if (message.payload.indexOf('REQUEST_CARDS') == 0) { | 1450 var payload = message.data.payload; |
| 1451 var tag = message.data.tag; |
| 1452 if (payload.indexOf('REQUEST_CARDS') == 0) { |
| 1344 tasks.add(ON_PUSH_MESSAGE_START_TASK_NAME, function() { | 1453 tasks.add(ON_PUSH_MESSAGE_START_TASK_NAME, function() { |
| 1345 // Accept promise rejection on failure since it's safer to do nothing, | 1454 // Accept promise rejection on failure since it's safer to do nothing, |
| 1346 // preventing polling the server when the payload really didn't change. | 1455 // preventing polling the server when the payload really didn't change. |
| 1347 fillFromChromeLocalStorage({ | 1456 fillFromChromeLocalStorage({ |
| 1348 lastPollNowPayloads: {}, | 1457 lastPollNowPayloads: {}, |
| 1349 /** @type {Object<string, StoredNotificationGroup>} */ | 1458 /** @type {Object<string, StoredNotificationGroup>} */ |
| 1350 notificationGroups: {} | 1459 notificationGroups: {} |
| 1351 }, PromiseRejection.ALLOW).then(function(items) { | 1460 }, PromiseRejection.ALLOW).then(function(items) { |
| 1352 if (items.lastPollNowPayloads[message.subchannelId] != | 1461 if (items.lastPollNowPayloads[tag] != payload) { |
| 1353 message.payload) { | 1462 items.lastPollNowPayloads[tag] = payload; |
| 1354 items.lastPollNowPayloads[message.subchannelId] = message.payload; | |
| 1355 | 1463 |
| 1356 items.notificationGroups['PUSH' + message.subchannelId] = { | 1464 items.notificationGroups['PUSH' + tag] = { |
| 1357 cards: [], | 1465 cards: [], |
| 1358 nextPollTime: Date.now() | 1466 nextPollTime: Date.now() |
| 1359 }; | 1467 }; |
| 1360 | 1468 |
| 1361 chrome.storage.local.set({ | 1469 chrome.storage.local.set({ |
| 1362 lastPollNowPayloads: items.lastPollNowPayloads, | 1470 lastPollNowPayloads: items.lastPollNowPayloads, |
| 1363 notificationGroups: items.notificationGroups | 1471 notificationGroups: items.notificationGroups |
| 1364 }); | 1472 }); |
| 1365 | 1473 |
| 1366 pollOptedInWithRecheck(); | 1474 pollOptedInWithRecheck(); |
| 1367 } | 1475 } |
| 1368 }); | 1476 }); |
| 1369 }); | 1477 }); |
| 1370 } | 1478 } |
| 1371 }); | 1479 }); |
| OLD | NEW |