Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't update optOut for AWS Pinpoint endpoint without address (APNs device token) #13272

Open
3 tasks done
victoravr opened this issue Apr 21, 2024 · 15 comments
Open
3 tasks done
Assignees
Labels
bug Something isn't working Push Notifications Related to Push Notification components

Comments

@victoravr
Copy link

Before opening, please confirm:

JavaScript Framework

React Native

Amplify APIs

Push Notifications

Amplify Version

v6

Amplify Categories

notifications

Backend

CDK

Environment information

  System:
    OS: macOS 14.4.1
    CPU: (8) arm64 Apple M2
    Memory: 58.22 MB / 24.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 21.7.3 - /opt/homebrew/bin/node
    Yarn: 1.22.22 - /opt/homebrew/bin/yarn
    npm: 10.5.0 - /opt/homebrew/bin/npm
    Watchman: 2024.04.15.00 - /opt/homebrew/bin/watchman
  Browsers:
    Chrome: 124.0.6367.62
    Edge: 124.0.2478.51
    Safari: 17.4.1
  npmPackages:
    @aws-amplify/react-native: ^1.0.28 => 1.0.28 
    @aws-amplify/rtn-push-notification: ^1.2.28 => 1.2.28 
    @aws-amplify/ui-react-native: ^2.1.5 => 2.1.5 
    @aws-crypto/sha256-js: ^5.2.0 => 5.2.0 (3.0.0)
    @babel/core: ^7.23.6 => 7.24.0 
    @babel/preset-env: ^7.23.6 => 7.24.0 
    @babel/preset-react: ^7.23.3 => 7.23.3 
    @babel/runtime: ^7.23.6 => 7.24.0 
    @react-native-async-storage/async-storage: ^1.23.1 => 1.23.1 
    @react-native-community/netinfo: ^11.2.1 => 11.3.1 
    @react-native-community/slider: ^4.5.0 => 4.5.0 
    @react-native-picker/picker: ^2.6.1 => 2.6.1 
    @react-native/babel-preset: 0.73.21 => 0.73.21 
    @react-native/eslint-config: 0.73.2 => 0.73.2 
    @react-native/metro-config: 0.73.5 => 0.73.5 
    @react-native/typescript-config: 0.73.1 => 0.73.1 
    @react-navigation/bottom-tabs: ^6.5.11 => 6.5.16 
    @react-navigation/drawer: ^6.6.6 => 6.6.11 
    @react-navigation/native: ^6.1.9 => 6.1.14 
    @react-navigation/stack: ^6.3.20 => 6.3.25 
    @reduxjs/toolkit: ^2.0.1 => 2.2.1 
    @reduxjs/toolkit-query:  1.0.0 
    @reduxjs/toolkit-query-react:  1.0.0 
    @reduxjs/toolkit-react:  1.0.0 
    @rneui/base: ^4.0.0-rc.7 => 4.0.0-rc.7 
    @rneui/themed: ^4.0.0-rc.8 => 4.0.0-rc.8 
    @rnx-kit/align-deps: ^2.3.1 => 2.3.4 
    @tanstack/query-codemods:  4.24.3 
    @tanstack/react-query: ^5.28.4 => 5.28.4 
    @tsconfig/react-native: ^3.0.2 => 3.0.3 
    @types/react: ^18.2.45 => 18.2.61 
    @types/react-redux: ^7.1.33 => 7.1.33 
    @types/react-test-renderer: ^18.0.7 => 18.0.7 
    HelloWorld:  0.0.1 
    aws-amplify: ^6.0.28 => 6.0.28 
    aws-amplify/adapter-core:  undefined ()
    aws-amplify/analytics:  undefined ()
    aws-amplify/analytics/kinesis:  undefined ()
    aws-amplify/analytics/kinesis-firehose:  undefined ()
    aws-amplify/analytics/personalize:  undefined ()
    aws-amplify/analytics/pinpoint:  undefined ()
    aws-amplify/api:  undefined ()
    aws-amplify/api/server:  undefined ()
    aws-amplify/auth:  undefined ()
    aws-amplify/auth/cognito:  undefined ()
    aws-amplify/auth/cognito/server:  undefined ()
    aws-amplify/auth/enable-oauth-listener:  undefined ()
    aws-amplify/auth/server:  undefined ()
    aws-amplify/data:  undefined ()
    aws-amplify/data/server:  undefined ()
    aws-amplify/datastore:  undefined ()
    aws-amplify/in-app-messaging:  undefined ()
    aws-amplify/in-app-messaging/pinpoint:  undefined ()
    aws-amplify/push-notifications:  undefined ()
    aws-amplify/push-notifications/pinpoint:  undefined ()
    aws-amplify/storage:  undefined ()
    aws-amplify/storage/s3:  undefined ()
    aws-amplify/storage/s3/server:  undefined ()
    aws-amplify/storage/server:  undefined ()
    aws-amplify/utils:  undefined ()
    babel-jest: ^29.7.0 => 29.7.0 
    babel-loader: ^9.1.3 => 9.1.3 
    detox: ^20.19.5 => 20.19.5 
    eslint: ^8.56.0 => 8.57.0 
    formik: ^2.4.5 => 2.4.5 
    ini: ^4.1.1 => 4.1.1 (1.3.8)
    inquirer: ^9.2.12 => 9.2.15 
    jest: ^29.7.0 => 29.7.0 
    metro-react-native-babel-preset: ^0.77.0 => 0.77.0 
    prettier: ^3.1.1 => 3.2.5 
    react: 18.2.0 => 18.2.0 
    react-dom: ^18.2.0 => 18.2.0 
    react-native: ^0.73.6 => 0.73.6 
    react-native-awesome-slider: ^2.4.6 => 2.5.1 
    react-native-codegen: ^0.70.7 => 0.70.7 
    react-native-fast-image: ^8.6.3 => 8.6.3 
    react-native-gesture-handler: ^2.14.0 => 2.15.0 
    react-native-get-random-values: ^1.11.0 => 1.11.0 
    react-native-image-crop-picker: ^0.40.2 => 0.40.3 
    react-native-modal: ^13.0.1 => 13.0.1 
    react-native-picker-select: ^9.0.1 => 9.0.1 
    react-native-purchases: ^7.5.1 => 7.22.0 
    react-native-reanimated: ^3.6.1 => 3.7.2 
    react-native-safe-area-context: ^4.8.2 => 4.9.0 
    react-native-screens: ^3.29.0 => 3.29.0 
    react-native-toast-message: ^2.2.0 => 2.2.0 
    react-native-url-polyfill: ^2.0.0 => 2.0.0 
    react-native-vector-icons: ^10.0.3 => 10.0.3 
    react-redux: ^9.0.4 => 9.1.0 
    react-test-renderer: 18.2.0 => 18.2.0 
    redux-persist: ^6.0.0 => 6.0.0 
    redux-persist/integration/react:  undefined ()
    redux-thunk: ^3.1.0 => 3.1.0 
    typescript: ^5.3.3 => 5.3.3 
    yup: ^1.3.3 => 1.3.3 
  npmGlobalPackages:
    @devicecloud.dev/dcd: 0.0.5
    detox-cli: 20.0.0
    ios-deploy: 1.12.2
    npm: 10.5.0


Describe the bug

Can't update optOut for AWS Pinpoint endpoint without address (APNs device token)

Expected behavior

Should be able to update optOut (e.g. set to 'NONE') for AWS Pinpoint endpoint without address (APNs device token)

Reproduction steps

call identifyUser() after user logs in.
If no device token provided along with optOut option as address, console warning appears as in screenshot.

  options: {
    address: deviceToken,
    optOut: 'NONE'
  },

Code Snippet

import {
  identifyUser,
} from 'aws-amplify/push-notifications';
...

const identifyUserInput =  {
      userId: userId,
      userProfile: {
        customProperties: userInfo,
      },
      options: {
        optOut: 'NONE'
      },
    }

await identifyUser(identifyUserInput);

Log output

// Put your logs below this line


aws-exports.js

No response

Manual configuration

No response

Additional configuration

No response

Mobile Device

iPhone 15 Pro + Simulator

Mobile Operating System

iOS 17.4

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

image
@victoravr victoravr added the pending-triage Issue is pending triage label Apr 21, 2024
@cwomack cwomack self-assigned this Apr 22, 2024
@cwomack cwomack added Push Notifications Related to Push Notification components bug Something isn't working and removed pending-triage Issue is pending triage labels Apr 22, 2024
@cwomack
Copy link
Contributor

cwomack commented Apr 22, 2024

Hello, @victoravr and thank you for opening this issue. Can you confirm if you also see this happen when you set optOut to ALL as well? Also, are you calling initializePushNotifications() before your identifyUser() call?

This does seem to be a bug and we'll investigate this as such. The address should not be required per Pinpoint's documentation.

@cwomack cwomack added the pending-response Issue is pending response from the issue requestor label Apr 22, 2024
@cwomack cwomack removed their assignment Apr 22, 2024
@cshfang
Copy link
Contributor

cshfang commented Apr 22, 2024

Hi @victoravr. As @cwomack mentioned, we are currently treating this as a bug, however I am unable to reproduce the error via the steps below:

  1. Start my RN app on iOS simulator
  2. Call identifyUser with optOut: 'ALL'
  3. Call identifyUser with optOut: 'NONE'

Could you please share more of your use case with us so we can better reproduce the circumstances around which you are seeing the issue? It would also be helpful if you could use Flipper or something similar to inspect the actual network calls being made? In particular - we want to be sure that the endpointId of the calls are consistent between calls E.g.

// On app start, request is made automatically to 
// https://pinpoint.<region>.amazonaws.com/v1/apps/123/endpoints/456
{
  RequestId: "abc123",
  ...
  Address: "my-token-string",
  ...
}
// On identifyUser call, request is made to (same endpoint)
// https://pinpoint.<region>.amazonaws.com/v1/apps/123/endpoints/456
{
  RequestId: "xyz789",
  ...
  OptOut: "ALL",
  ...
}

@victoravr
Copy link
Author

Hello, @victoravr and thank you for opening this issue. Can you confirm if you also see this happen when you set optOut to ALL as well? Also, are you calling initializePushNotifications() before your identifyUser() call?

Sorry, haven't tried setting optOut to ALL yet. Will try it soon (need to set it to ALL for the endpoint upon user sign-out I guess) and will report back.

And yes, initializePushNotifications() is called before identifyUser() call.

@github-actions github-actions bot removed the pending-response Issue is pending response from the issue requestor label Apr 22, 2024
@victoravr
Copy link
Author

Hi @victoravr. As @cwomack mentioned, we are currently treating this as a bug, however I am unable to reproduce the error via the steps below:

  1. Start my RN app on iOS simulator
  2. Call identifyUser with optOut: 'ALL'
  3. Call identifyUser with optOut: 'NONE'

Could you please share more of your use case with us so we can better reproduce the circumstances around which you are seeing the issue? It would also be helpful if you could use Flipper or something similar to inspect the actual network calls being made? In particular - we want to be sure that the endpointId of the calls are consistent between calls E.g.

// On app start, request is made automatically to 
// https://pinpoint.<region>.amazonaws.com/v1/apps/123/endpoints/456
{
  RequestId: "abc123",
  ...
  Address: "my-token-string",
  ...
}
// On identifyUser call, request is made to (same endpoint)
// https://pinpoint.<region>.amazonaws.com/v1/apps/123/endpoints/456
{
  RequestId: "xyz789",
  ...
  OptOut: "ALL",
  ...
}

Hmm, could you try to reproduce the error on the physical device, if possible? I have noticed this error while testing on iPhone Xr with iOS 17.4.1.

@victoravr
Copy link
Author

OK, I have updated identifyUser() call to set optOut to 'ALL' upon user sign-out.

During testing this time I can't reproduce the reported error - neither for 'NONE', nor for 'ALL' optOut without address.

If you can't reproduce it on the physical device too, happy to close this issue - maybe it was a bug in my code.

Thanks!

@cwomack cwomack added question General question and removed bug Something isn't working labels Apr 23, 2024
@cwomack
Copy link
Contributor

cwomack commented Apr 23, 2024

Appreciate the confirmation, @victoravr. We haven't been able to reproduce it on out side either following the documented flows. We'll close this issue out for now, but feel free to reply back if you happen to find a way to reproduce it reliably again!

@cwomack cwomack closed this as completed Apr 23, 2024
@victoravr
Copy link
Author

victoravr commented Apr 28, 2024

Hi @cwomack and @cshfang,

Getting this error again! :(
This time I've installed Reactotron for debugging.
Here's API request:

RequestId:4fd0c28e-4af5-46dd-a795-aeb765af7cc2
EffectiveDate:2024-04-28T00:53:07.680Z
ChannelType:APNS_SANDBOX
Attributes:
AppVersion:ios/17.4
Make:null
Model:null
ModelVersion:17.4
Platform:ios
Location:
OptOut:NONE
UserId:c9ae5488-2001-70f4-13d8-24ff71a0ff45
image

And response:
Message
Missing Address
image
image

Is it a bug?

@cshfang
Copy link
Contributor

cshfang commented Apr 28, 2024

Hmm is this the first network call to Pinpoint being made by the application? When initializing push notifications, there should be a call made automatically to bootstrap the endpoint with the token (address).

Would it be possible to add a onTokenReceived handler to check if the token is being received? How early on is the initializePushNotifications call being made in the lifecycle of your application?

What is supposed to happen is:

  1. When remote notification is registered, a token received native event is queued
  2. When initializePushNotifications is called, listeners for all native events are wired up, flushing the aforementioned queue
  3. The token received native event is picked up by the JS side
  4. An endpoint is created/updated with the token as the address

It seems like somehow step 4 either did not trigger or did not complete in time 🤔 so it would be helpful to know if there are any other calls being made by the library at all..

@victoravr
Copy link
Author

victoravr commented Apr 29, 2024

Thanks for prompt reply, @cshfang!

Hmm is this the first network call to Pinpoint being made by the application? When initializing push notifications, there should be a call made automatically to bootstrap the endpoint with the token (address).

Yes, looks like it was the first network call to Pinpoint made by the app since launch.

Would it be possible to add a onTokenReceived handler to check if the token is being received? How early on is the initializePushNotifications call being made in the lifecycle of your application?

I've got onTokenReceived used inside useEffect of the React component, as below:

import React, {useEffect} from 'react';
import {
  onTokenReceived,
  OnTokenReceivedInput,
  OnTokenReceivedOutput,
} from 'aws-amplify/push-notifications';
import {setDeviceToken} from './src/redux/deviceToken';
import {useDispatch} from 'react-redux';

export const RegisterToken = () : React.JSX.Element => {
  const dispatch = useDispatch();

  useEffect(() => {
    const tokenReceivedHandler: OnTokenReceivedInput = token => {
      // Do something with the received token
      console.log(token, 'token');
      dispatch(setDeviceToken(token));
    };

    const tokenListener: OnTokenReceivedOutput =
      onTokenReceived(tokenReceivedHandler);

    return () => {
      tokenListener.remove();
    };
  }, []);
  // @ts-ignore
  return <></>;
};

then in App.tsx:

...
import {RegisterToken} from './RegisterToken';
...
  // @ts-ignore
  return (
    <>
      <Provider store={store}>
        <PersistGate loading={<Text>Loading...</Text>} persistor={persistor}>
          <QueryClientProvider client={queryClient}>
            <SafeAreaProvider>
              <ThemeProvider theme={theme}>
                <RegisterToken />
                <AppNavigation />
              </ThemeProvider>
            </SafeAreaProvider>
          </QueryClientProvider>
        </PersistGate>
      </Provider>
      <Toast />
    </>
  );
...

Is this approach supported?

@cshfang
Copy link
Contributor

cshfang commented Apr 29, 2024

Hey @victoravr.

Yes, looks like it was the first network call to Pinpoint made by the app since launch.

Is there a network call made afterwards?

Where in the application are you calling initializePushNotifications and identifyUser? It seems like perhaps the identifyUser network call is happening between the endpoint has a chance to get the token registered. This ought not to happen and I'm hoping to better understand why it's happening in your application flow

@cshfang
Copy link
Contributor

cshfang commented Apr 29, 2024

After testing this out, I do think we have a potential race condition if initializePushNotifications and identifyUser are called closely in sequence due to the fact that they are both trying to update a Pinpoint endpoint behind the scenes. There are a couple issues here:

  1. When creating an endpoint internally, both calls attempt to retrieve some cached endpoint - but if they are called back-to-back, the asynchronous nature of our cache lookup causes a potential for different endpoint IDs to be created. I am reopening this issue as a result of this and will track as a bug.
  2. Even with the above resolved, it appears possible for the network call associated to an identifyUser call to end up earlier in sequence than the one triggered by initializePushNotifications depending on your application. As a result, if an endpoint is created/updated with OptOut set to 'NONE' before the address is set by initializePushNotifications, you can run into the issue you're seeing above.

Issue 2 is a bit hairier to resolve as it is not clear to me why the Pinpoint service does not allow the use case of opting an endpoint out before attaching an address to it. So I am in contact with the Pinpoint team regarding this. Pending their response, I will work on evaluating possible solutions to this.

In the meantime, since you are listening for a token, I think a potential fix in the meantime could be to pass that token in along with your identifyUser call as the address.

@cshfang cshfang reopened this Apr 29, 2024
@cwomack cwomack added bug Something isn't working and removed question General question labels Apr 29, 2024
@victoravr
Copy link
Author

Hi @cshfang,

Is there a network call made afterwards?

Yes, looks like there was a call after that with address:

{
  "RequestId": "7d62b64a-bcca-48cd-872f-db17b3bcdca3",
  "EffectiveDate": "2024-04-28T01:04:49.043Z",
  "ChannelType": "APNS_SANDBOX",
  "Address": "80042cfb1a57d0426ad71ebe50...663931ca806",
  "Demographic": {
    "AppVersion": "ios/17.4",
    "Make": null,
    "Model": null,
    "ModelVersion": "17.4",
    "Platform": "ios"
  },
  "Location": {},
  "User": {
    "UserId": "ap-southeast-2:7de8bcb7-b604-cd23-5116-5211a83a494f"
  }
}

Where in the application are you calling initializePushNotifications and identifyUser? It seems like perhaps the identifyUser network call is happening between the endpoint has a chance to get the token registered. This ought not to happen and I'm hoping to better understand why it's happening in your application flow

I'm calling initializePushNotifications(); in root index.js after Amplify.configure(...); but before AppRegistry.registerComponent(appName, () => App);

As for identifyUser, it's inside useEffect in AppNavigation.js:

...
  useEffect(() => {
    if (user) {
      if (user.userId) {
        identifyUsertoPinpoint({
          deviceToken: deviceToken,
          userId: user.userId,
          optOut: 'NONE'
        });
      };
    };
  }, [user, deviceToken]);
...

@cshfang
Copy link
Contributor

cshfang commented Apr 30, 2024

...
  useEffect(() => {
    if (user) {
      if (user.userId) {
        identifyUsertoPinpoint({
          deviceToken: deviceToken,
          userId: user.userId,
          optOut: 'NONE'
        });
      };
    };
  }, [user, deviceToken]);
...

I think the network call happening out of order is consistent with my findings. However, I noticed that you are already sending the device token along with your identifyUser call. I don't know what the exact body of your identifyUsertoPinpoint function looks like - but two things we want to make sure of are:

  1. That deviceToken is passed along with the identifyUser call e.g. options: { address: deviceToken, optOut }
  2. That the address is never explicitly an empty string (this might cause weirdness with the service API)

@victoravr
Copy link
Author

... make sure of are:

  1. That deviceToken is passed along with the identifyUser call e.g. options: { address: deviceToken, optOut }
  2. That the address is never explicitly an empty string (this might cause weirdness with the service API)

Thanks! Updated my code to only do API request with OptOut if deviceToken is not empty/null.

Question: when device token is sent to the end user device/simulator for APNs (e.g. in a Sandbox)?

At least on simulator, I've noticed it does not appear at all sometimes...

@cshfang
Copy link
Contributor

cshfang commented May 6, 2024

Question: when device token is sent to the end user device/simulator for APNs (e.g. in a Sandbox)?

At least on simulator, I've noticed it does not appear at all sometimes...

It should be invoked when didRegisterForRemoteNotificationsWithDeviceToken is invoked in the AppDelegate - so essentially when an app is launched for the first time. On simulator the token is only reliably passed to the JS side after terminating the app, relaunching, and then doing an additional background + resume. It seems to be another quirk with the simulator at this time though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working Push Notifications Related to Push Notification components
Projects
None yet
Development

No branches or pull requests

3 participants