Skip to content

Commit 8c2bbc3

Browse files
authored
Make IdbManager only handle values of type InstallationEntry (#2245)
1 parent 83aacd8 commit 8c2bbc3

File tree

6 files changed

+112
-146
lines changed

6 files changed

+112
-146
lines changed

packages/installations/src/functions/delete-installation.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,13 @@ import { FirebaseApp } from '@firebase/app-types';
1919
import { deleteInstallation as deleteInstallationRequest } from '../api/delete-installation';
2020
import { extractAppConfig } from '../helpers/extract-app-config';
2121
import { remove, update } from '../helpers/idb-manager';
22-
import {
23-
InProgressInstallationEntry,
24-
InstallationEntry,
25-
RegisteredInstallationEntry,
26-
RequestStatus
27-
} from '../interfaces/installation-entry';
22+
import { RequestStatus } from '../interfaces/installation-entry';
2823
import { ERROR_FACTORY, ErrorCode } from '../util/errors';
2924

3025
export async function deleteInstallation(app: FirebaseApp): Promise<void> {
3126
const appConfig = extractAppConfig(app);
3227

33-
const entry = await update(appConfig, (oldEntry?: InstallationEntry):
34-
| InProgressInstallationEntry
35-
| RegisteredInstallationEntry
36-
| undefined => {
28+
const entry = await update(appConfig, oldEntry => {
3729
if (oldEntry && oldEntry.registrationStatus === RequestStatus.NOT_STARTED) {
3830
// Delete the unregistered entry without sending a deleteInstallation request.
3931
return undefined;

packages/installations/src/helpers/get-installation-entry.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ describe('getInstallationEntry', () => {
107107
clock.next(); // Finish registration request.
108108
await expect(registrationPromise).to.be.fulfilled;
109109

110-
const newDbEntry = (await get(appConfig)) as RegisteredInstallationEntry;
111-
expect(newDbEntry.registrationStatus).to.equal(RequestStatus.COMPLETED);
110+
const newDbEntry = await get(appConfig);
111+
expect(newDbEntry!.registrationStatus).to.equal(RequestStatus.COMPLETED);
112112
});
113113

114114
it('saves the InstallationEntry in the database when registration fails', async () => {
@@ -137,8 +137,8 @@ describe('getInstallationEntry', () => {
137137
clock.next(); // Finish registration request.
138138
await expect(registrationPromise).to.be.rejected;
139139

140-
const newDbEntry = (await get(appConfig)) as UnregisteredInstallationEntry;
141-
expect(newDbEntry.registrationStatus).to.equal(RequestStatus.NOT_STARTED);
140+
const newDbEntry = await get(appConfig);
141+
expect(newDbEntry!.registrationStatus).to.equal(RequestStatus.NOT_STARTED);
142142
});
143143

144144
it('removes the InstallationEntry from the database when registration fails with 409', async () => {

packages/installations/src/helpers/get-installation-entry.ts

Lines changed: 32 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,15 @@ export async function getInstallationEntry(
4343
): Promise<InstallationEntryWithRegistrationPromise> {
4444
let registrationPromise: Promise<RegisteredInstallationEntry> | undefined;
4545

46-
const installationEntry = await update(
47-
appConfig,
48-
(oldEntry?: InstallationEntry): InstallationEntry => {
49-
const installationEntry = updateOrCreateInstallationEntry(oldEntry);
50-
const entryWithPromise = triggerRegistrationIfNecessary(
51-
appConfig,
52-
installationEntry
53-
);
54-
registrationPromise = entryWithPromise.registrationPromise;
55-
return entryWithPromise.installationEntry;
56-
}
57-
);
46+
const installationEntry = await update(appConfig, oldEntry => {
47+
const installationEntry = updateOrCreateInstallationEntry(oldEntry);
48+
const entryWithPromise = triggerRegistrationIfNecessary(
49+
appConfig,
50+
installationEntry
51+
);
52+
registrationPromise = entryWithPromise.registrationPromise;
53+
return entryWithPromise.installationEntry;
54+
});
5855

5956
if (installationEntry.fid === INVALID_FID) {
6057
// FID generation failed. Waiting for the FID from the server.
@@ -67,6 +64,10 @@ export async function getInstallationEntry(
6764
};
6865
}
6966

67+
/**
68+
* Creates a new Installation Entry if one does not exist.
69+
* Also clears timed out pending requests.
70+
*/
7071
function updateOrCreateInstallationEntry(
7172
oldEntry: InstallationEntry | undefined
7273
): InstallationEntry {
@@ -75,19 +76,12 @@ function updateOrCreateInstallationEntry(
7576
registrationStatus: RequestStatus.NOT_STARTED
7677
};
7778

78-
if (hasInstallationRequestTimedOut(entry)) {
79-
return {
80-
fid: entry.fid,
81-
registrationStatus: RequestStatus.NOT_STARTED
82-
};
83-
}
84-
85-
return entry;
79+
return clearTimedOutRequest(entry);
8680
}
8781

8882
/**
89-
* If the Firebase Installation is not registered yet, this will trigger the registration
90-
* and return an InProgressInstallationEntry.
83+
* If the Firebase Installation is not registered yet, this will trigger the
84+
* registration and return an InProgressInstallationEntry.
9185
*/
9286
function triggerRegistrationIfNecessary(
9387
appConfig: AppConfig,
@@ -189,23 +183,23 @@ async function waitUntilFidRegistration(
189183
function updateInstallationRequest(
190184
appConfig: AppConfig
191185
): Promise<InstallationEntry> {
192-
return update(
193-
appConfig,
194-
(oldEntry?: InstallationEntry): InstallationEntry => {
195-
if (!oldEntry) {
196-
throw ERROR_FACTORY.create(ErrorCode.INSTALLATION_NOT_FOUND);
197-
}
198-
199-
if (hasInstallationRequestTimedOut(oldEntry)) {
200-
return {
201-
fid: oldEntry.fid,
202-
registrationStatus: RequestStatus.NOT_STARTED
203-
};
204-
}
205-
206-
return oldEntry;
186+
return update(appConfig, oldEntry => {
187+
if (!oldEntry) {
188+
throw ERROR_FACTORY.create(ErrorCode.INSTALLATION_NOT_FOUND);
207189
}
208-
);
190+
return clearTimedOutRequest(oldEntry);
191+
});
192+
}
193+
194+
function clearTimedOutRequest(entry: InstallationEntry): InstallationEntry {
195+
if (hasInstallationRequestTimedOut(entry)) {
196+
return {
197+
fid: entry.fid,
198+
registrationStatus: RequestStatus.NOT_STARTED
199+
};
200+
}
201+
202+
return entry;
209203
}
210204

211205
function hasInstallationRequestTimedOut(

packages/installations/src/helpers/idb-manager.test.ts

Lines changed: 31 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ import { AppConfig } from '../interfaces/app-config';
2020
import { getFakeAppConfig } from '../testing/get-fake-app';
2121
import '../testing/setup';
2222
import { clear, get, remove, set, update } from './idb-manager';
23+
import {
24+
InstallationEntry,
25+
RequestStatus
26+
} from '../interfaces/installation-entry';
27+
28+
const VALUE_A: InstallationEntry = {
29+
fid: 'VALUE_A',
30+
registrationStatus: RequestStatus.NOT_STARTED
31+
};
32+
const VALUE_B: InstallationEntry = {
33+
fid: 'VALUE_B',
34+
registrationStatus: RequestStatus.NOT_STARTED
35+
};
2336

2437
describe('idb manager', () => {
2538
let appConfig1: AppConfig;
@@ -32,9 +45,9 @@ describe('idb manager', () => {
3245

3346
describe('get / set', () => {
3447
it('sets a value and then gets the same value back', async () => {
35-
await set(appConfig1, 'value');
48+
await set(appConfig1, VALUE_A);
3649
const value = await get(appConfig1);
37-
expect(value).to.equal('value');
50+
expect(value).to.deep.equal(VALUE_A);
3851
});
3952

4053
it('gets undefined for a key that does not exist', async () => {
@@ -43,22 +56,22 @@ describe('idb manager', () => {
4356
});
4457

4558
it('sets and gets multiple values with different keys', async () => {
46-
await set(appConfig1, 'value');
47-
await set(appConfig2, 'value2');
48-
expect(await get(appConfig1)).to.equal('value');
49-
expect(await get(appConfig2)).to.equal('value2');
59+
await set(appConfig1, VALUE_A);
60+
await set(appConfig2, VALUE_B);
61+
expect(await get(appConfig1)).to.deep.equal(VALUE_A);
62+
expect(await get(appConfig2)).to.deep.equal(VALUE_B);
5063
});
5164

5265
it('overwrites a value', async () => {
53-
await set(appConfig1, 'value');
54-
await set(appConfig1, 'newValue');
55-
expect(await get(appConfig1)).to.equal('newValue');
66+
await set(appConfig1, VALUE_A);
67+
await set(appConfig1, VALUE_B);
68+
expect(await get(appConfig1)).to.deep.equal(VALUE_B);
5669
});
5770
});
5871

5972
describe('remove', () => {
6073
it('deletes a key', async () => {
61-
await set(appConfig1, 'value');
74+
await set(appConfig1, VALUE_A);
6275
await remove(appConfig1);
6376
expect(await get(appConfig1)).to.be.undefined;
6477
});
@@ -71,8 +84,8 @@ describe('idb manager', () => {
7184

7285
describe('clear', () => {
7386
it('deletes all keys', async () => {
74-
await set(appConfig1, 'value');
75-
await set(appConfig2, 'value2');
87+
await set(appConfig1, VALUE_A);
88+
await set(appConfig2, VALUE_B);
7689
await clear();
7790
expect(await get(appConfig1)).to.be.undefined;
7891
expect(await get(appConfig2)).to.be.undefined;
@@ -83,53 +96,27 @@ describe('idb manager', () => {
8396
it('gets and sets a value atomically, returns the new value', async () => {
8497
let isGetCalled = false;
8598

86-
await set(appConfig1, 'value');
99+
await set(appConfig1, VALUE_A);
87100

88-
const resultPromise = update<string, string>(appConfig1, oldValue => {
101+
const resultPromise = update(appConfig1, oldValue => {
89102
// get is already called for the same key, but it will only complete
90103
// after update transaction finishes, at which point it will return the
91104
// new value.
92105
expect(isGetCalled).to.be.true;
93106

94-
expect(oldValue).to.equal('value');
95-
return 'newValue';
107+
expect(oldValue).to.deep.equal(VALUE_A);
108+
return VALUE_B;
96109
});
97110

98111
// Called immediately after update, but before update completed.
99112
const getPromise = get(appConfig1);
100113
isGetCalled = true;
101114

102115
// Update returns the new value
103-
expect(await resultPromise).to.equal('newValue');
116+
expect(await resultPromise).to.deep.equal(VALUE_B);
104117

105118
// If update weren't atomic, this would return the old value.
106-
expect(await getPromise).to.equal('newValue');
107-
});
108-
109-
it('can change the type of the value', async () => {
110-
let isGetCalled = false;
111-
112-
await set(appConfig1, 'value');
113-
114-
const resultPromise = update<string, number>(appConfig1, oldValue => {
115-
// get is already called for the same key, but it will only complete
116-
// after update transaction finishes, at which point it will return the
117-
// new value.
118-
expect(isGetCalled).to.be.true;
119-
120-
expect(oldValue).to.equal('value');
121-
return 123;
122-
});
123-
124-
// Called immediately after update, but before update completed.
125-
const getPromise = get(appConfig1);
126-
isGetCalled = true;
127-
128-
// Update returns the new value
129-
expect(await resultPromise).to.equal(123);
130-
131-
// If update weren't atomic, this would return the old value.
132-
expect(await getPromise).to.equal(123);
119+
expect(await getPromise).to.deep.equal(VALUE_B);
133120
});
134121
});
135122
});

packages/installations/src/helpers/idb-manager.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import { DB, openDb } from 'idb';
1919
import { AppConfig } from '../interfaces/app-config';
20+
import { InstallationEntry } from '../interfaces/installation-entry';
2021

2122
const DATABASE_NAME = 'firebase-installations-database';
2223
const DATABASE_VERSION = 1;
@@ -41,7 +42,9 @@ function getDbPromise(): Promise<DB> {
4142
}
4243

4344
/** Gets record(s) from the objectStore that match the given key. */
44-
export async function get(appConfig: AppConfig): Promise<unknown> {
45+
export async function get(
46+
appConfig: AppConfig
47+
): Promise<InstallationEntry | undefined> {
4548
const key = getKey(appConfig);
4649
const db = await getDbPromise();
4750
return db
@@ -51,7 +54,7 @@ export async function get(appConfig: AppConfig): Promise<unknown> {
5154
}
5255

5356
/** Assigns or overwrites the record for the given key with the given value. */
54-
export async function set<ValueType>(
57+
export async function set<ValueType extends InstallationEntry>(
5558
appConfig: AppConfig,
5659
value: ValueType
5760
): Promise<ValueType> {
@@ -78,21 +81,17 @@ export async function remove(appConfig: AppConfig): Promise<void> {
7881
* deleted instead.
7982
* @return Updated value
8083
*/
81-
export async function update<OldType, NewType>(
84+
export async function update<ValueType extends InstallationEntry | undefined>(
8285
appConfig: AppConfig,
83-
updateFn: (previousValue: OldType | undefined) => NewType
84-
): Promise<NewType> {
86+
updateFn: (previousValue: InstallationEntry | undefined) => ValueType
87+
): Promise<ValueType> {
8588
const key = getKey(appConfig);
8689
const db = await getDbPromise();
8790
const tx = db.transaction(OBJECT_STORE_NAME, 'readwrite');
8891
const store = tx.objectStore(OBJECT_STORE_NAME);
89-
const oldValue = await store.get(key);
92+
const oldValue: InstallationEntry | undefined = await store.get(key);
9093
const newValue = updateFn(oldValue);
9194

92-
if (newValue === oldValue) {
93-
return newValue;
94-
}
95-
9695
if (newValue === undefined) {
9796
await store.delete(key);
9897
} else {

0 commit comments

Comments
 (0)