Changeset View
Changeset View
Standalone View
Standalone View
keyserver/src/push/send.js
// @flow | // @flow | ||||
import apn from '@parse/node-apn'; | import apn from '@parse/node-apn'; | ||||
import type { ResponseFailure } from '@parse/node-apn'; | import type { ResponseFailure } from '@parse/node-apn'; | ||||
import invariant from 'invariant'; | import invariant from 'invariant'; | ||||
import _cloneDeep from 'lodash/fp/cloneDeep.js'; | import _cloneDeep from 'lodash/fp/cloneDeep.js'; | ||||
import _flow from 'lodash/fp/flow.js'; | import _flow from 'lodash/fp/flow.js'; | ||||
import _mapValues from 'lodash/fp/mapValues.js'; | import _mapValues from 'lodash/fp/mapValues.js'; | ||||
import _pickBy from 'lodash/fp/pickBy.js'; | import _pickBy from 'lodash/fp/pickBy.js'; | ||||
import t from 'tcomb'; | |||||
import uuidv4 from 'uuid/v4.js'; | import uuidv4 from 'uuid/v4.js'; | ||||
import { oldValidUsernameRegex } from 'lib/shared/account-utils.js'; | import { oldValidUsernameRegex } from 'lib/shared/account-utils.js'; | ||||
import { isMentioned } from 'lib/shared/mention-utils.js'; | import { isMentioned } from 'lib/shared/mention-utils.js'; | ||||
import { | import { | ||||
createMessageInfo, | createMessageInfo, | ||||
sortMessageInfoList, | sortMessageInfoList, | ||||
shimUnsupportedRawMessageInfos, | shimUnsupportedRawMessageInfos, | ||||
} from 'lib/shared/message-utils.js'; | } from 'lib/shared/message-utils.js'; | ||||
import { messageSpecs } from 'lib/shared/messages/message-specs.js'; | import { messageSpecs } from 'lib/shared/messages/message-specs.js'; | ||||
import { notifTextsForMessageInfo } from 'lib/shared/notif-utils.js'; | import { notifTextsForMessageInfo } from 'lib/shared/notif-utils.js'; | ||||
import { | import { | ||||
rawThreadInfoFromServerThreadInfo, | rawThreadInfoFromServerThreadInfo, | ||||
threadInfoFromRawThreadInfo, | threadInfoFromRawThreadInfo, | ||||
} from 'lib/shared/thread-utils.js'; | } from 'lib/shared/thread-utils.js'; | ||||
import type { Platform, PlatformDetails } from 'lib/types/device-types.js'; | import type { Platform, PlatformDetails } from 'lib/types/device-types.js'; | ||||
import { messageTypes } from 'lib/types/message-types-enum.js'; | import { messageTypes } from 'lib/types/message-types-enum.js'; | ||||
import { | import { | ||||
type RawMessageInfo, | type RawMessageInfo, | ||||
type MessageData, | type MessageData, | ||||
} from 'lib/types/message-types.js'; | } from 'lib/types/message-types.js'; | ||||
import { rawMessageInfoValidator } from 'lib/types/message-types.js'; | |||||
import type { | import type { | ||||
WebNotification, | WebNotification, | ||||
WNSNotification, | WNSNotification, | ||||
ResolvedNotifTexts, | ResolvedNotifTexts, | ||||
} from 'lib/types/notif-types.js'; | } from 'lib/types/notif-types.js'; | ||||
import { resolvedNotifTextsValidator } from 'lib/types/notif-types.js'; | |||||
import type { ServerThreadInfo } from 'lib/types/thread-types.js'; | import type { ServerThreadInfo } from 'lib/types/thread-types.js'; | ||||
import { updateTypes } from 'lib/types/update-types-enum.js'; | import { updateTypes } from 'lib/types/update-types-enum.js'; | ||||
import { promiseAll } from 'lib/utils/promises.js'; | import { promiseAll } from 'lib/utils/promises.js'; | ||||
import { tID, tPlatformDetails, tShape } from 'lib/utils/validation-utils.js'; | |||||
import { getAPNsNotificationTopic } from './providers.js'; | import { getAPNsNotificationTopic } from './providers.js'; | ||||
import { rescindPushNotifs } from './rescind.js'; | import { rescindPushNotifs } from './rescind.js'; | ||||
import { | import { | ||||
apnPush, | apnPush, | ||||
fcmPush, | fcmPush, | ||||
getUnreadCounts, | getUnreadCounts, | ||||
apnMaxNotificationPayloadByteSize, | apnMaxNotificationPayloadByteSize, | ||||
fcmMaxNotificationPayloadByteSize, | fcmMaxNotificationPayloadByteSize, | ||||
wnsMaxNotificationPayloadByteSize, | wnsMaxNotificationPayloadByteSize, | ||||
webPush, | webPush, | ||||
wnsPush, | wnsPush, | ||||
type WebPushError, | type WebPushError, | ||||
type WNSPushError, | type WNSPushError, | ||||
} from './utils.js'; | } from './utils.js'; | ||||
import createIDs from '../creators/id-creator.js'; | import createIDs from '../creators/id-creator.js'; | ||||
import { createUpdates } from '../creators/update-creator.js'; | import { createUpdates } from '../creators/update-creator.js'; | ||||
import { dbQuery, SQL, mergeOrConditions } from '../database/database.js'; | import { dbQuery, SQL, mergeOrConditions } from '../database/database.js'; | ||||
import type { CollapsableNotifInfo } from '../fetchers/message-fetchers.js'; | import type { CollapsableNotifInfo } from '../fetchers/message-fetchers.js'; | ||||
import { fetchCollapsableNotifs } from '../fetchers/message-fetchers.js'; | import { fetchCollapsableNotifs } from '../fetchers/message-fetchers.js'; | ||||
import { fetchServerThreadInfos } from '../fetchers/thread-fetchers.js'; | import { fetchServerThreadInfos } from '../fetchers/thread-fetchers.js'; | ||||
import { fetchUserInfos } from '../fetchers/user-fetchers.js'; | import { fetchUserInfos } from '../fetchers/user-fetchers.js'; | ||||
import type { Viewer } from '../session/viewer.js'; | import type { Viewer } from '../session/viewer.js'; | ||||
import { getENSNames } from '../utils/ens-cache.js'; | import { getENSNames } from '../utils/ens-cache.js'; | ||||
import { validateOutput } from '../utils/validation-utils.js'; | |||||
type Device = { | type Device = { | ||||
+platform: Platform, | +platform: Platform, | ||||
+deviceToken: string, | +deviceToken: string, | ||||
+codeVersion: ?number, | +codeVersion: ?number, | ||||
}; | }; | ||||
type PushUserInfo = { | type PushUserInfo = { | ||||
+devices: Device[], | +devices: Device[], | ||||
▲ Show 20 Lines • Show All 177 Lines • ▼ Show 20 Lines | for (const notifInfo of usersToCollapsableNotifInfo[userID]) { | ||||
); | ); | ||||
})(); | })(); | ||||
deliveryPromises.push(deliveryPromise); | deliveryPromises.push(deliveryPromise); | ||||
} | } | ||||
} | } | ||||
const webVersionsToTokens = byPlatform.get('web'); | const webVersionsToTokens = byPlatform.get('web'); | ||||
if (webVersionsToTokens) { | if (webVersionsToTokens) { | ||||
for (const [codeVersion, deviceTokens] of webVersionsToTokens) { | for (const [codeVersion, deviceTokens] of webVersionsToTokens) { | ||||
const platformDetails = { platform: 'web', codeVersion }; | |||||
const deliveryPromise = (async () => { | const deliveryPromise = (async () => { | ||||
const notification = await prepareWebNotification({ | const notification = await prepareWebNotification({ | ||||
notifTexts, | notifTexts, | ||||
threadID: threadInfo.id, | threadID: threadInfo.id, | ||||
unreadCount: unreadCounts[userID], | unreadCount: unreadCounts[userID], | ||||
platformDetails, | |||||
}); | }); | ||||
return await sendWebNotification(notification, [...deviceTokens], { | return await sendWebNotification(notification, [...deviceTokens], { | ||||
...notificationInfo, | ...notificationInfo, | ||||
codeVersion, | codeVersion, | ||||
}); | }); | ||||
})(); | })(); | ||||
deliveryPromises.push(deliveryPromise); | deliveryPromises.push(deliveryPromise); | ||||
} | } | ||||
Show All 27 Lines | for (const notifInfo of usersToCollapsableNotifInfo[userID]) { | ||||
); | ); | ||||
})(); | })(); | ||||
deliveryPromises.push(deliveryPromise); | deliveryPromises.push(deliveryPromise); | ||||
} | } | ||||
} | } | ||||
const windowsVersionsToTokens = byPlatform.get('windows'); | const windowsVersionsToTokens = byPlatform.get('windows'); | ||||
if (windowsVersionsToTokens) { | if (windowsVersionsToTokens) { | ||||
for (const [codeVersion, deviceTokens] of windowsVersionsToTokens) { | for (const [codeVersion, deviceTokens] of windowsVersionsToTokens) { | ||||
const platformDetails = { platform: 'windows', codeVersion }; | |||||
const deliveryPromise = (async () => { | const deliveryPromise = (async () => { | ||||
const notification = await prepareWNSNotification({ | const notification = await prepareWNSNotification({ | ||||
notifTexts, | notifTexts, | ||||
threadID: threadInfo.id, | threadID: threadInfo.id, | ||||
unreadCount: unreadCounts[userID], | unreadCount: unreadCounts[userID], | ||||
platformDetails, | |||||
}); | }); | ||||
return await sendWNSNotification(notification, [...deviceTokens], { | return await sendWNSNotification(notification, [...deviceTokens], { | ||||
...notificationInfo, | ...notificationInfo, | ||||
codeVersion, | codeVersion, | ||||
}); | }); | ||||
})(); | })(); | ||||
deliveryPromises.push(deliveryPromise); | deliveryPromises.push(deliveryPromise); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 287 Lines • ▼ Show 20 Lines | type APNsNotifInputData = { | ||||
+notifTexts: ResolvedNotifTexts, | +notifTexts: ResolvedNotifTexts, | ||||
+newRawMessageInfos: RawMessageInfo[], | +newRawMessageInfos: RawMessageInfo[], | ||||
+threadID: string, | +threadID: string, | ||||
+collapseKey: ?string, | +collapseKey: ?string, | ||||
+badgeOnly: boolean, | +badgeOnly: boolean, | ||||
+unreadCount: number, | +unreadCount: number, | ||||
+platformDetails: PlatformDetails, | +platformDetails: PlatformDetails, | ||||
}; | }; | ||||
const apnsNotifInputDataValidator = tShape<APNsNotifInputData>({ | |||||
notifTexts: resolvedNotifTextsValidator, | |||||
newRawMessageInfos: t.list(rawMessageInfoValidator), | |||||
threadID: tID, | |||||
collapseKey: t.maybe(t.String), | |||||
badgeOnly: t.Boolean, | |||||
unreadCount: t.Number, | |||||
platformDetails: tPlatformDetails, | |||||
}); | |||||
async function prepareAPNsNotification( | async function prepareAPNsNotification( | ||||
inputData: APNsNotifInputData, | inputData: APNsNotifInputData, | ||||
): Promise<apn.Notification> { | ): Promise<apn.Notification> { | ||||
const convertedData = validateOutput( | |||||
inputData.platformDetails, | |||||
apnsNotifInputDataValidator, | |||||
inputData, | |||||
); | |||||
const { | const { | ||||
notifTexts, | notifTexts, | ||||
newRawMessageInfos, | newRawMessageInfos, | ||||
threadID, | threadID, | ||||
collapseKey, | collapseKey, | ||||
badgeOnly, | badgeOnly, | ||||
unreadCount, | unreadCount, | ||||
platformDetails, | platformDetails, | ||||
} = inputData; | } = convertedData; | ||||
const uniqueID = uuidv4(); | const uniqueID = uuidv4(); | ||||
const notification = new apn.Notification(); | const notification = new apn.Notification(); | ||||
notification.topic = getAPNsNotificationTopic(platformDetails); | notification.topic = getAPNsNotificationTopic(platformDetails); | ||||
const { merged, ...rest } = notifTexts; | const { merged, ...rest } = notifTexts; | ||||
// We don't include alert's body on macos because we | // We don't include alert's body on macos because we | ||||
// handle displaying the notification ourselves and | // handle displaying the notification ourselves and | ||||
▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Lines | ): Promise<apn.Notification> { | ||||
} | } | ||||
return notification; | return notification; | ||||
} | } | ||||
type AndroidNotifInputData = { | type AndroidNotifInputData = { | ||||
...APNsNotifInputData, | ...APNsNotifInputData, | ||||
+dbID: string, | +dbID: string, | ||||
}; | }; | ||||
const androidNotifInputDataValidator = tShape<AndroidNotifInputData>({ | |||||
...apnsNotifInputDataValidator.meta.props, | |||||
dbID: t.String, | |||||
}); | |||||
async function prepareAndroidNotification( | async function prepareAndroidNotification( | ||||
inputData: AndroidNotifInputData, | inputData: AndroidNotifInputData, | ||||
): Promise<Object> { | ): Promise<Object> { | ||||
const convertedData = validateOutput( | |||||
inputData.platformDetails, | |||||
androidNotifInputDataValidator, | |||||
inputData, | |||||
); | |||||
const { | const { | ||||
notifTexts, | notifTexts, | ||||
newRawMessageInfos, | newRawMessageInfos, | ||||
threadID, | threadID, | ||||
collapseKey, | collapseKey, | ||||
badgeOnly, | badgeOnly, | ||||
unreadCount, | unreadCount, | ||||
platformDetails: { codeVersion }, | platformDetails: { codeVersion }, | ||||
dbID, | dbID, | ||||
} = inputData; | } = convertedData; | ||||
const notifID = collapseKey ? collapseKey : dbID; | const notifID = collapseKey ? collapseKey : dbID; | ||||
const { merged, ...rest } = notifTexts; | const { merged, ...rest } = notifTexts; | ||||
const notification = { | const notification = { | ||||
data: { | data: { | ||||
badge: unreadCount.toString(), | badge: unreadCount.toString(), | ||||
...rest, | ...rest, | ||||
threadID, | threadID, | ||||
Show All 37 Lines | ): Promise<Object> { | ||||
} | } | ||||
return notification; | return notification; | ||||
} | } | ||||
type WebNotifInputData = { | type WebNotifInputData = { | ||||
+notifTexts: ResolvedNotifTexts, | +notifTexts: ResolvedNotifTexts, | ||||
+threadID: string, | +threadID: string, | ||||
+unreadCount: number, | +unreadCount: number, | ||||
+platformDetails: PlatformDetails, | |||||
}; | }; | ||||
const webNotifInputDataValidator = tShape<WebNotifInputData>({ | |||||
notifTexts: resolvedNotifTextsValidator, | |||||
threadID: tID, | |||||
unreadCount: t.Number, | |||||
platformDetails: tPlatformDetails, | |||||
}); | |||||
async function prepareWebNotification( | async function prepareWebNotification( | ||||
inputData: WebNotifInputData, | inputData: WebNotifInputData, | ||||
): Promise<WebNotification> { | ): Promise<WebNotification> { | ||||
const { notifTexts, threadID, unreadCount } = inputData; | const convertedData = validateOutput( | ||||
inputData.platformDetails, | |||||
webNotifInputDataValidator, | |||||
inputData, | |||||
); | |||||
const { notifTexts, threadID, unreadCount } = convertedData; | |||||
const id = uuidv4(); | const id = uuidv4(); | ||||
const { merged, ...rest } = notifTexts; | const { merged, ...rest } = notifTexts; | ||||
const notification = { | const notification = { | ||||
...rest, | ...rest, | ||||
unreadCount, | unreadCount, | ||||
id, | id, | ||||
threadID, | threadID, | ||||
}; | }; | ||||
return notification; | return notification; | ||||
} | } | ||||
type WNSNotifInputData = { | type WNSNotifInputData = { | ||||
+notifTexts: ResolvedNotifTexts, | +notifTexts: ResolvedNotifTexts, | ||||
+threadID: string, | +threadID: string, | ||||
+unreadCount: number, | +unreadCount: number, | ||||
+platformDetails: PlatformDetails, | |||||
}; | }; | ||||
const wnsNotifInputDataValidator = tShape<WNSNotifInputData>({ | |||||
notifTexts: resolvedNotifTextsValidator, | |||||
threadID: tID, | |||||
unreadCount: t.Number, | |||||
platformDetails: tPlatformDetails, | |||||
}); | |||||
async function prepareWNSNotification( | async function prepareWNSNotification( | ||||
inputData: WNSNotifInputData, | inputData: WNSNotifInputData, | ||||
): Promise<WNSNotification> { | ): Promise<WNSNotification> { | ||||
const { notifTexts, threadID, unreadCount } = inputData; | const convertedData = validateOutput( | ||||
inputData.platformDetails, | |||||
wnsNotifInputDataValidator, | |||||
inputData, | |||||
); | |||||
const { notifTexts, threadID, unreadCount } = convertedData; | |||||
const { merged, ...rest } = notifTexts; | const { merged, ...rest } = notifTexts; | ||||
const notification = { | const notification = { | ||||
...rest, | ...rest, | ||||
unreadCount, | unreadCount, | ||||
threadID, | threadID, | ||||
}; | }; | ||||
if ( | if ( | ||||
▲ Show 20 Lines • Show All 363 Lines • Show Last 20 Lines |