amplify-js
amplify-js copied to clipboard
Removing Endpoint From User
Before opening, please confirm:
- [X] I have searched for duplicate or closed issues and discussions.
- [X] I have read the guide for submitting bug reports.
- [X] I have done my best to include a minimal, self-contained set of instructions for consistently reproducing the issue.
JavaScript Framework
React Native
Amplify APIs
Push Notifications
Amplify Version
v6
Amplify Categories
No response
Backend
None
Environment information
System:
OS: macOS 13.6.1
CPU: (8) arm64 Apple M1
Memory: 111.48 MB / 16.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 20.13.1 - ~/.nvm/versions/node/v20.13.1/bin/node
Yarn: 1.22.17 - /usr/local/bin/yarn
npm: 10.8.0 - ~/.nvm/versions/node/v20.13.1/bin/npm
pnpm: 8.15.6 - /opt/homebrew/bin/pnpm
Watchman: 2024.04.01.00 - /opt/homebrew/bin/watchman
Browsers:
Chrome: 126.0.6478.127
Safari: 16.6
npmPackages:
@apollo/client: ^3.7.17 => 3.10.8
@apollo/client/cache: undefined ()
@apollo/client/core: undefined ()
@apollo/client/dev: undefined ()
@apollo/client/errors: undefined ()
@apollo/client/link/batch: undefined ()
@apollo/client/link/batch-http: undefined ()
@apollo/client/link/context: undefined ()
@apollo/client/link/core: undefined ()
@apollo/client/link/error: undefined ()
@apollo/client/link/http: undefined ()
@apollo/client/link/persisted-queries: undefined ()
@apollo/client/link/remove-typename: undefined ()
@apollo/client/link/retry: undefined ()
@apollo/client/link/schema: undefined ()
@apollo/client/link/subscriptions: undefined ()
@apollo/client/link/utils: undefined ()
@apollo/client/link/ws: undefined ()
@apollo/client/react: undefined ()
@apollo/client/react/components: undefined ()
@apollo/client/react/context: undefined ()
@apollo/client/react/hoc: undefined ()
@apollo/client/react/hooks: undefined ()
@apollo/client/react/internal: undefined ()
@apollo/client/react/parser: undefined ()
@apollo/client/react/ssr: undefined ()
@apollo/client/testing: undefined ()
@apollo/client/testing/core: undefined ()
@apollo/client/testing/experimental: undefined ()
@apollo/client/utilities: undefined ()
@apollo/client/utilities/globals: undefined ()
@apollo/client/utilities/subscriptions/relay: undefined ()
@apollo/client/utilities/subscriptions/urql: undefined ()
@aws-amplify/react-native: ^1.1.1 => 1.1.1
@aws-amplify/rtn-push-notification: ^1.2.29 => 1.2.29
@babel/core: ^7.20.0 => 7.24.7
@babel/preset-env: ^7.20.0 => 7.24.7
@babel/runtime: ^7.20.0 => 7.24.7
@commitlint/cli: ^12.1.4 => 12.1.4
@commitlint/config-conventional: ^12.1.4 => 12.1.4
@commitlint/cz-commitlint: ^12.1.4 => 12.1.4
@datadog/mobile-react-native: ^2.3.2 => 2.3.6
@datadog/mobile-react-navigation: ^2.3.2 => 2.3.6
@eva-design/eva: 2.0.0 => 2.0.0
@graphql-codegen/add: ^4.0.0 => 4.0.1
@graphql-codegen/cli: ^3.0.0 => 3.3.1
@graphql-codegen/typescript: ^3.0.0 => 3.0.4
@graphql-codegen/typescript-operations: ^3.0.0 => 3.0.4
@graphql-codegen/typescript-react-apollo: ^3.3.7 => 3.3.7
@intercom/intercom-react-native: 7.1.3 => 7.1.3
@onfido/react-native-sdk: ^12.1.0 => 12.2.0
@react-native-async-storage/async-storage: ^1.23.1 => 1.23.1
@react-native-clipboard/clipboard: ^1.10.0 => 1.14.1
@react-native-community/hooks: ^2.8.0 => 2.8.1
@react-native-community/push-notification-ios: ^1.10.1 => 1.11.0
@react-native-community/slider: ^4.4.2 => 4.5.2
@react-native/babel-preset: 0.73.21 => 0.73.21 (0.74.85)
@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.75.0-main => 0.75.0-rc.3
@react-navigation/bottom-tabs: ^6.5.20 => 6.6.0
@react-navigation/drawer: ^6.6.15 => 6.7.0
@react-navigation/elements: ^1.3.30 => 1.3.30
@react-navigation/native: ^6.1.17 => 6.1.17
@react-navigation/stack: ^6.3.29 => 6.4.0
@types/bech32: ^1.1.4 => 1.1.4
@types/bip21: ^2.0.0 => 2.0.3
@types/d3-scale: ^4.0.1 => 4.0.8
@types/d3-shape: ^3.0.2 => 3.1.6
@types/jest: ^27.4.1 => 27.5.2
@types/lodash: ^4.14.170 => 4.17.6
@types/react: ^18.2.6 => 18.3.3
@types/react-native-push-notification: ^7.3.2 => 7.3.3
@types/react-native-snap-carousel: ^3.8.3 => 3.8.11
@types/react-native-video: ^5.0.10 => 5.0.20
@types/react-test-renderer: ^18.0.0 => 18.3.0
@types/semver: ^7.3.10 => 7.5.8
@ui-kitten/components: 4.4.1 => 4.4.1
@ui-kitten/eva-icons: ^4.4.1 => 4.4.1
HelloWorld: 0.0.1
apollo-link-timeout: ^4.0.0 => 4.0.0
apollo3-cache-persist: ^0.14.1 => 0.14.1
aws-amplify: 6.3.4 => 6.3.4
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 ()
aws-appsync-auth-link: ^3.0.4 => 3.0.7
axios: ^0.24.0 => 0.24.0 (1.7.2)
babel-jest: ^29.6.3 => 29.7.0
babel-plugin-module-resolver: ^5.0.0 => 5.0.2
babel-plugin-transform-remove-console: ^6.9.4 => 6.9.4
bech32: ^2.0.0 => 2.0.0
bignumber.js: ^9.0.2 => 9.1.2
bip21: ^2.0.3 => 2.0.3
bitcoin-address-validation: ^2.1.0 => 2.2.3
bitcoin-units: ^0.3.0 => 0.3.0
commitizen: ^4.2.4 => 4.3.0
country-iso-2-to-3: ^1.1.0 => 1.1.0
d3-scale: ^4.0.2 => 4.0.2 (2.2.2)
d3-shape: ^3.0.1 => 3.2.0
date-fns: ^2.28.0 => 2.30.0
deepl-node: ^1.7.1 => 1.13.0
deprecated-react-native-prop-types: ^4.0.0 => 4.2.3 (2.3.0, 5.0.0)
eslint: ^8.19.0 => 8.57.0
eslint-plugin-ft-flow: ^2.0.3 => 2.0.3
example: 0.0.1
expo: ^50.0.14 => 50.0.19
expo-barcode-scanner: 12.9.3 => 12.9.3
expo-blur: 12.9.2 => 12.9.2
expo-camera: ^14.1.3 => 14.1.3
expo-file-system: ^16.0.9 => 16.0.9
expo-local-authentication: 13.8.0 => 13.8.0
expo-sharing: 11.10.0 => 11.10.0
fast-text-encoding: ^1.0.6 => 1.0.6
fior-tracking: ^0.1.1 => 0.1.3
global: ^4.4.0 => 4.4.0
graphql: ^16.6.0 => 16.9.0 (15.8.0)
graphql-tag: ^2.12.4 => 2.12.6
husky: ^6.0.0 => 6.0.0
i18next: ^20.3.0 => 20.6.1
jest: ^29.6.3 => 29.7.0
libphonenumber-js: ^1.10.54 => 1.11.4
libphonenumber-js/build: undefined ()
libphonenumber-js/core: undefined ()
libphonenumber-js/max: undefined ()
libphonenumber-js/max/metadata: undefined ()
libphonenumber-js/min: undefined ()
libphonenumber-js/min/metadata: undefined ()
libphonenumber-js/mobile: undefined ()
libphonenumber-js/mobile/examples: undefined ()
libphonenumber-js/mobile/metadata: undefined ()
light-bolt11-decoder: ^3.0.0 => 3.1.1
lodash: ^4.17.21 => 4.17.21
lottie-react-native: ^6.7.2 => 6.7.2
metro-minify-terser: ^0.78.1 => 0.78.1 (0.80.9)
metro-react-native-babel-preset: ^0.77.0 => 0.77.0
metro-react-native-babel-transformer: ^0.77.0 => 0.77.0
patch-package: ^6.4.7 => 6.5.1
postinstall-postinstall: ^2.1.0 => 2.1.0
prettier: 2.8.8 => 2.8.8
prompt-sync: ^4.2.0 => 4.2.0
proxy-polyfill: ^0.3.2 => 0.3.2
query-string: ^7.1.1 => 7.1.3
react: 18.2.0 => 18.2.0
react-content-loader: ^6.0.3 => 6.2.1
react-content-loader/native: undefined ()
react-hook-form: ^7.43.1 => 7.52.1
react-i18next: ^11.10.0 => 11.18.6
react-native: 0.73.8 => 0.73.8 (0.74.3)
react-native-actions-sheet: ^0.8.29 => 0.8.29
react-native-bootsplash: 4.7.5 => 4.7.5
react-native-branch: ^6.2.1 => 6.2.2
react-native-circular-progress: ^1.3.7 => 1.4.0
react-native-cli-bump-version: ^1.5.0 => 1.5.0
react-native-code-push: ^8.1.0 => 8.2.2
react-native-collapsible: ^1.6.0 => 1.6.1
react-native-config: ^1.5.1 => 1.5.2
react-native-confirmation-code-field: ^7.1.0 => 7.4.0
react-native-device-country: ^1.0.3 => 1.0.4
react-native-device-info: ^10.13.2 => 10.14.0
react-native-event-listeners: ^1.0.7 => 1.0.7
react-native-flags: ^1.0.0 => 1.0.0
react-native-gesture-handler: 2.16.0 => 2.16.0
react-native-get-random-values: ^1.11.0 => 1.11.0
react-native-google-places-autocomplete: ^2.4.1 => 2.5.6
react-native-haptic-feedback: ^1.14.0 => 1.14.0
react-native-in-app-review: ^4.3.3 => 4.3.3
react-native-inappbrowser-reborn: ^3.7.0 => 3.7.0
react-native-iphone-x-helper: ^1.3.1 => 1.3.1
react-native-keychain: ^8.1.0 => 8.2.0
react-native-modal: ^13.0.0 => 13.0.1
react-native-push-notification: ^8.1.1 => 8.1.1
react-native-reanimated: ^3.9.0 => 3.13.0
react-native-safe-area-context: ^4.7.1 => 4.10.7
react-native-screens: 3.30.1 => 3.30.1
react-native-snap-carousel: ^3.9.1 => 3.9.1
react-native-startup-time: ^2.0.0 => 2.1.0
react-native-svg: 13.9.0 => 13.9.0
react-native-svg-transformer: ^1.1.0 => 1.4.0
react-native-toast-message: ^2.2.0 => 2.2.0
react-native-video: ^5.2.1 => 5.2.1
react-native-wagmi-charts: 2.3.2 => 2.3.2
react-navigation-header-buttons: ^7.0.1 => 7.0.2
react-query: ^3.33.4 => 3.39.3
react-test-renderer: 18.2.0 => 18.2.0
recoil: ^0.7.4 => 0.7.7
semver: ^7.3.7 => 7.6.2 (6.3.1, 7.3.5, 5.7.2, 7.5.3, 7.3.2)
typescript: 5.0.4 => 5.0.4
url: ^0.11.3 => 0.11.3
url-pattern: ^1.0.3 => 1.0.3
npmGlobalPackages:
corepack: 0.28.0
npm: 10.8.0
Describe the bug
How do we remove an endpoint from a user in push notifications?
Currently, to deregister a user and prevent him from receiving notification, I call this function, where optOut: 'ALL' ensures that no more notifications are sent:
await PushNotification.identifyUser({
userId: userAttributes?.sub || '',
userProfile: {
(...)
},
options: {
address: deviceToken?.token,
optOut: 'ALL',
},
});
The issue with this approach is that while it works, you can only have a maximum of 15 endpoints per user. And if a user signs in & out multiple times, the endpoints get cluttered, and you reach a limit very soon.
What's the best approach to remove an endpoint from a user?
Seems like I'm not the only one facing this issue:
- https://stackoverflow.com/questions/78361238/how-can-i-unsubscribe-user-from-apns-push-notifications-from-pinpoint-upon-sign
- https://github.com/aws-amplify/amplify-flutter/issues/3721
- https://github.com/aws-amplify/amplify-flutter/issues/3179
Expected behavior
Have function that simply allows you to remove/delete an endpoint on sign out.
Reproduction steps
Documentation.
Code Snippet
// Put your code below this line.
Log output
// Put your logs below this line
aws-exports.js
No response
Manual configuration
No response
Additional configuration
No response
Mobile Device
No response
Mobile Operating System
No response
Mobile Browser
No response
Mobile Browser Version
No response
Additional information and screenshots
No response
Hi @julian-dotcom, thank you for opening this issue. We are definitely aware there are some sharp edges to using these APIs and I'll try to explain what's happening under the hood a bit.
How do we remove an endpoint from a user in push notifications?
An endpoint can be thought of as the combination of a device + notification channel. E.g., all of these would be separate endpoints:
- My iPhone + Push notification
- My iPhone + In-app messaging
- My Android + Push notification
- My Android + In-app messaging
Under the hood, what Amplify is doing on each device when you use a specific channel's APIs is creating a unique endpoint for that device + channel combination. So if you want remove an endpoint from a user, you are actually looking to associate their device + channel combination to... some other user id (e.g. '')
The issue with this approach is that while it works, you can only have a maximum of 15 endpoints per user. And if a user signs in & out multiple times, the endpoints get cluttered, and you reach a limit very soon.
As mentioned above, an endpoint should be tied to a specific device + channel combination - so as your user signs and an out (and you're calling identifyUser each time) - what should be happening is that the same endpoint is being overwritten rather than new ones being created. E.g.
- User signs in for the first time and uses push notifications - New endpoint
ABCis created - User signs out (and you call identifyUser) - Endpoint
ABCis updated but not recreated - User signs in again (and you call identifyUser) - Endpoint
ABCis updated but not recreated
If your user is signing in on 15 different devices or reinstalling your app 15 different times - then 15 separate endpoints for your user would be created. However, this should still not be an issue because the Pinpoint service is supposed to automatically mark the least active push channel type endpoint as "inactive" - thus not counting against the 15 maximum.
Given the above, could you help us understand better how you are running into the maximum endpoints issue? I am providing some background above but I would still need to understand your particular circumstances to narrow down the source of the issue.
Wow, thank you for this explanation. I'll check in a bit.
Unfortunately, this is not working as expected.
When a user logs in, this is how I identify the endpoint:
await PushNotification.identifyUser({
userId: userAttributes.sub,
userProfile: {
customProperties: {
locale: [userAttributes.locale],
},
},
options: { address: deviceToken.token, optOut: 'NONE' },
});
When a user logs out, this is how I attempt to deregister the endpoint:
await PushNotification.identifyUser({
userId: '',
userProfile: {},
options: {
address: deviceToken?.token,
optOut: 'ALL',
},
});
Unfortunately, this doesn't work as expected, where the following things go wrong:
- The
userIdis never wiped from the endpoint. The only thing that successfully changes isoptOutfromNONEtoALL - When I log the user back in with the same device token, a new endpoint is created.
- Because a new endpoint is created every time I sign in (even with an identical token), I reach the limit of 15.
Thanks and looking forward to the response.
Hi @julian-dotcom, what I suspect might be happening is the wrong combination of userId, address and optOut values. Can you try the below code?
When a user logs in, identify the endpoint:
await PushNotification.identifyUser({
userId: userAttributes.sub,
userProfile: {
customProperties: {
locale: [userAttributes.locale],
},
},
options: {
address: deviceToken.token, // make sure there is an deviceToken present here.
optOut: 'NONE'
},
});
When a user logs out, attempt to deregister the endpoint:
await PushNotification.identifyUser({
userId: userAttributes.sub,
userProfile: {},
options: {
address: '',
optOut: 'ALL',
},
});
Basing it on this Pinpoint documentation.
@Samaritan1011001, thank you so much for the suggestion. I tried exactly as you suggested, but the following happens after logging out:
- The
addressis removed from the endpoint optOutis set toALL- The userId persists and the endpoint is still present when querying all endpoints for a userId
EndpointStatusstays active
Using your above methodology, by logging in and out 15 times, I hit the threshold and can no longer create endpoints.
This really sucks, how come there is no clear way to remove an endpoint from a user?
@julian-dotcom sorry that did not work. Can you try this slightly different code. In my testing it removed the endpoint from the segment that had a criteria set to the customProperty value. I am assuming it means Pinpoint disassociated the endpoint from that user.
After log in ,
await identifyUser({
userId: 'user1',
userProfile: {customProperties: {testProperty: ['test']}},
});
After log out,
await identifyUser({
userId: 'user1',
userProfile: {customProperties: {testProperty: ['test']}},
options: {address: undefined, optOut: 'ALL'},
});
@Samaritan1011001, thanks again for your time and energy.
Unfortunately, the suggested above also will not work.
-
For the login identification you suggested, you're missing the device token / address. This has to be submitted, or else the endpoint is not specific to the device.
-
When I try the above method for logging out, the endpoint unfortunately persists.
I also tried it like this, where I do pass the device token, but to no avail.
log in:
await PushNotification.identifyUser({
userId: userAttributes.sub,
userProfile: { customProperties: { testProperty: ['test'] } },
options: { address: deviceToken.token, optOut: 'NONE' },
});
}
log out:
await PushNotification.identifyUser({
userId: sub,
userProfile: { customProperties: { testProperty: ['test'] } },
options: {
address: undefined,
optOut: 'ALL',
},
});
Thanks a lot! Any ideas?
Just for my understanding, why would your above suggested method work?
In one of your comments above, you said that the way to remove an endpoint from a user is to associate the device with another user id:
So if you want remove an endpoint from a user, you are actually looking to associate their device + channel combination to... some other user id (e.g. '')
But what you suggested in your most recent comment is to use the userId in both calls.
@julian-dotcom, this seems to be a limitation of the Pinpoint service to be able to remove a user from an endpoint. We're reaching out to the Pinpoint team to confirm if this is the case, and will provide an update as soon as we can.
@julian-dotcom, we appreciate your patience on a response.
Unfortunately, we do not currently have an API that allows for the direct removal of a user from an endpoint. The way Pinpoint's system is designed, once a user is attached to an endpoint via the updateEndpoint operation, that endpoint will always have a user associated with it. The only workaround we can suggest is to change the userId field for the endpoint to a different ID or an empty string. This would effectively “remove” the user, but the endpoint would still retain a user association. We understand this may not be an ideal solution, and we apologize for the inconvenience. The Pinpoint team is continuously working to improve their APIs and functionality, and we appreciate you bringing this issue to our attention. Please let us know if you have any other questions or concerns.
To add some more info here.
- Using the @aws-sdk/[email protected], the endpoint replacement behavior is as described in this comment by @cshfang. Here is a reference to the pinpoint guideline describing the same behavior. One thing I noticed here, the endpoint gets set to inactive and purged of the user id, when the endpoint length for a user is 14. I was not able to reaching 15 endpoints for a user.
- With [email protected] even if all the endpoints are of channelType APNS and the new endpoint would also be APNS, the identifyUser and in turn the updateEndpoint call results in a
BadRequestException Exceeded maximum endpoint per user count:15when reinstalling the app 15 times. This isn't inline with the js pinpoint client behavior nor how the behavior is described in the pinpoint developer guide.