Skip to content
Draft
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
8 changes: 7 additions & 1 deletion lib/ddp.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ class DDP extends EventEmitter {
// Default `autoConnect` and `autoReconnect` to true
this.autoConnect = options.autoConnect !== false;
this.autoReconnect = options.autoReconnect !== false;
// save the initial value of autoReconnect to be able to reset it
this._autoReconnectInitital = options.autoReconnect !== false;
this.reconnectInterval =
options.reconnectInterval || DEFAULT_RECONNECT_INTERVAL;

Expand Down Expand Up @@ -203,7 +205,7 @@ class DDP extends EventEmitter {
}

/**
* Emits a new event.
* Emits a new event. Wraps emitting in a setTimeout (macrotask)
* @override
*/
emit(...args) {
Expand All @@ -213,8 +215,12 @@ class DDP extends EventEmitter {

/**
* Initiates the underlying websocket to open the connection
* If the connection was closed before eg. by calling `disconnect`
* from the client side,
* the value of `autoReconnect` will be reset to the initial value.
*/
connect() {
this.autoReconnect = this._autoReconnectInitital;
this.socket.open();
}

Expand Down
15 changes: 14 additions & 1 deletion src/Meteor.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,15 @@ const Meteor = {
}
}

if (!options.AsyncTokenStorage) {
options.AsyncTokenStorage = options.AsyncStorage;
}

Data._endpoint = endpoint;
Data._options = options;

this._reactiveDict.set('_userReady', false);

const ddp = new DDP({
endpoint: endpoint,
SocketConstructor: WebSocket,
Expand All @@ -153,7 +159,7 @@ const Meteor = {
if (this.isVerbose) {
console.info('Connected to DDP server.');
}
this._loadInitialUser().then(() => {
User._loadInitialUser().then(() => {
this._subscriptionsRestart();
});
this._reactiveDict.set('connected', true);
Expand All @@ -179,6 +185,9 @@ const Meteor = {
sub.readyDeps.changed();
}

// Mark user as not ready
this._reactiveDict.set('_userReady', false);

if (!Data.ddp.autoReconnect) return;

if (!lastDisconnect || new Date() - lastDisconnect > 3000) {
Expand Down Expand Up @@ -310,6 +319,7 @@ const Meteor = {
}

// Reconnect if we lose internet

NetInfo.addEventListener(
({ type, isConnected, isInternetReachable, isWifiEnabled }) => {
if (isConnected && Data.ddp.autoReconnect) {
Expand All @@ -321,7 +331,10 @@ const Meteor = {
console.warn(
'Warning: NetInfo not installed, so DDP will not automatically reconnect'
);
Data.ddp.connect();
}
} else {
Data.ddp.connect();
}
},
subscribe(name) {
Expand Down
113 changes: 90 additions & 23 deletions src/user/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ const User = {
const user = Users.findOne(user_id);
return user && user._id;
},
userReady() {
return Meteor._reactiveDict.get('_userReady');
},
_isLoggingIn: true,
_isLoggingOut: false,
loggingIn() {
Expand All @@ -41,7 +44,7 @@ const User = {
return User._isLoggingOut;
},
logout(callback) {
this._isTokenLogin = false;
User._isTokenLogin = false;
User._startLoggingOut();
Meteor.call('logout', (err) => {
User.handleLogout();
Expand All @@ -51,15 +54,27 @@ const User = {
});
},
handleLogout() {
Data._options.AsyncStorage.removeItem(TOKEN_KEY);
Data._options.AsyncTokenStorage.removeItem(TOKEN_KEY);
Data._tokenIdSaved = null;
Meteor.isVerbose &&
console.info('User.handleLogout()::: userId Saved: null');
this._reactiveDict.set('_userIdSaved', null);

User._userIdSaved = null;
User._endLoggingOut();
},
loginWithCustomHandler(req, callback) {
User._isTokenLogin = false;
User._startLoggingIn();

Meteor.call('login', req, (err, result) => {
User._handleLoginCallback(err, result);

typeof callback == 'function' && callback(err);
});
},
loginWithPassword(selector, password, callback) {
this._isTokenLogin = false;
User._isTokenLogin = false;
if (typeof selector === 'string') {
if (selector.indexOf('@') === -1) selector = { username: selector };
else selector = { email: selector };
Expand All @@ -80,7 +95,7 @@ const User = {
);
},
loginWithPasswordAnd2faCode(selector, password, code, callback) {
this._isTokenLogin = false;
User._isTokenLogin = false;
if (typeof selector === 'string') {
if (selector.indexOf('@') === -1) selector = { username: selector };
else selector = { email: selector };
Expand Down Expand Up @@ -129,6 +144,7 @@ const User = {
Data.notify('loggingOut');
},
_endLoggingIn() {
Meteor._reactiveDict.set('_userReady', true);
this._reactiveDict.set('_loggingIn', false);
Data.notify('loggingIn');
},
Expand All @@ -145,17 +161,25 @@ const User = {
'id:',
result.id
);
Data._options.AsyncStorage.setItem(TOKEN_KEY, result.token);

Data._options.AsyncTokenStorage.setItem(TOKEN_KEY, result.token).catch(
(error) => {
console.warn(
'AsyncStorage error: ' + error.message + ' while saving token'
);
}
);

Data._tokenIdSaved = result.token;
this._reactiveDict.set('_userIdSaved', result.id);
User._userIdSaved = result.id;
User._endLoggingIn();
this._isTokenLogin = false;
User._isTokenLogin = false;
Data.notify('onLogin');
} else {
Meteor.isVerbose &&
console.info('User._handleLoginCallback::: error:', err);
if (this._isTokenLogin) {
if (User._isTokenLogin) {
setTimeout(() => {
if (User._userIdSaved) {
return;
Expand All @@ -164,16 +188,33 @@ const User = {
if (Meteor.user()) {
return;
}
User._loginWithToken(User._userIdSaved);
Meteor.isVerbose &&
console.info(
'User._handleLoginCallback::: trying again to login with ' +
Data._tokenIdSaved +
' after ' +
this._timeout +
'ms.'
);
if (this._timeout > 10000) {
Meteor.isVerbose &&
console.info(
'User._handleLoginCallback::: 10000ms timeout exceeded, ending logging in.'
);
User._endLoggingIn();
Data.notify('onLoginFailure', err);
return;
}
User._loginWithToken(Data._tokenIdSaved);
}, this._timeout);
}
// Signify we aren't logginging in any more after a few seconds
if (this._timeout > 2000) {
} else {
Meteor.isVerbose &&
console.info(
'User._handleLoginCallback::: not token login, ending logging in.'
);
User._endLoggingIn();
Data.notify('onLoginFailure', err);
}
User._endLoggingIn();
// we delegate the error to enable better logging
Data.notify('onLoginFailure', err);
}
Data.notify('change');
},
Expand All @@ -192,11 +233,9 @@ const User = {
}

if (value !== null) {
this._isTokenLogin = true;
User._isTokenLogin = true;
Meteor.isVerbose && console.info('User._loginWithToken::: token:', value);
if (this._isCallingLogin) {
return;
}

this._isCallingLogin = true;
User._startLoggingIn();
Meteor.call('login', { resume: value }, (err, result) => {
Expand All @@ -215,13 +254,30 @@ const User = {
this._loadInitialUser();
}, time + 100);
} else if (err?.error === 403) {
Meteor.isVerbose &&
console.info(
'User._handleLoginCallback::: token login failed, logging out.'
);
Data.notify('onLoginFailure', err);
Data.notify('change');
User.logout();
} else {
Meteor.isVerbose &&
console.info(
'User._handleLoginCallback::: token login error and result:',
err,
result
);
User._handleLoginCallback(err, result);
}
});
} else {
Meteor.isVerbose && console.info('User._loginWithToken::: token is null');
Meteor.isVerbose &&
console.info(
'User._loginWithToken::: token is null, ending logging in.'
);
Data.notify('onLoginFailure', new Error("Token doesn't exist"));
Data.notify('change');
User._endLoggingIn();
}
},
Expand All @@ -234,11 +290,22 @@ const User = {
User._startLoggingIn();
var value = null;
try {
value = await Data._options.AsyncStorage.getItem(TOKEN_KEY);
} catch (error) {
console.warn('AsyncStorage error: ' + error.message);
} finally {
value = await Data._options.AsyncTokenStorage.getItem(TOKEN_KEY).catch(
(error) => {
console.warn(
'AsyncStorage error: ' + error.message + ' while loading token'
);
}
);

User._loginWithToken(value);
} catch (error) {
User._endLoggingIn();
Data.notify('onLoginFailure', err);
Data.notify('change');
console.warn(
'AsyncStorage error: ' + error.message + ' while loading token'
);
}
},
};
Expand Down