chore: more device lock fixes

This commit is contained in:
Chris Anderson 2025-07-26 15:48:02 -05:00
parent 308bc9f3aa
commit e33f9d79c0

View file

@ -16,9 +16,8 @@ import Project from '../projects/Project'
import { acquireLock, LockError, releaseLock } from '../core/Lock'
const CacheKeys = {
user: (id: number) => `user:${id}`,
userPatch: (id: number) => `lock:user:${id}:patch`,
devicePatch: (deviceId: string) => `lock:device:${deviceId}:patch`,
userPatch: (id: number) => `lock:u:${id}`,
devicePatch: (deviceId: string) => `lock:d:${deviceId}`,
}
export const getUser = async (id: number, projectId?: number, trx?: Transaction): Promise<User | undefined> => {
@ -218,50 +217,52 @@ export const deleteUser = async (projectId: number, externalId: string): Promise
export const saveDevice = async (projectId: number, { external_id, anonymous_id, ...params }: DeviceParams, trx?: Transaction): Promise<number | undefined> => {
const { device_id, token } = params
const user = await getUserFromClientId(projectId, { external_id, anonymous_id } as ClientIdentity, trx)
if (!user) throw new RetryError()
// Make sure we aren't trying to add the same device twice
const { device_id, token } = params
const key = CacheKeys.devicePatch(device_id)
const acquired = await acquireLock({ key, timeout: 90 })
if (!acquired) throw new LockError()
const user = await getUserFromClientId(projectId, { external_id, anonymous_id } as ClientIdentity, trx)
if (!user) throw new RetryError()
const device = await getDeviceFromIdOrToken(projectId, device_id, token, trx)
// If we have a device, move it to the new user and update both users
// in the DB to reflect their current push state
if (device) {
try {
const oldParams = pick(device, ['os', 'os_version', 'model', 'app_build', 'app_version', 'token'])
const newParams = pick(params, ['os', 'os_version', 'model', 'app_build', 'app_version', 'token'])
// If we have a device, move it to the new user and update both users
// in the DB to reflect their current push state
if (device) {
// If nothing has changed on the device, just return the ID
const isDirty = !deepEqual(oldParams, newParams) || device.user_id !== user.id
if (!isDirty) return device.id
const oldParams = pick(device, ['os', 'os_version', 'model', 'app_build', 'app_version', 'token'])
const newParams = pick(params, ['os', 'os_version', 'model', 'app_build', 'app_version', 'token'])
// Update the device, combining the old and new params
await Device.update(qb => qb.where('id', device.id), {
...oldParams,
...newParams,
user_id: user.id,
}, trx)
// If nothing has changed on the device, just return the ID
const isDirty = !deepEqual(oldParams, newParams) || device.user_id !== user.id
if (!isDirty) return device.id
// If this user had no previous push device, update the db
if (!user.has_push_device && !!token) {
await updateUserDeviceState(user, true, trx)
// Update the device, combining the old and new params
await Device.update(qb => qb.where('id', device.id), {
...oldParams,
...newParams,
user_id: user.id,
}, trx)
// If this user had no previous push device, update the db
if (!user.has_push_device && !!token) {
await updateUserDeviceState(user, true, trx)
}
// Update the user we stole the device from
const previousUser = await getUser(device.user_id, projectId, trx)
const hasPushDevice = await userHasPushDevice(user.project_id, device.user_id, trx)
if (previousUser) {
await updateUserDeviceState(previousUser, hasPushDevice, trx)
}
return device.id
}
// Update the user we stole the device from
const previousUser = await getUser(device.user_id, projectId, trx)
const hasPushDevice = await userHasPushDevice(user.project_id, device.user_id, trx)
if (previousUser) {
await updateUserDeviceState(previousUser, hasPushDevice, trx)
}
await releaseLock(key)
return device.id
} else {
// If no device found, create a new one
const newDevice = {
...params,
@ -275,9 +276,10 @@ export const saveDevice = async (projectId: number, { external_id, anonymous_id,
// If user previously had another device, no need to update
if (user.has_push_device || !token) return deviceId
await updateUserDeviceState(user, true, trx)
await releaseLock(key)
return deviceId
} finally {
await releaseLock(key)
}
}