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 || {};
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().
17 * @param {!string} url
18 * @param {!StorageArea} storage
19 * @param {!string} storageKey
22 remoting.XhrEventWriter = function(url, storage, storageKey) {
26 this.storage_ = storage;
27 /** @private @const */
28 this.storageKey_ = storageKey;
30 this.pendingRequests_ = new Map();
31 /** @private {base.Deferred} */
32 this.pendingFlush_ = null;
36 * @return {Promise} A promise that resolves when initialization is completed.
38 remoting.XhrEventWriter.prototype.loadPendingRequests = function() {
40 var deferred = new base.Deferred();
41 this.storage_.get(this.storageKey_, function(entry) {
43 that.pendingRequests_ = new Map(entry[that.storageKey_]);
45 that.pendingRequests_ = new Map();
47 deferred.resolve(that.pendingRequests_);
49 return deferred.promise();
53 * @param {Object} event The event to be written to the server.
54 * @return {Promise} A promise that resolves on success.
56 remoting.XhrEventWriter.prototype.write = function(event) {
57 console.log('Writing Event - ' + JSON.stringify(event));
58 this.markPending_(event);
63 * @return {Promise} A promise that resolves on success.
65 remoting.XhrEventWriter.prototype.flush = function() {
66 if (!this.pendingFlush_) {
68 this.pendingFlush_ = new base.Deferred();
70 var onFailure = function(/** * */e) {
71 that.pendingFlush_.reject(e);
72 that.pendingFlush_ = null;
75 var flushAll = function() {
76 if (that.pendingRequests_.size > 0) {
77 that.doFlush_().then(flushAll, onFailure);
79 that.pendingFlush_.resolve();
80 that.pendingFlush_ = null;
84 // Ensures that |this.pendingFlush_| won't be set to null
85 // in the same stack frame.
86 Promise.resolve().then(flushAll);
89 return this.pendingFlush_.promise();
93 * @return {Promise} A promise that resolves on success.
96 remoting.XhrEventWriter.prototype.doFlush_ = function() {
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);
107 return this.doXhr_(requestIds, {'event': payLoad});
111 * @return {Promise} A promise that resolves when the pending requests are
114 remoting.XhrEventWriter.prototype.writeToStorage = function() {
115 var deferred = new base.Deferred();
117 this.pendingRequests_.forEach(
118 function(/** Object */ request, /** string */ id) {
119 map.push([id, request]);
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
134 remoting.XhrEventWriter.prototype.doXhr_ = function(requestIds, event) {
136 var XHR_RETRY_ATTEMPTS = 20;
137 var xhr = new remoting.AutoRetryXhr(
138 {method: 'POST', url: this.url_, jsonContent: event, useIdentity: true},
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);
149 if (!error.isNone()) {
156 * @param {number} length
157 * @return {string} A random string of length |length|
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
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);