Skip to content

Commit c9ae186

Browse files
committed
Switch to using a map of devices rather than an array
1 parent 69ca480 commit c9ae186

File tree

5 files changed

+108
-48
lines changed

5 files changed

+108
-48
lines changed

README.md

+10-5
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ The structure of this Document is as follows:
5353

5454
```
5555
{
56-
devices: Device[],
56+
devices: {
57+
deviceId1: Device,
58+
deviceId2: Device,
59+
...
60+
},
5761
userId: string,
5862
}
5963
```
@@ -62,11 +66,12 @@ A `Device` object contains the following:
6266

6367
```
6468
{
65-
deviceId: string, // The browser name and version
69+
deviceId: string, // A randomly generated ID
6670
fcmToken: string, // The FCM token
67-
name: 'Unknown', // Web browser's do not provide a name field
71+
name: string, // The browser name
6872
os: string, // The OS of the device
69-
type: 'Web'
73+
type: 'Web',
74+
userAgent: string // The browser user agent string
7075
}
7176
```
7277

@@ -87,7 +92,7 @@ Returns a `DeviceStore`.
8792

8893
Indicate to the DeviceStore that the user is about to sign out, and the current device token should be removed.
8994

90-
This cannot be done automatically with `onAuthStateChanged` as the user won't have permission to remove the token from Firestore as they are already signed out by this point and the Cloud Firestore security rules will prevent the database deletion.
95+
This can't be done automatically with `onAuthStateChanged` as the user is already signed out at this point. This means the Cloud Firestore security rules will prevent the database deletion as they no longer have the correct user permissions to remove the token.
9196

9297
#### `DeviceStore.subscribe(): Promise<void>`
9398

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
},
2323
"homepage": "https://github.com/csfrequency/firebase-device-store-js-sdk#readme",
2424
"dependencies": {
25-
"detect-browser": "^4.5.0"
25+
"detect-browser": "^4.5.0",
26+
"nanoid": "^2.0.2"
2627
},
2728
"devDependencies": {
29+
"@types/nanoid": "^2.0.0",
2830
"firebase": "^5.9.4",
2931
"prettier": "^1.17.0",
3032
"rimraf": "^2.6.3",

src/index.ts

+82-42
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
import { detect } from 'detect-browser';
22
import * as firebase from 'firebase';
3+
import nanoid from 'nanoid';
34

45
type Device = {
56
deviceId: string;
67
fcmToken: string;
78
name: string;
89
os: string;
910
type: 'Android' | 'iOS' | 'Web';
11+
userAgent?: string;
12+
};
13+
14+
type Devices = { [deviceId: string]: Device };
15+
16+
type UserDevices = {
17+
devices: Devices;
18+
userId: string;
1019
};
1120

1221
interface DeviceStore {
@@ -138,20 +147,19 @@ const addToken = (
138147
const doc = await transaction.get(docRef);
139148

140149
if (doc.exists) {
141-
const devices: Device[] = doc.data().devices || [];
142-
// Add the new device if it doesn't already exist
143-
if (!devices.find(device => device.fcmToken === token)) {
144-
devices.push(createDevice(token));
145-
}
150+
const devices = getDevices(doc);
151+
152+
// Check if a device already matches the FCM token, or generate a new one
153+
const deviceId = findDeviceId(devices, token) || generateDeviceId();
154+
// Set the device information
155+
devices[deviceId] = createDevice(deviceId, token);
146156
// Update the document
147157
return transaction.update(docRef, {
148158
devices,
149159
});
150160
} else {
151-
return transaction.set(docRef, {
152-
devices: [createDevice(token)],
153-
userId,
154-
});
161+
const userDevices = createUserDevices(userId, token);
162+
return transaction.set(docRef, userDevices);
155163
}
156164
});
157165
};
@@ -168,21 +176,21 @@ const deleteToken = (
168176
const doc = await transaction.get(docRef);
169177

170178
if (doc.exists) {
171-
const devices: Device[] = doc.data().devices || [];
172-
// Remove the old device
173-
const updatedDevices = devices.filter(
174-
device => device.fcmToken !== token
175-
);
176-
// Update the document
177-
return transaction.update(docRef, {
178-
devices: updatedDevices,
179-
});
180-
} else {
181-
return transaction.set(docRef, {
182-
devices: [],
183-
userId,
184-
});
179+
const devices = getDevices(doc);
180+
181+
// Find the device that matches the FCM token
182+
const deviceId = findDeviceId(devices, token);
183+
184+
// If there is a matching device, remove it and update the document
185+
if (deviceId) {
186+
delete devices[deviceId];
187+
}
188+
return transaction.update(docRef, 'devices', devices);
185189
}
190+
191+
// Firestore requires that every document read in a transaction must also
192+
// be written
193+
return transaction.set(docRef, undefined);
186194
});
187195
};
188196

@@ -199,47 +207,79 @@ const updateToken = (
199207
const doc = await transaction.get(docRef);
200208

201209
if (doc.exists) {
202-
const devices: Device[] = doc.data().devices || [];
203-
let updatedDevices = devices;
204-
// Remove the old device
210+
const devices = getDevices(doc);
211+
const updatedDevices: Devices = {
212+
...devices,
213+
};
214+
215+
// If an old token is specified, find the device that matches the token
205216
if (oldToken) {
206-
updatedDevices = devices.filter(device => device.fcmToken !== oldToken);
217+
const deviceId = findDeviceId(devices, oldToken);
218+
// If there is a matching device, then remove it
219+
if (deviceId) {
220+
delete updatedDevices[deviceId];
221+
}
207222
}
208-
// Add the new device if it doesn't already exist
223+
224+
// If a new token is specified, then add the device
209225
if (newToken) {
210-
if (!updatedDevices.find(device => device.fcmToken === newToken)) {
211-
updatedDevices.push(createDevice(newToken));
212-
}
226+
const deviceId = generateDeviceId();
227+
updatedDevices[deviceId] = createDevice(deviceId, newToken);
213228
}
229+
214230
// Update the document
215231
return transaction.update(docRef, {
216232
devices: updatedDevices,
217233
});
218-
} else if (newToken) {
219-
return transaction.set(docRef, {
220-
devices: [createDevice(newToken)],
221-
userId,
222-
});
223234
} else {
224-
return transaction.set(docRef, {
225-
devices: [],
226-
userId,
227-
});
235+
const userDevices = createUserDevices(userId, newToken);
236+
return transaction.set(docRef, userDevices);
228237
}
229238
});
230239
};
231240

232-
const createDevice = (fcmToken: string): Device => {
241+
const createDevice = (deviceId: string, fcmToken: string): Device => {
233242
const browser = detect();
234243
return {
235-
deviceId: window.navigator.userAgent,
244+
deviceId,
236245
fcmToken,
237246
name: browser.name.charAt(0).toUpperCase() + browser.name.slice(1),
238247
os: browser.os,
239248
type: 'Web',
249+
userAgent: window.navigator.userAgent,
250+
};
251+
};
252+
253+
const createUserDevices = (
254+
userId: string,
255+
fcmToken: string | void
256+
): UserDevices => {
257+
const deviceId = generateDeviceId();
258+
return {
259+
devices: fcmToken
260+
? {
261+
[deviceId]: createDevice(deviceId, fcmToken),
262+
}
263+
: {},
264+
userId,
240265
};
241266
};
242267

268+
const findDeviceId = (devices: Devices, token: string): string | void => {
269+
return Object.keys(devices).find(deviceId => {
270+
const device = devices[deviceId];
271+
return device.fcmToken === token;
272+
});
273+
};
274+
275+
const generateDeviceId = (): string => {
276+
return nanoid();
277+
};
278+
279+
const getDevices = (doc: firebase.firestore.DocumentSnapshot): Devices => {
280+
return doc.data().devices || {};
281+
};
282+
243283
const userRef = (
244284
firestore: firebase.firestore.Firestore,
245285
collectionPath: string,

tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"compileOnSave": false,
33
"compilerOptions": {
4+
"allowSyntheticDefaultImports": true,
45
"declaration": true,
56
"importHelpers": true,
67
"lib": ["es2017", "es2015", "dom"],

yarn.lock

+12
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,13 @@
151151
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
152152
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
153153

154+
"@types/nanoid@^2.0.0":
155+
version "2.0.0"
156+
resolved "https://registry.yarnpkg.com/@types/nanoid/-/nanoid-2.0.0.tgz#b59002c475e6dfcc26e67ba563ff61b512e5ebf8"
157+
integrity sha512-NtwPHfAyU3IDXdKAB2OMPpAauHBg9gUjpOYr3FAzI84D70nWdS8k5mryteLvT/s1ACeAFAkGg132/XJVN4qx/w==
158+
dependencies:
159+
"@types/node" "*"
160+
154161
"@types/node@*", "@types/node@^11.13.4":
155162
version "11.13.5"
156163
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.13.5.tgz#266564afa8a6a09dc778dfacc703ed3f09c80516"
@@ -1044,6 +1051,11 @@ nan@^2.0.0:
10441051
resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7"
10451052
integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==
10461053

1054+
nanoid@^2.0.2:
1055+
version "2.0.2"
1056+
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.0.2.tgz#2163edc84828cd42f9b8e4578979a4b5ffc1bb18"
1057+
integrity sha512-X4yQ8VHoFvHcykGunT2Jxrsm1c4vH5UKtau7LLJYXO1istCRE3jD8JxDyGCzN+h7dpWBCvWaSYgloRuphKRqUQ==
1058+
10471059
nanomatch@^1.2.9:
10481060
version "1.2.13"
10491061
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"

0 commit comments

Comments
 (0)