Skip to content
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

Create a token manager library for MapBox #23511

Merged
merged 29 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c9b8494
Add Onyx key and initial method
tgolen Jul 24, 2023
b0fed71
Handle expired tokens
tgolen Jul 24, 2023
5449a64
Add token refresh
tgolen Jul 24, 2023
c8bb64b
Add app becomes active handler
tgolen Jul 24, 2023
416b970
Fix expiration calculation, improve comments, document token shape
tgolen Jul 24, 2023
53fd9f9
Move timeout logic into refresh method
tgolen Jul 24, 2023
c53d822
Add API calls
tgolen Jul 24, 2023
2ffe94a
Merge branch 'main' into tgolen-mapbox-tokenmanager
tgolen Jul 28, 2023
1083804
Move file to actions and rename
tgolen Jul 28, 2023
f1dd506
Add imports and debug logs
tgolen Jul 28, 2023
09b09e5
Protect against multiple connections
tgolen Jul 28, 2023
cdcd828
Move refresh interval to a variable
tgolen Jul 28, 2023
957f01c
Move API method inside of timeout
tgolen Jul 28, 2023
4df7fed
Add early return and change variable to const
tgolen Jul 28, 2023
073fbe6
Protect against undefined token
tgolen Jul 28, 2023
49a8a74
Rename file and protect against logged out user
tgolen Jul 31, 2023
d8479d0
Update comments and add another logout check
tgolen Jul 31, 2023
09e4f5c
Rename method
tgolen Jul 31, 2023
e7b930b
Simplify expiration check
tgolen Jul 31, 2023
37c4017
Simplify method even further
tgolen Jul 31, 2023
f807f7b
Fix typo, all caps constant, remove function body
tgolen Jul 31, 2023
646da93
Merge branch 'main' into tgolen-mapbox-tokenmanager
tgolen Aug 1, 2023
9d89624
Add missing import
tgolen Aug 1, 2023
3193c56
Merge branch 'main' into tgolen-mapbox-tokenmanager
thienlnam Aug 4, 2023
e07ede3
Comment update
thienlnam Aug 4, 2023
98336ec
Update src/libs/actions/MapboxToken.js
tgolen Aug 7, 2023
de6f49a
Update src/libs/actions/MapboxToken.js
tgolen Aug 7, 2023
db9bc9f
Merge branch 'main' into tgolen-mapbox-tokenmanager
tgolen Aug 7, 2023
29c6f35
Remove unnecessary whitespace
tgolen Aug 7, 2023
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
3 changes: 3 additions & 0 deletions src/ONYXKEYS.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@ export default {
// Experimental memory only Onyx mode flag
IS_USING_MEMORY_ONLY_KEYS: 'isUsingMemoryOnlyKeys',

// The access token to be used with the Mapbox library
MAPBOX_ACCESS_TOKEN: 'mapboxAccessToken',

ONYX_UPDATES: {
// The ID of the last Onyx update that was applied to this client
LAST_UPDATE_ID: 'onyxUpdatesLastUpdateID',
Expand Down
108 changes: 108 additions & 0 deletions src/libs/actions/MapboxToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import _ from 'underscore';
import moment from 'moment';
import Onyx from 'react-native-onyx';
import {AppState} from 'react-native';
import lodashGet from 'lodash/get';
import ONYXKEYS from '../../ONYXKEYS';
import * as API from '../API';
import CONST from '../../CONST';

let authToken;
Onyx.connect({
key: ONYXKEYS.SESSION,
callback: (val) => {
authToken = lodashGet(val, 'authToken', null);
},
});

let connectionID;
let currentToken;
let refreshTimeoutID;
const REFRESH_INTERVAL = 1000 * 60 * 25;

const setExpirationTimer = () => {
console.debug('[MapboxToken] refreshing token on an interval', REFRESH_INTERVAL, 'ms');

// Cancel any previous timeouts so that there is only one request to get a token at a time.
clearTimeout(refreshTimeoutID);

// Refresh the token every 25 minutes
refreshTimeoutID = setTimeout(() => {
// If the user has logged out while the timer was running, skip doing anything when this callback runs
if (!authToken) {
console.debug('[MapboxToken] Skipping the fetch of a new token because user signed out');
return;
}
console.debug(`[MapboxToken] Fetching a new token after waiting ${REFRESH_INTERVAL / 1000 / 60} minutes`);
API.read('GetMapboxAccessToken');
}, REFRESH_INTERVAL);
};

const hasTokenExpired = () => moment().isAfter(currentToken.expiration);

const clearToken = () => {
console.debug('[MapboxToken] Deleting the token stored in Onyx');

// Use Onyx.set() to delete the key from Onyx, which will trigger a new token to be retrieved from the API.
Onyx.set(ONYXKEYS.MAPBOX_ACCESS_TOKEN, null);
};

const init = () => {
if (connectionID) {
console.debug('[MapboxToken] init() is already listening to Onyx so returning early');
return;
}

// When the token changes in Onyx, the expiration needs to be checked so a new token can be retrieved.
connectionID = Onyx.connect({
key: ONYXKEYS.MAPBOX_ACCESS_TOKEN,
/**
* @param {Object} token
* @param {String} token.token
* @param {String} token.expiration
* @param {String[]} [token.errors]
*/
callback: (token) => {
// If the user has logged out, don't do anything and ignore changes to the access token
if (!authToken) {
console.debug('[MapboxToken] Ignoring changes to token because user signed out');
return;
}

// If the token is falsy or an empty object, the token needs to be retrieved from the API.
// The API sets a token in Onyx with a 30 minute expiration.
if (_.isEmpty(token)) {
console.debug('[MapboxToken] Token does not exist so fetching one');
API.read('GetMapboxAccessToken');
return;
}

// Store the token in a place where the AppState callback can also access it.
currentToken = token;

if (hasTokenExpired()) {
console.debug('[MapboxToken] Token has expired after reading from Onyx');
clearToken();
return;
}

console.debug('[MapboxToken] Token is valid, setting up refresh');
setExpirationTimer();
},
});

AppState.addEventListener('change', (nextAppState) => {
// Skip getting a new token if:
// - The app state is not changing to active
// - There is no current token (which means it's not been fetch yet for the first time)
// - The token hasn't expired yet (this would just be a waste of an API call)
// - There is no authToken (which means the user has logged out)
if (nextAppState !== CONST.APP_STATE.ACTIVE || !currentToken || !hasTokenExpired() || !authToken) {
return;
}
console.debug('[MapboxToken] Token is expired after app became active');
clearToken();
});
};

export default init;