Skip to content

Various auth fixes #1141

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 28 additions & 11 deletions packages/auth/src/exports_lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,26 +114,43 @@ fireauth.exportlib.exportPrototypeProperties = function(protObj, propMap) {
if (unobfuscatedPropName === obfuscatedPropName) {
continue;
}
/**
* @this {!Object}
* @param {string} obfuscatedPropName The obfuscated property name.
* @return {*} The value of the property.
*/
var getter = function(obfuscatedPropName) {
return this[obfuscatedPropName];
};
/**
* @this {!Object}
* @param {string} unobfuscatedPropName The unobfuscated property name.
* @param {string} obfuscatedPropName The obfuscated property name.
* @param {!fireauth.args.Argument} expectedArg The expected argument to the
* setter of this property.
* @param {*} value The new value of the property.
*/
var setter = function(unobfuscatedPropName, obfuscatedPropName,
expectedArg, value) {
// Validate the argument before setting it.
fireauth.args.validate(
unobfuscatedPropName, [expectedArg], [value], true);
this[obfuscatedPropName] = value;
};
// Get the expected argument.
var expectedArg = propMap[obfuscatedPropName].arg;
Object.defineProperty(protObj, unobfuscatedPropName, {
/**
* @this {!Object}
* @return {string} The value of the property.
* @return {*} The value of the property.
*/
get: function() {
return this[obfuscatedPropName];
},
get: goog.partial(getter, obfuscatedPropName),
/**
* @this {!Object}
* @param {string} value The new value of the property.
* @param {*} value The new value of the property.
*/
set: function(value) {
// Validate the argument before setting it.
fireauth.args.validate(
unobfuscatedPropName, [expectedArg], [value], true);
this[obfuscatedPropName] = value;
},
set: goog.partial(setter, unobfuscatedPropName, obfuscatedPropName,
expectedArg),
enumerable: true
});
}
Expand Down
7 changes: 5 additions & 2 deletions packages/auth/src/messagechannel/defines.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ goog.provide('fireauth.messagechannel.utils');
* @enum {string}
*/
fireauth.messagechannel.Error = {
CONNECTION_CLOSED: 'connection_closed',
CONNECTION_UNAVAILABLE: 'connection_unavailable',
INVALID_RESPONSE: 'invalid_response',
TIMEOUT: 'timeout',
Expand All @@ -53,8 +54,10 @@ fireauth.messagechannel.Status = {
* @enum {number}
*/
fireauth.messagechannel.TimeoutDuration = {
ACK: 20,
COMPLETION: 500
ACK: 50,
COMPLETION: 3000,
// Used when a handler is confirmed to be available on the other side.
LONG_ACK: 800
};


Expand Down
10 changes: 5 additions & 5 deletions packages/auth/src/messagechannel/receiver.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ fireauth.messagechannel.Receiver = function(eventTarget) {
this.eventTarget_ = eventTarget;
/**
* @const @private {!Object.<string,
* !Array<function(string, *):!goog.Promise<*>|void>>}
* !Array<function(string, *):!goog.Promise<?>|void>>}
* This is the event type to handlers hash map. It is used to hold the
* corresponding handlers for specified events.
*/
this.eventHandlers_ = {};
/**
* @const@private {function(!Event)} The internal 'message' event handler used
* to reroute the request to corresponding subscribed handlers.
* @const @private {function(!Event)} The internal 'message' event handler
* used to reroute the request to corresponding subscribed handlers.
*/
this.messageEventHandler_ = goog.bind(this.handleEvent_, this);
};
Expand Down Expand Up @@ -180,7 +180,7 @@ fireauth.messagechannel.Receiver.prototype.handleEvent_ = function(event) {
/**
* Subscribes to events of the specified type.
* @param {string} eventType The event type to listen to.
* @param {function(string, *):!goog.Promise<*>|void} handler The async callback
* @param {function(string, *):!goog.Promise<?>|void} handler The async callback
* function to run when the event is triggered.
*/
fireauth.messagechannel.Receiver.prototype.subscribe =
Expand All @@ -199,7 +199,7 @@ fireauth.messagechannel.Receiver.prototype.subscribe =
* Unsubscribes the specified handler from the specified event. If no handler
* is specified, all handlers are unsubscribed.
* @param {string} eventType The event type to unsubscribe from.
* @param {?function(string, *):!goog.Promise<*>|void=} opt_handler The
* @param {?function(string, *):!goog.Promise<?>|void=} opt_handler The
* callback function to unsubscribe from the specified event type. If none
* is specified, all handlers are unsubscribed.
*/
Expand Down
113 changes: 58 additions & 55 deletions packages/auth/src/messagechannel/sender.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ goog.require('goog.Promise');
goog.require('goog.array');


/**
* This is the interface defining a MessageChannel/handler pair.
*
* @typedef {{
* onMessage: function(!Event),
* messageChannel: !MessageChannel
* }}
*/
fireauth.messagechannel.MessageHandler;


/**
* Helper static function to create messageChannel errors.
Expand Down Expand Up @@ -60,17 +70,11 @@ fireauth.messagechannel.Sender = function(postMessager) {
* PostMessager.
*/
this.postMessager_ = postMessager;
/** @private {boolean} Whether the connection was closed. */
this.closed_ = false;
/**
* @private {?MessageChannel} The messageChannel reference if
* supported.
*/
this.messageChannel_ =
fireauth.messagechannel.utils.initializeMessageChannel();
/** @private {boolean} Whether the connection was started. */
this.started_ = false;
/**
* @const @private {!Array<function(!Event)>} The list of subscribed message
* handlers.
* @const @private {!Array<!fireauth.messagechannel.MessageHandler>} The list
* of subscribed message handlers and their corresponding MessageChannels.
*/
this.messageHandlers_ = [];
};
Expand All @@ -94,24 +98,38 @@ fireauth.messagechannel.Sender = function(postMessager) {
* @param {string} eventType The event type identifying the message. This is
* used to help the receiver handle this message.
* @param {?Object=} opt_data The optional data to send along the message.
* @param {?boolean=} opt_useLongTimeout Whether long timeout should be used
* for ACK responses.
* @return {!goog.Promise<!Array<{fulfilled: boolean,
* value: (*|undefined),
* reason: (*|undefined)}>>} A promise that
* resolves with the receiver responses.
*/
fireauth.messagechannel.Sender.prototype.send = function(eventType, opt_data) {
fireauth.messagechannel.Sender.prototype.send = function(
eventType, opt_data, opt_useLongTimeout) {
var self = this;
var eventId;
var data = opt_data || {};
var onMessage;
var ackTimer;
var completionTimer;
var entry = null;
if (this.closed_) {
return goog.Promise.reject(fireauth.messagechannel.createError_(
fireauth.messagechannel.Error.CONNECTION_UNAVAILABLE));
}
var ackTimeout =
!!opt_useLongTimeout ?
fireauth.messagechannel.TimeoutDuration.LONG_ACK :
fireauth.messagechannel.TimeoutDuration.ACK;
var messageChannel =
fireauth.messagechannel.utils.initializeMessageChannel();
return new goog.Promise(function(resolve, reject) {
// Send message along with port for reply
if (self.messageChannel_) {
if (messageChannel) {
eventId = fireauth.messagechannel.utils.generateEventId();
// Start the connection if not already started.
self.start();
messageChannel.port1.start();
// Handler for receiving message reply from receiver.
// Blocks promise resolution until service worker detects the change.
ackTimer = setTimeout(function() {
Expand All @@ -121,7 +139,7 @@ fireauth.messagechannel.Sender.prototype.send = function(eventType, opt_data) {
// Timeout after some time.
reject(fireauth.messagechannel.createError_(
fireauth.messagechannel.Error.UNSUPPORTED_EVENT));
}, fireauth.messagechannel.TimeoutDuration.ACK);
}, ackTimeout);
onMessage = function(event) {
// Process only the expected events that match current event ID.
if (event.data['eventId'] !== eventId) {
Expand Down Expand Up @@ -153,8 +171,12 @@ fireauth.messagechannel.Sender.prototype.send = function(eventType, opt_data) {
fireauth.messagechannel.Error.INVALID_RESPONSE));
}
};
self.messageHandlers_.push(onMessage);
self.messageChannel_.port1.addEventListener('message', onMessage);
entry = {
'messageChannel': messageChannel,
'onMessage': onMessage
};
self.messageHandlers_.push(entry);
messageChannel.port1.addEventListener('message', onMessage);
var request = {
'eventType': eventType,
'eventId': eventId,
Expand All @@ -165,7 +187,7 @@ fireauth.messagechannel.Sender.prototype.send = function(eventType, opt_data) {
// receiver or using an outdated version.
self.postMessager_.postMessage(
request,
[self.messageChannel_.port2]);
[messageChannel.port2]);
} else {
// No connection available.
reject(fireauth.messagechannel.createError_(
Expand All @@ -174,64 +196,45 @@ fireauth.messagechannel.Sender.prototype.send = function(eventType, opt_data) {
}).then(function(result) {
// On completion, remove the message handler. A new one is needed for a
// new message.
self.removeMessageHandler_(onMessage);
self.removeMessageHandler_(entry);
return result;
}).thenCatch(function(error) {
// On failure, remove the message handler. A new one is needed for a new
// message.
self.removeMessageHandler_(onMessage);
self.removeMessageHandler_(entry);
throw error;
});
};


/**
* @param {function(!Event)} onMessage The message handler to remove.
* Removes the onMessage handler for the specified messageChannel.
* @param {?fireauth.messagechannel.MessageHandler} messageHandler
* @private
*/
fireauth.messagechannel.Sender.prototype.removeMessageHandler_ =
function(onMessage) {
if (this.messageChannel_) {
goog.array.removeAllIf(this.messageHandlers_, function(ele) {
return ele == onMessage;
});
this.messageChannel_.port1.removeEventListener('message', onMessage);
function(messageHandler) {
if (!messageHandler) {
return;
}
};


/**
* Removing all subscribed message handlers.
* @private
*/
fireauth.messagechannel.Sender.prototype.removeAllMessageHandlers_ =
function() {
while (this.messageHandlers_.length > 0) {
var onMessage = this.messageHandlers_.pop();
this.messageChannel_.port1.removeEventListener('message', onMessage);
var messageChannel = messageHandler['messageChannel'];
var onMessage = messageHandler['onMessage'];
if (messageChannel) {
messageChannel.port1.removeEventListener('message', onMessage);
messageChannel.port1.close();
}
goog.array.removeAllIf(this.messageHandlers_, function(ele) {
return ele == messageHandler;
});
};


/** Closes the underlying MessageChannel connection. */
fireauth.messagechannel.Sender.prototype.close = function() {
if (this.messageChannel_) {
// Any pending event will timeout.
this.removeAllMessageHandlers_();
// No new messages will be returned.
this.messageChannel_.port1.close();
this.messageChannel_ = null;
this.started_ = false;
// Any pending event will timeout.
while (this.messageHandlers_.length > 0) {
this.removeMessageHandler_(this.messageHandlers_[0]);
}
this.closed_ = true;
};


/** Starts the underlying MessageChannel connection if not already started. */
fireauth.messagechannel.Sender.prototype.start = function() {
// Note that re-connection is not supported. If a connection is closed, a new
// sender has to be created.
if (this.messageChannel_ && !this.started_) {
this.messageChannel_.port1.start();
this.started_ = true;
}
};
58 changes: 52 additions & 6 deletions packages/auth/src/storage/indexeddb.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ fireauth.storage.IndexedDB = function(
* running from a serviceworker.
*/
this.receiver_ = null;
/**
* @private {?fireauth.messagechannel.Sender} The messageChannel sender to
* send keyChanged messages to the service worker from the client.
*/
this.sender_ = null;
/**
* @private {boolean} Whether the service worker has a receiver for the
* keyChanged events.
*/
this.serviceWorkerReceiverAvailable_ = false;
/** @private {?ServiceWorker} The current active service worker. */
this.activeServiceWorker_ = null;
var scope = this;
if (fireauth.util.getWorkerGlobalScope()) {
this.receiver_ = fireauth.messagechannel.Receiver.getInstance(
Expand All @@ -128,6 +140,34 @@ fireauth.storage.IndexedDB = function(
};
});
});
// Used to inform sender that service worker what events it supports.
this.receiver_.subscribe('ping', function(origin, request) {
return goog.Promise.resolve(['keyChanged']);
});
} else {
// Get active service worker when its available.
fireauth.util.getActiveServiceWorker()
.then(function(sw) {
scope.activeServiceWorker_ = sw;
if (sw) {
// Initialize the sender.
scope.sender_ = new fireauth.messagechannel.Sender(
new fireauth.messagechannel.WorkerClientPostMessager(sw));
// Ping the service worker to check what events they can handle.
// Use long timeout.
scope.sender_.send('ping', null, true)
.then(function(results) {
// Check if keyChanged is supported.
if (results[0]['fulfilled'] &&
goog.array.contains(results[0]['value'], 'keyChanged')) {
scope.serviceWorkerReceiverAvailable_ = true;
}
})
.thenCatch(function(error) {
// Ignore error.
});
}
});
}
};

Expand Down Expand Up @@ -413,12 +453,18 @@ fireauth.storage.IndexedDB.prototype.set = function(key, value) {
* @private
*/
fireauth.storage.IndexedDB.prototype.notifySW_ = function(key) {
if (fireauth.util.getServiceWorkerController()) {
var sender = new fireauth.messagechannel.Sender(
new fireauth.messagechannel.WorkerClientPostMessager(
/** @type {!ServiceWorker} */ (
fireauth.util.getServiceWorkerController())));
return sender.send('keyChanged', {'key': key})
// If sender is available.
// Run some sanity check to confirm no sw change occurred.
// For now, we support one service worker per page.
if (this.sender_ &&
this.activeServiceWorker_ &&
fireauth.util.getServiceWorkerController() ===
this.activeServiceWorker_) {
return this.sender_.send(
'keyChanged',
{'key': key},
// Use long timeout if receiver is known to be available.
this.serviceWorkerReceiverAvailable_)
.then(function(responses) {
// Return nothing.
})
Expand Down
Loading