Skip to content

Create a token manager library for MapBox #23511

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 29 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from 14 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 @@ -235,4 +235,7 @@ 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',
};
89 changes: 89 additions & 0 deletions src/libs/actions/MapboxTokens.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import _ from 'underscore';
import moment from 'moment';
import Onyx from 'react-native-onyx';
import {AppState} from 'react-native';
import ONYXKEYS from '../../ONYXKEYS';
import * as API from '../API';
import CONST from '../../CONST';

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

const refreshToken = () => {
console.debug('[MapboxTokens] refreshing token every 25 minutes', refreshInterval);

// 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(() => {
console.debug('[MapboxTokens] Fetching a new token after waiting 25 minutes');
API.read('GetMapboxAccessToken');
}, refreshInterval);
};

const hasTokenExpired = () => {
const now = moment();
const expiration = moment(currentToken.expiration);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will most likely not happen but there might be case when AppState change callback is called before Onyx connect callback, which will cause crash because of undefined currentToken.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, you're right. I'll add a check for that.

Updated!

const minutesUntilTokenExpires = expiration.diff(now, 'minutes');
return minutesUntilTokenExpires < 0;
};

const clearToken = () => {
console.debug('[MapboxTokens] 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(`[MapboxTokens] 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) => {
// token is an object with. If it 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 (!token || _.size(token) === 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!token || _.size(token) === 0) {
if (!token || _.isEmpty(token)) {

console.debug('[MapboxTokens] 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('[MapboxTokens] Token has expired after reading from Onyx');
clearToken();
return;
}

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

// When the app becomes active (eg. after being in the background), check if the token has expired.
AppState.addEventListener('change', (nextAppState) => {
if (nextAppState !== CONST.APP_STATE.ACTIVE || !hasTokenExpired()) {
return;
}
console.debug('[MapboxTokens] Token is expired after app became active');
clearToken();
});
};

export default init;