feat: 🎸 Update how we store tokens in indexed-db (#2223)

Added logic to make the list tokens stored in indexed DB more granular.
I get a hash of the query parameters that will get sent to the controller
and use that as the id for the token. This also means extra logic for when
a token is invalid as we will need to delete all tokens of the same
resource type.
This commit is contained in:
Zhihe Li committed 2024-04-12 14:55:34 -04:00
1 parent 6c59baa413
commit 5d32d987bc
13 files changed
+121 -33

No files matched your search

+1 -1
View File
@@ -283,7 +283,7 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
const baseError = {
isInvalid: true,
status,
code: payload.code,
code: payload.kind,
detail: payload.message,
source: { pointer: '/data' },
};
+31 -23
View File
@@ -3,6 +3,7 @@ import { inject as service } from '@ember/service';
import { getOwner, setOwner } from '@ember/application';
import { queryIndexedDb } from '../utils/indexed-db-query';
import { paginateResults } from '../utils/paginate-results';
import { hashCode } from '../utils/hash-code';
/**
* Handler to sit in front of the API request layer
@@ -34,9 +35,10 @@ export default class IndexedDbHandler {
}
let { page, pageSize, query: queryObj, ...remainingQuery } = query;
const tokenKey = `${type}-${hashCode(remainingQuery)}`;
if (!peekIndexedDB) {
const listToken = await indexedDb.token.get(type);
const listToken = await indexedDb.token.get(tokenKey);
const adapter = store.adapterFor(type);
const schema = store.modelFor(type);
@@ -44,35 +46,41 @@ export default class IndexedDbHandler {
try {
payload = await adapter.query(store, schema, {
...remainingQuery,
scope_id: 'global',
recursive: true,
list_token: listToken?.token,
});
} catch (e) {
// If we get an invalid list token, we'll delete the token and
// clear the DB and try again without a token.
if (
e?.errors[0].status === 400 &&
e?.errors[0].code === 'invalid list token'
) {
await indexedDb[type].clear();
// Delete all tokens of the same resource type so we don't keep clearing the DB.
// Even if other tokens may still be valid, we might have cleared data that they
// depended on so we should clear all tokens of the same type when clearing the DB.
const tokenKeys = await indexedDb.token
.where(':id')
.startsWith(type)
.primaryKeys();
await indexedDb.token.bulkDelete(tokenKeys);
payload = await adapter.query(store, schema, {
...remainingQuery,
});
} catch (e) {
// If we get any error that's not a rate limiting error,
// assume the token is not good anymore. We'll delete the token and
// clear the DB and try again without a token.
// TODO: Probably should check for 400 and bad token error specifically instead?
if (e?.errors[0].status !== 429) {
await indexedDb[type].clear();
await indexedDb.token.delete(type);
payload = await adapter.query(store, schema, {
...remainingQuery,
scope_id: 'global',
recursive: true,
list_token: null,
});
} else {
throw e;
}
} else {
throw e;
}
}
// Store the token we just got back from the payload
// Store the token we just got back from the payload if it exists
if (payload.list_token) {
await indexedDb.token.put({
id: type,
id: tokenKey,
token: payload.list_token,
});
}
// Remove any records from the DB if the API indicates they've been deleted
if (payload.removed_ids?.length > 0) {
+24
View File
@@ -0,0 +1,24 @@
/**
* Takes an object and gets the JSON stringified version of it to be converted
* to a hash code. Taken from https://stackoverflow.com/a/8076436
* @param obj
*/
export const hashCode = (obj) => {
if (!obj) {
return 0;
}
// Use a replacer function to sort the keys of the object so we get the same hashcode
// regardless of the order of the keys.
const jsonString = JSON.stringify(obj, Object.keys(obj).sort());
let hash = 0;
for (let i = 0; i < jsonString.length; i++) {
let code = jsonString.charCodeAt(i);
hash = (hash << 5) - hash + code;
hash = hash & hash; // Convert to 32bit integer
}
// Make sure the hash is positive since we're using it as an ID
return Math.abs(hash);
};