in packages/fxa-auth-server/lib/push.js [380:489]
async sendPush(uid, devices, reason, options = {}) {
devices = filterSupportedDevices(options.data, devices);
if (!PUSH_REASONS.has(reason)) {
throw `Unknown push reason: ${reason}`;
}
// There's no spec-compliant way to error out as a result of having
// too many devices to notify. For now, just log metrics about it.
if (devices.length > MAX_ACTIVE_DEVICES) {
log.warn(LOG_OP_TOO_MANY_DEVICES, { uid });
}
const sendErrors = {};
for (const device of devices) {
const deviceId = device.id;
// Details that we might want to include in logs.
const metricsTags = {
reason,
uid,
deviceId,
uaOS: device.uaOS,
uaOSVersion: device.uaOSVersion,
uaBrowser: device.uaBrowser,
uaBrowserVersion: device.uaBrowserVersion,
lastSeen: this.getLastSeenTag(device),
};
this.reportPushAttempt(device.pushCallback, metricsTags);
if (!device.pushCallback) {
sendErrors[deviceId] = this.reportPushFailure(
ERR_NO_PUSH_CALLBACK,
metricsTags
);
continue;
}
if (device.pushEndpointExpired) {
sendErrors[deviceId] = this.reportPushFailure(
ERR_PUSH_CALLBACK_EXPIRED,
metricsTags
);
continue;
}
const pushSubscription = { endpoint: device.pushCallback };
let pushPayload = null;
const pushOptions = { TTL: options.TTL || '0' };
if (options.data) {
if (!device.pushPublicKey || !device.pushAuthKey) {
sendErrors[deviceId] = this.reportPushFailure(
ERR_DATA_BUT_NO_KEYS,
metricsTags
);
continue;
}
pushSubscription.keys = {
p256dh: device.pushPublicKey,
auth: device.pushAuthKey,
};
pushPayload = Buffer.from(JSON.stringify(options.data));
}
if (vapid) {
pushOptions.vapidDetails = vapid;
}
try {
await webpush.sendNotification(
pushSubscription,
pushPayload,
pushOptions
);
this.reportPushSuccess(metricsTags);
} catch (err) {
// If we've stored an invalid key in the db for some reason, then we
// might get an encryption failure here. Check the key, which also
// happens to work around bugginess in node's handling of said failures.
let keyWasInvalid = false;
if (!err.statusCode && device.pushPublicKey) {
if (!isValidPublicKey(device.pushPublicKey)) {
keyWasInvalid = true;
}
}
// 404 or 410 error from the push servers means
// the push settings need to be reset.
// the clients will check this and re-register push endpoints
if (
err.statusCode === 404 ||
err.statusCode === 410 ||
keyWasInvalid
) {
sendErrors[deviceId] = this.reportPushFailure(
ERR_PUSH_CALLBACK_RESET,
metricsTags
);
// set the push endpoint expired flag
// Warning: this method is called without any session tokens or auth validation.
device.pushEndpointExpired = true;
try {
await db.updateDevice(uid, device);
} catch (err) {
log.warn(LOG_OP_DEVICE_UPDATE_FAILED, { uid, deviceId, err });
}
} else {
log.error(LOG_OP_PUSH_UNEXPECTED_ERROR, {
err,
statusCode: err.statusCode,
body: err.body,
});
sendErrors[deviceId] = this.reportPushFailure(err, metricsTags);
}
}
}
return sendErrors;
},