Skip to content

Commit

Permalink
Merge pull request #165 from yssk22/revert-163-revert-159-develop
Browse files Browse the repository at this point in the history
Revert "Revert "[expo] implement push notification handler""
  • Loading branch information
yssk22 authored Nov 29, 2024
2 parents ef1fb04 + 3bc9f2d commit 0dabed2
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 22 deletions.
72 changes: 56 additions & 16 deletions expo/features/app/user/internals/UserRoot.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { useCurrentUser, useUserConfig } from '@hpapp/features/app/settings';
import { Initializer, Loading } from '@hpapp/features/common';
import { Screen, ScreenParams, createStackNavigator } from '@hpapp/features/common/stack';
import { usePushNotificationToken, usePushNotificationTokenUpdator } from '@hpapp/features/push';
import {
usePushNotificationToken,
usePushNotificationTokenUpdator,
PushNotificationData,
PushNotificationHandler
} from '@hpapp/features/push';
import { logScreenView, useSetUserId } from '@hpapp/system/firebase';
import * as logging from '@hpapp/system/logging';
import { init as initURICache } from '@hpapp/system/uricache';
import { useNavigationContainerRef } from '@react-navigation/native';
import { useEffect } from 'react';
import { useCallback, useEffect } from 'react';

import UserError from './UserError';
import UserServiceProvider from './UserServiceProvider';
Expand All @@ -19,11 +24,16 @@ const Stack = createStackNavigator({
const initializers = [initURICache];
const Screens: Screen<ScreenParams>[] = [];

// TODO: genscreen should create this param types as well.
type PushNotificationParams = {
'/feed/item/': { feedId: string };
};

export type UserRootProps = { screens?: Screen<ScreenParams>[] };

export default function UserRoot({ screens = Screens }: UserRootProps) {
const userConfig = useUserConfig();
const navigation = useNavigationContainerRef<ReactNavigation.RootParamList>();
const navigation = useNavigationContainerRef<PushNotificationParams>();
const token = usePushNotificationToken();
const [tokenUpdator] = usePushNotificationTokenUpdator();
useEffect(() => {
Expand All @@ -33,22 +43,52 @@ export default function UserRoot({ screens = Screens }: UserRootProps) {
}, [token.data]);
const currentUser = useCurrentUser();
useSetUserId(currentUser?.id ?? null);
const onData = useCallback((data: PushNotificationData) => {
// TODO: if navigation is not ready, we should wait for it, and navigate after it's ready.
const nav = navigation?.current;
if (nav === null) {
return;
}
if (!nav.isReady()) {
return;
}

// we need a compatibility of payload with v2 implementation
// == Ameblo Notification payload example
// {
// "params": {
// "id": 94489510263,
// "memberKey": "yukiho_shimoitani",
// "path": "/angerme-new/entry-12876844045.html",
// "post_id": 12889971534
// },
// "path": "/ameblo/post/"
// }
if (data.body.payload.path === '/ameblo/post/') {
nav.navigate('/feed/item/', { feedId: data.body.payload.params.id });
return;
}
// in v3, the server should send path and params so that we don't have to handle details.
nav.navigate(data.body.payload.path, data.body.payload.params);
}, []);
return (
<Initializer initializers={initializers}>
<UserServiceProvider errorFallback={UserError} loadingFallback={<Loading testID="UserRoot.Loading" />}>
<Stack
ref={navigation}
screens={screens}
initialRouteName={userConfig?.completeOnboarding ? '/' : '/onboarding/'}
onStateChange={(state) => {
logScreenView(state.screen.name);
logging.Info('features.app.user.onStateChagne', `to ${state.screen.name}`, {
name: state.screen.name,
path: state.screen.path,
params: state.params
});
}}
/>
<PushNotificationHandler onData={onData}>
<Stack
ref={navigation}
screens={screens}
initialRouteName={userConfig?.completeOnboarding ? '/' : '/onboarding/'}
onStateChange={(state) => {
logScreenView(state.screen.name);
logging.Info('features.app.user.onStateChagne', `to ${state.screen.name}`, {
name: state.screen.name,
path: state.screen.path,
params: state.params
});
}}
/>
</PushNotificationHandler>
</UserServiceProvider>
</Initializer>
);
Expand Down
12 changes: 12 additions & 0 deletions expo/features/devtool/DevtoolScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useThemeColor } from '@hpapp/features/app/theme';
import { AuthGateByRole } from '@hpapp/features/auth';
import { Text } from '@hpapp/features/common';
import { defineScreen } from '@hpapp/features/common/stack';
import { usePushNotificationToken } from '@hpapp/features/push';
import { getAppCheckToken } from '@hpapp/system/firebase';
import { t } from '@hpapp/system/i18n';
import { Divider, ListItem } from '@rneui/themed';
Expand All @@ -19,6 +20,8 @@ export default defineScreen('/devtool/', function DevtoolScreen() {
const appConfig = useAppConfig();
const userConfig = useUserConfig();
const [appCheckToken, setAppCheckToken] = useState<string | null>(null);
const pushToken = usePushNotificationToken();

useEffect(() => {
(async () => {
setAppCheckToken((await getAppCheckToken()).token);
Expand Down Expand Up @@ -49,6 +52,15 @@ export default defineScreen('/devtool/', function DevtoolScreen() {
displayValue={(appCheckToken ?? '').substring(0, 20) + '****'}
/>
<Divider />
<DevtoolListItem
name="Push Token"
value={pushToken.error !== undefined ? pushToken.error.toString() : (pushToken.data ?? '')}
displayValue={
pushToken.error !== undefined ? pushToken.error.toString() : (pushToken.data ?? '').substring(0, 20) + '****'
}
/>
<Divider />

<ListItemClearCache />
<Divider />
<DevtoolUserConfigForm />
Expand Down
2 changes: 1 addition & 1 deletion expo/features/feed/FeedItemScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type FeedItemScreenProps = {
feedId: string;
};

export default defineScreen('/feed/', function FeedItemScreen(props: FeedItemScreenProps) {
export default defineScreen('/feed/item/', function FeedItemScreen(props: FeedItemScreenProps) {
return (
<View style={styles.container}>
<Suspense fallback={<Loading />}>
Expand Down
4 changes: 2 additions & 2 deletions expo/features/push/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import PushNotificationHandler, { PushNotificationData } from './internals/PushNotificationHandler';
import usePushNotificationToken from './internals/usePushNotificationToken';
import usePushNotificationTokenUpdator from './internals/usePushNotificationTokenUpdator';

export { usePushNotificationToken, usePushNotificationTokenUpdator };
export { usePushNotificationToken, usePushNotificationTokenUpdator, PushNotificationHandler, PushNotificationData };
50 changes: 50 additions & 0 deletions expo/features/push/internals/PushNotificationHandler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { logEvent } from '@hpapp/system/firebase';
import * as Notifications from 'expo-notifications';
import React, { useEffect, useRef } from 'react';

type Subscription = ReturnType<typeof Notifications.addNotificationResponseReceivedListener>;

export type PushNotificationData = {
id: string;
timestamp: Date;
body: {
type: string;
payload: { [key: string]: any };
};
};

export type PushNotificationHandlerProps = {
children: React.ReactElement;
onData?: (data: PushNotificationData) => void;
};

export default function PushNotificationHandler({ children, onData }: PushNotificationHandlerProps) {
const responseSubscription = useRef<Subscription>();
useEffect(() => {
(async () => {
responseSubscription.current = Notifications.addNotificationResponseReceivedListener((response) => {
const body = response.notification.request.content.data as {
type: string;
payload: string;
};
logEvent('respond_to_notification', {
body
});
onData?.({
id: response.notification.request.identifier,
timestamp: new Date(response.notification.date),
body: {
type: body.type,
payload: JSON.parse(body.payload)
}
});
});
return () => {
if (responseSubscription.current) {
Notifications.removeNotificationSubscription(responseSubscription.current);
}
};
})();
}, [onData]);
return <>{children}</>;
}
1 change: 1 addition & 0 deletions expo/jest.setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,5 @@ jest.mock('@hpapp/features/app/storybook', () => {
}
};
});

jest.setTimeout(30000);
15 changes: 12 additions & 3 deletions go/service/push/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/yssk22/hpapp/go/foundation/slice"
"github.com/yssk22/hpapp/go/service/ent"
"github.com/yssk22/hpapp/go/service/ent/usernotificationlog"
"github.com/yssk22/hpapp/go/service/ent/usernotificationsetting"
"github.com/yssk22/hpapp/go/service/entutil"
"github.com/yssk22/hpapp/go/service/schema/enums"
Expand Down Expand Up @@ -133,20 +134,28 @@ func Deliver(ctx context.Context, notif Notification, options ...DeliveryOption)
fmt.Println(receivers)

entclient := entutil.NewClient(ctx)
isTest := len(o.TestTokens) > 0
record, err := entclient.UserNotificationLog.Create().
SetKey(key).
SetTrigger(trigger).
SetReactNavigationMessage(*message).
SetIsTest(len(o.TestTokens) > 0).
SetIsTest(isTest).
SetStatus(enums.UserNotificationStatusPrepared).
SetExpectedDeliveryTime(expectedTime).
AddReceivers(receivers...).
Save(ctx)
if err != nil {
if ent.IsConstraintError(err) {
if !ent.IsConstraintError(err) {
return nil, err
}
// ignore duplicate notification if it's test
if !isTest {
return nil, ErrDuplicateNotification
}
return nil, err
record, err = entclient.UserNotificationLog.Query().Where(usernotificationlog.KeyEQ(key), usernotificationlog.TriggerEQ(trigger)).WithReceivers().First(ctx)
if err != nil {
return nil, err
}
}
messages, err := buildPushMessage(ctx, record)
var status enums.UserNotificationStatus
Expand Down

0 comments on commit 0dabed2

Please sign in to comment.