Skip to content

Commit

Permalink
Handle push tokens on new phones/installs
Browse files Browse the repository at this point in the history
  • Loading branch information
IanPhilips committed Oct 30, 2024
1 parent 63c1e5e commit 33098f7
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 35 deletions.
19 changes: 16 additions & 3 deletions backend/api/src/update-private-user.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import { APIHandler } from './helpers/endpoint'
import { createSupabaseDirectClient } from 'shared/supabase/init'
import { updatePrivateUser as updatePrivateUserData } from 'shared/supabase/users'
import {
updatePrivateUser as updatePrivateUserData,
UpdateType,
} from 'shared/supabase/users'
import { FieldVal } from 'shared/supabase/utils'
import { DELETE_PUSH_TOKEN } from 'common/notification'

export const updatePrivateUser: APIHandler<'me/private/update'> = async (
props,
auth
) => {
const db = createSupabaseDirectClient()
await updatePrivateUserData(db, auth.uid, props)
const pg = createSupabaseDirectClient()

const updates = {
...props,
} as UpdateType
if (props.pushToken === DELETE_PUSH_TOKEN) {
updates.pushToken = FieldVal.delete()
}

await updatePrivateUserData(pg, auth.uid, updates)
}
2 changes: 1 addition & 1 deletion backend/shared/src/supabase/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export const updateUser = async (
}

// private_users has 2 columns that aren't in the data column
type UpdateType =
export type UpdateType =
| Partial<PrivateUser>
| {
[key in keyof PrivateUser]?: FieldValFunction
Expand Down
2 changes: 2 additions & 0 deletions common/src/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,3 +506,5 @@ export const BalanceChangeNotificationTypes: NotificationReason[] = [
'bet_fill',
'mana_payment_received',
]

export const DELETE_PUSH_TOKEN = 'delete'
4 changes: 3 additions & 1 deletion web/components/native-message-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ export const NativeMessageProvider = (props: { children: React.ReactNode }) => {
await handlePushNotificationPermissionStatus(status)
} else if (type === 'pushToken') {
const { token } = data as MesageTypeMap['pushToken']
await setPushToken(token)
if (token !== privateUser?.pushToken) {
await setPushToken(token)
}
} else if (type === 'notification') {
const notification = data as Notification
// TODO: mark the notification as seen
Expand Down
15 changes: 9 additions & 6 deletions web/components/notification-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ import { WatchMarketModal } from 'web/components/contract/watch-market-modal'
import { Col } from 'web/components/layout/col'
import { Row } from 'web/components/layout/row'
import { SwitchSetting } from 'web/components/switch-setting'
import { getIsNative } from 'web/lib/native/is-native'
import { UserWatchedContractsButton } from 'web/components/notifications/watched-markets'
import { usePrivateUser, useUser } from 'web/hooks/use-user'
import { usePersistentInMemoryState } from 'web/hooks/use-persistent-in-memory-state'
import TrophyIcon from 'web/lib/icons/trophy-icon.svg'
import { postMessageToNative } from 'web/lib/native/post-message'
import { api } from 'web/lib/api/api'
import { useNativeInfo } from './native-message-provider'

const emailsEnabled: Array<notification_preference> = [
'all_comments_on_watched_markets',
Expand Down Expand Up @@ -190,15 +190,16 @@ export const SectionRoutingContext = createContext<string | undefined>(

export function NotificationSettings(props: {
navigateToSection: string | undefined
privateUser: PrivateUser
}) {
const { navigateToSection } = props
const { navigateToSection, privateUser } = props
const user = useUser()
const [showWatchModal, setShowWatchModal] = useState(false)

return (
<SectionRoutingContext.Provider value={navigateToSection}>
<Col className={'gap-6 p-2'}>
<PushNotificationsBanner />
<PushNotificationsBanner privateUser={privateUser} />
<Row className={'text-ink-700 gap-2 text-xl'}>
{user ? (
<UserWatchedContractsButton user={user} />
Expand Down Expand Up @@ -422,15 +423,17 @@ export const NotificationSection = (props: {
)
}

export const PushNotificationsBanner = () => {
const privateUser = usePrivateUser()!
export const PushNotificationsBanner = (props: {
privateUser: PrivateUser
}) => {
const { privateUser } = props
const {
interestedInPushNotifications,
rejectedPushNotificationsOn,
pushToken,
} = privateUser

const isNative = getIsNative()
const { isNative } = useNativeInfo()

if (pushToken || !isNative) return <div />

Expand Down
66 changes: 47 additions & 19 deletions web/components/push-notifications-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import { postMessageToNative } from 'web/lib/native/post-message'
import dayjs from 'dayjs'
import { formatMoney } from 'common/util/format'
import { PUSH_NOTIFICATION_BONUS } from 'common/economy'
import { getIsNative } from 'web/lib/native/is-native'
import { api } from 'web/lib/api/api'
import { useNativeMessages } from 'web/hooks/use-native-messages'
import { nativeToWebMessageType } from 'common/native-message'
import { MesageTypeMap } from 'common/native-message'
import { useEvent } from 'web/hooks/use-event'

export function PushNotificationsModal(props: {
privateUser: PrivateUser
Expand All @@ -17,20 +20,39 @@ export function PushNotificationsModal(props: {
}) {
const { privateUser, user, totalNotifications } = props
const [open, setOpen] = useState(false)
const [showSettingsDescription, setShowSettingsDescription] = useState(false)
const showSystemNotificationsPrompt = () => {
const [showHowToEnableInSettings, setShowHowToEnableInSettings] =
useState(false)

const showEnableSystemNotificationsPrompt = () => {
postMessageToNative('promptEnablePushNotifications', {})
}

const handleNativeMessage = useEvent(
async (type: nativeToWebMessageType, data: MesageTypeMap[typeof type]) => {
const { status } =
data as MesageTypeMap['pushNotificationPermissionStatus']
if (status === 'undetermined' && privateUser.pushToken) {
setOpen(true)
}
}
)
useNativeMessages(['pushNotificationPermissionStatus'], handleNativeMessage)

useEffect(() => {
if (!getIsNative() || privateUser.pushToken) return
if (privateUser.pushToken) {
postMessageToNative('tryToGetPushTokenWithoutPrompt', {})
}
}, [privateUser.pushToken])

useEffect(() => {
if (privateUser.pushToken) return

// They said 'sure' to our prompt, but they haven't given us system permissions yet
if (
privateUser.interestedInPushNotifications &&
!privateUser.rejectedPushNotificationsOn
) {
showSystemNotificationsPrompt()
showEnableSystemNotificationsPrompt()
return
}

Expand Down Expand Up @@ -59,9 +81,6 @@ export function PushNotificationsModal(props: {
const shouldShowOurNotificationPrompt = totalNotifications >= 10
const openTimer = setTimeout(() => {
setOpen(shouldShowOurNotificationPrompt)
api('me/private/update', {
lastPromptedToEnablePushNotifications: Date.now(),
})
}, 1000)
return () => clearTimeout(openTimer)
}, [
Expand All @@ -71,18 +90,21 @@ export function PushNotificationsModal(props: {
])

useEffect(() => {
postMessageToNative('tryToGetPushTokenWithoutPrompt', {})
}, [showSettingsDescription])
const verified = humanish(user)
if (open) {
api('me/private/update', {
lastPromptedToEnablePushNotifications: Date.now(),
})
}
}, [open])

if (!getIsNative()) return <div />
const bonusEligible = humanish(user) && !privateUser.pushToken

return (
<Modal open={open} setOpen={setOpen}>
<Col className="bg-canvas-0 text-ink-1000 w-full justify-start gap-3 rounded-md px-8 py-6">
<span className="text-primary-700 mb-2 text-2xl font-semibold">
Enable push notifications
{verified && (
{bonusEligible && (
<>
, earn{' '}
<span className={'text-teal-500'}>
Expand All @@ -91,20 +113,20 @@ export function PushNotificationsModal(props: {
</>
)}
</span>
{!showSettingsDescription && (
{!showHowToEnableInSettings && (
<span className={'text-ink-700'}>
Get the most out of Manifold: replies, breaking market news, and
direct messages.
</span>
)}
{showSettingsDescription ? (
{showHowToEnableInSettings ? (
<Col className={'justify-between gap-2'}>
<Col className={'gap-1 text-lg'}>
<span>1. Go to your settings</span>
<span>3. Search & tap Manifold</span>
<span>2. Tap Notifications</span>
<span>4. Tap Allow Notifications</span>
{verified && (
{bonusEligible && (
<span>
5. We'll send you{' '}
<span className={'font-semibold text-teal-500'}>
Expand Down Expand Up @@ -136,6 +158,11 @@ export function PushNotificationsModal(props: {
medium: 'mobile',
enabled: true,
})
if (privateUser.pushToken) {
api('me/private/update', {
pushToken: 'delete',
})
}
setOpen(false)
}}
color={'gray-white'}
Expand All @@ -152,16 +179,17 @@ export function PushNotificationsModal(props: {
enabled: false,
})
if (!!privateUser.rejectedPushNotificationsOn) {
setShowSettingsDescription(true)
postMessageToNative('tryToGetPushTokenWithoutPrompt', {})
setShowHowToEnableInSettings(true)
return
}
showSystemNotificationsPrompt()
showEnableSystemNotificationsPrompt()
setOpen(false)
}}
>
<span>
Enable notifications
{verified && (
{bonusEligible && (
<>
{' '}
for{' '}
Expand Down
4 changes: 3 additions & 1 deletion web/lib/supabase/notifications.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { postMessageToNative } from 'web/lib/native/post-message'
import { api } from '../api/api'
import { DELETE_PUSH_TOKEN } from 'common/notification'

export const setPushToken = async (pushToken: string) => {
try {
Expand All @@ -15,7 +16,7 @@ export const handlePushNotificationPermissionStatus = async (
status: 'denied' | 'undetermined'
) => {
const privateUser = await api('me/private')
if (!privateUser || privateUser.pushToken) return
if (!privateUser) return
if (status === 'denied') {
await setPushTokenRequestDenied()
}
Expand All @@ -26,5 +27,6 @@ export const setPushTokenRequestDenied = async () => {
await api('me/private/update', {
rejectedPushNotificationsOn: Date.now(),
interestedInPushNotifications: false,
pushToken: DELETE_PUSH_TOKEN,
})
}
14 changes: 10 additions & 4 deletions web/pages/notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,18 @@ import { useIsPageVisible } from 'web/hooks/use-page-visible'
import { useRedirectIfSignedOut } from 'web/hooks/use-redirect-if-signed-out'
import { usePrivateUser, useUser } from 'web/hooks/use-user'
import { XIcon } from '@heroicons/react/outline'
import { getNativePlatform } from 'web/lib/native/is-native'
import { AppBadgesOrGetAppButton } from 'web/components/buttons/app-badges-or-get-app-button'
import { LoadingIndicator } from 'web/components/widgets/loading-indicator'
import { track } from 'web/lib/service/analytics'
import { useNativeInfo } from 'web/components/native-message-provider'

export default function NotificationsPage() {
const privateUser = usePrivateUser()
const user = useUser()
useRedirectIfSignedOut()

const [navigateToSection, setNavigateToSection] = useState<string>()
const { isNative } = getNativePlatform()
const { isNative } = useNativeInfo()
const router = useRouter()
useEffect(() => {
if (!router.isReady) return
Expand Down Expand Up @@ -174,7 +174,12 @@ function NotificationsContent(props: {
{
queryString: 'Settings',
title: 'Settings',
content: <NotificationSettings navigateToSection={section} />,
content: (
<NotificationSettings
navigateToSection={section}
privateUser={privateUser}
/>
),
},
]}
/>
Expand Down Expand Up @@ -250,6 +255,7 @@ export function NotificationsList(props: {
}, [JSON.stringify(groupedNotifications), page])

const isPageVisible = useIsPageVisible()
const { isNative } = useNativeInfo()

// Mark all notifications as seen. Rerun as new notifications come in.
useEffect(() => {
Expand All @@ -274,7 +280,7 @@ export function NotificationsList(props: {
setPage={setPage}
/>
)}
{privateUser && groupedNotifications && user && (
{privateUser && groupedNotifications && user && isNative && (
<PushNotificationsModal
user={user}
privateUser={privateUser}
Expand Down

0 comments on commit 33098f7

Please sign in to comment.