Roll src/third_party/WebKit 3529d49:06e8485 (svn 202554:202555)
[chromium-blink-merge.git] / remoting / webapp / base / js / xhr_event_writer.js
blobd4acdb980a10b9e0ba99df7627edf3df92221345
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /** @suppress {duplicate} */
6 var remoting = remoting || {};
8 (function() {
10 'use strict';
12 /**
13  * A class that writes log events to our back-end using XHR.
14  * If a log request fails due to network failure, it will be stored to
15  * |storage| for retrying in the future by calling flush().
16  *
17  * @param {!string} url
18  * @param {!StorageArea} storage
19  * @param {!string} storageKey
20  * @constructor
21  */
22 remoting.XhrEventWriter = function(url, storage, storageKey) {
23   /** @private */
24   this.url_ = url;
25   /** @private */
26   this.storage_ = storage;
27   /** @private @const */
28   this.storageKey_ = storageKey;
29   /** @private */
30   this.pendingRequests_ = new Map();
31   /** @private {base.Deferred} */
32   this.pendingFlush_ = null;
35 /**
36  * @return {Promise} A promise that resolves when initialization is completed.
37  */
38 remoting.XhrEventWriter.prototype.loadPendingRequests = function() {
39   var that = this;
40   var deferred = new base.Deferred();
41   this.storage_.get(this.storageKey_, function(entry) {
42     try {
43       that.pendingRequests_ = new Map(entry[that.storageKey_]);
44     } catch(e) {
45       that.pendingRequests_ = new Map();
46     }
47     deferred.resolve(that.pendingRequests_);
48   });
49   return deferred.promise();
52 /**
53  * @param {Object} event  The event to be written to the server.
54  * @return {Promise} A promise that resolves on success.
55  */
56 remoting.XhrEventWriter.prototype.write = function(event) {
57   console.log('Writing Event - ' + JSON.stringify(event));
58   this.markPending_(event);
59   return this.flush();
62 /**
63  * @return {Promise} A promise that resolves on success.
64  */
65 remoting.XhrEventWriter.prototype.flush = function() {
66   if (!this.pendingFlush_) {
67     var that = this;
68     this.pendingFlush_ = new base.Deferred();
70     var onFailure = function(/** * */e) {
71       that.pendingFlush_.reject(e);
72       that.pendingFlush_ = null;
73     };
75     var flushAll = function() {
76       if (that.pendingRequests_.size > 0) {
77         that.doFlush_().then(flushAll, onFailure);
78       } else {
79         that.pendingFlush_.resolve();
80         that.pendingFlush_ = null;
81       }
82     };
84     // Ensures that |this.pendingFlush_| won't be set to null
85     // in the same stack frame.
86     Promise.resolve().then(flushAll);
87   }
89   return this.pendingFlush_.promise();
92 /**
93  * @return {Promise} A promise that resolves on success.
94  * @private
95  */
96 remoting.XhrEventWriter.prototype.doFlush_ = function() {
97   var payLoad = [];
98   var requestIds = [];
100   // Map.forEach enumerates the entires of the map in insertion order.
101   this.pendingRequests_.forEach(
102     function(/** Object */ event, /** string */ requestId) {
103       requestIds.push(requestId);
104       payLoad.push(event);
105     });
107   return this.doXhr_(requestIds, {'event': payLoad});
111  * @return {Promise} A promise that resolves when the pending requests are
112  *     written to disk.
113  */
114 remoting.XhrEventWriter.prototype.writeToStorage = function() {
115   var deferred = new base.Deferred();
116   var map = [];
117   this.pendingRequests_.forEach(
118     function(/** Object */ request, /** string */ id) {
119       map.push([id, request]);
120     });
122   var entry = {};
123   entry[this.storageKey_] = map;
124   this.storage_.set(entry, deferred.resolve.bind(deferred));
125   return deferred.promise();
129  * @param {Array<string>} requestIds
130  * @param {Object} event
131  * @return {Promise}
132  * @private
133  */
134 remoting.XhrEventWriter.prototype.doXhr_ = function(requestIds, event) {
135   var that = this;
136   var XHR_RETRY_ATTEMPTS = 20;
137   var xhr = new remoting.AutoRetryXhr(
138       {method: 'POST', url: this.url_, jsonContent: event, useIdentity: true},
139       XHR_RETRY_ATTEMPTS);
140   return xhr.start().then(function(response) {
141     var error = remoting.Error.fromHttpStatus(response.status);
142     // Only store requests that are failed with NETWORK_FAILURE, so that
143     // malformed requests won't be stuck in the client forever.
144     if (!error.hasTag(remoting.Error.Tag.NETWORK_FAILURE)) {
145       requestIds.forEach(function(/** string */ requestId) {
146         that.pendingRequests_.delete(requestId);
147       });
148     }
149     if (!error.isNone()) {
150       throw error;
151     }
152   });
156  * @param {number} length
157  * @return {string} A random string of length |length|
158  */
159 function randomString(length) {
160   var random = new Uint8Array(length);
161   window.crypto.getRandomValues(random);
162   return window.btoa(String.fromCharCode.apply(null, random));
166  * @param {Object} event
167  * @private
168  */
169 remoting.XhrEventWriter.prototype.markPending_ = function(event) {
170   var requestId = Date.now() + '_' + randomString(16);
171   console.assert(!this.pendingRequests_.has(requestId),
172                  'There is already an event with id ' + requestId + '.');
173   this.pendingRequests_.set(requestId, event);
176 })();