diff --git a/CHANGELOG.md b/CHANGELOG.md index f0b2f4c33..812560427 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## Unreleased + +### Updates + +- Added `Iterable.disableDeviceForAllUsers()` to unregister this device's push token from every user associated with the device (SDK-550). + - iOS: forwards to native `IterableAPI.disableDeviceForAllUsers()`. + - Android: graceful no-op that logs a warning; use `disableDeviceForCurrentUser()` to disable push for the current user. There is no public native "all users" equivalent. + ## 3.1.0 ### Fixes diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index b25432d4b..ea7bae18b 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -373,6 +373,10 @@ public void disableDeviceForCurrentUser() { IterableApi.getInstance().disablePush(); } + public void disableDeviceForAllUsers() { + IterableLogger.w(TAG, "disableDeviceForAllUsers is not supported on Android; use disableDeviceForCurrentUser. There is no public native equivalent."); + } + public void registerDeviceToken(String token) { IterableLogger.v(TAG, "registerDeviceToken"); IterableApi.getInstance().registerDeviceToken(token); diff --git a/android/src/newarch/java/com/RNIterableAPIModule.java b/android/src/newarch/java/com/RNIterableAPIModule.java index fb71142b8..b25c39362 100644 --- a/android/src/newarch/java/com/RNIterableAPIModule.java +++ b/android/src/newarch/java/com/RNIterableAPIModule.java @@ -137,6 +137,11 @@ public void disableDeviceForCurrentUser() { moduleImpl.disableDeviceForCurrentUser(); } + @Override + public void disableDeviceForAllUsers() { + moduleImpl.disableDeviceForAllUsers(); + } + @Override public void registerDeviceToken(String token) { moduleImpl.registerDeviceToken(token); diff --git a/android/src/oldarch/java/com/RNIterableAPIModule.java b/android/src/oldarch/java/com/RNIterableAPIModule.java index c2f9b3161..cb290f3e4 100644 --- a/android/src/oldarch/java/com/RNIterableAPIModule.java +++ b/android/src/oldarch/java/com/RNIterableAPIModule.java @@ -138,6 +138,11 @@ public void disableDeviceForCurrentUser() { moduleImpl.disableDeviceForCurrentUser(); } + @ReactMethod + public void disableDeviceForAllUsers() { + moduleImpl.disableDeviceForAllUsers(); + } + @ReactMethod public void registerDeviceToken(String token) { moduleImpl.registerDeviceToken(token); diff --git a/ios/RNIterableAPI/RNIterableAPI.mm b/ios/RNIterableAPI/RNIterableAPI.mm index 965b2d81e..a766a6814 100644 --- a/ios/RNIterableAPI/RNIterableAPI.mm +++ b/ios/RNIterableAPI/RNIterableAPI.mm @@ -230,6 +230,10 @@ - (void)disableDeviceForCurrentUser { [_swiftAPI disableDeviceForCurrentUser]; } +- (void)disableDeviceForAllUsers { + [_swiftAPI disableDeviceForAllUsers]; +} + - (void)registerDeviceToken:(NSString *)token { [_swiftAPI registerDeviceToken:token]; } @@ -516,6 +520,10 @@ - (void)wakeApp { [_swiftAPI disableDeviceForCurrentUser]; } +RCT_EXPORT_METHOD(disableDeviceForAllUsers) { + [_swiftAPI disableDeviceForAllUsers]; +} + RCT_EXPORT_METHOD(registerDeviceToken : (NSString *)token) { [_swiftAPI registerDeviceToken:token]; } diff --git a/ios/RNIterableAPI/ReactIterableAPI.swift b/ios/RNIterableAPI/ReactIterableAPI.swift index a216a0e69..26def081d 100644 --- a/ios/RNIterableAPI/ReactIterableAPI.swift +++ b/ios/RNIterableAPI/ReactIterableAPI.swift @@ -144,6 +144,12 @@ import React IterableAPI.disableDeviceForCurrentUser() } + @objc(disableDeviceForAllUsers) + public func disableDeviceForAllUsers() { + ITBInfo() + IterableAPI.disableDeviceForAllUsers() + } + @objc(registerDeviceToken:) public func registerDeviceToken(token: String) { ITBInfo() diff --git a/src/__mocks__/MockRNIterableAPI.ts b/src/__mocks__/MockRNIterableAPI.ts index 60013bdbb..68355d2a1 100644 --- a/src/__mocks__/MockRNIterableAPI.ts +++ b/src/__mocks__/MockRNIterableAPI.ts @@ -34,6 +34,8 @@ export class MockRNIterableAPI { static disableDeviceForCurrentUser = jest.fn(); + static disableDeviceForAllUsers = jest.fn(); + static registerDeviceToken = jest.fn((token: string): void => { MockRNIterableAPI.token = token; }); diff --git a/src/api/NativeRNIterableAPI.ts b/src/api/NativeRNIterableAPI.ts index 7e96bc649..c902dc598 100644 --- a/src/api/NativeRNIterableAPI.ts +++ b/src/api/NativeRNIterableAPI.ts @@ -147,6 +147,7 @@ export interface Spec extends TurboModule { // Device management disableDeviceForCurrentUser(): void; + disableDeviceForAllUsers(): void; registerDeviceToken(token: string): void; getLastPushPayload(): Promise<{ [key: string]: string | number | boolean; diff --git a/src/core/classes/Iterable.test.ts b/src/core/classes/Iterable.test.ts index baccc3e41..6da4b9ca5 100644 --- a/src/core/classes/Iterable.test.ts +++ b/src/core/classes/Iterable.test.ts @@ -143,6 +143,16 @@ describe('Iterable', () => { }); }); + describe('disableDeviceForAllUsers', () => { + it('should disable the device for all users', () => { + // GIVEN no parameters + // WHEN Iterable.disableDeviceForAllUsers is called + Iterable.disableDeviceForAllUsers(); + // THEN corresponding method is called on RNIterableAPI + expect(MockRNIterableAPI.disableDeviceForAllUsers).toBeCalled(); + }); + }); + describe('registerDeviceToken', () => { it('should register the device token for the current user', () => { // GIVEN a device token diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts index aaaa84073..e7119fcbd 100644 --- a/src/core/classes/Iterable.ts +++ b/src/core/classes/Iterable.ts @@ -345,6 +345,27 @@ export class Iterable { IterableApi.disableDeviceForCurrentUser(); } + /** + * Disable this device's push token for every user associated with the device, + * not only the currently signed-in user. + * + * On iOS this forwards to native `IterableAPI.disableDeviceForAllUsers()`. + * On Android this is a no-op that logs a warning in Metro and logcat: the + * native Android SDK has no public "all users" equivalent. Use + * {@link disableDeviceForCurrentUser} to disable push for the current user + * on both platforms. The native Android method is still invoked. + * + * Fire-and-forget: native success or failure is not surfaced to JS. + * + * @example + * ```typescript + * Iterable.disableDeviceForAllUsers(); + * ``` + */ + static disableDeviceForAllUsers() { + IterableApi.disableDeviceForAllUsers(); + } + /** * Register the device's token for the current user, re-enabling push notifications. * diff --git a/src/core/classes/IterableApi.test.ts b/src/core/classes/IterableApi.test.ts index d639d79ed..b19954f73 100644 --- a/src/core/classes/IterableApi.test.ts +++ b/src/core/classes/IterableApi.test.ts @@ -3,6 +3,7 @@ import { Platform } from 'react-native'; import { MockRNIterableAPI } from '../../__mocks__/MockRNIterableAPI'; import { IterableApi } from './IterableApi'; import { IterableConfig } from './IterableConfig'; +import { IterableLogger } from './IterableLogger'; import { IterableAttributionInfo } from './IterableAttributionInfo'; import { IterableCommerceItem } from './IterableCommerceItem'; import { IterableInAppMessage } from '../../inApp/classes/IterableInAppMessage'; @@ -282,6 +283,59 @@ describe('IterableApi', () => { }); }); + describe('disableDeviceForAllUsers', () => { + it('should call RNIterableAPI.disableDeviceForAllUsers', () => { + // GIVEN no parameters + // WHEN disableDeviceForAllUsers is called + IterableApi.disableDeviceForAllUsers(); + + // THEN RNIterableAPI.disableDeviceForAllUsers is called + expect(MockRNIterableAPI.disableDeviceForAllUsers).toBeCalled(); + }); + + it('should log an unsupported warning on Android and still call native', () => { + const originalPlatform = Platform.OS; + Object.defineProperty(Platform, 'OS', { + value: 'android', + writable: true, + }); + const logSpy = jest.spyOn(IterableLogger, 'log'); + + IterableApi.disableDeviceForAllUsers(); + + expect(logSpy).toHaveBeenCalledWith( + 'disableDeviceForAllUsers is not supported on Android; use disableDeviceForCurrentUser. There is no public native equivalent.' + ); + expect(MockRNIterableAPI.disableDeviceForAllUsers).toBeCalled(); + + logSpy.mockRestore(); + Object.defineProperty(Platform, 'OS', { + value: originalPlatform, + writable: true, + }); + }); + + it('should log the method name on iOS and still call native', () => { + const originalPlatform = Platform.OS; + Object.defineProperty(Platform, 'OS', { + value: 'ios', + writable: true, + }); + const logSpy = jest.spyOn(IterableLogger, 'log'); + + IterableApi.disableDeviceForAllUsers(); + + expect(logSpy).toHaveBeenCalledWith('disableDeviceForAllUsers'); + expect(MockRNIterableAPI.disableDeviceForAllUsers).toBeCalled(); + + logSpy.mockRestore(); + Object.defineProperty(Platform, 'OS', { + value: originalPlatform, + writable: true, + }); + }); + }); + describe('registerDeviceToken', () => { it('should call RNIterableAPI.registerDeviceToken with the token', () => { // GIVEN a device token diff --git a/src/core/classes/IterableApi.ts b/src/core/classes/IterableApi.ts index 2fad862a7..4d0650102 100644 --- a/src/core/classes/IterableApi.ts +++ b/src/core/classes/IterableApi.ts @@ -137,6 +137,25 @@ export class IterableApi { return RNIterableAPI.disableDeviceForCurrentUser(); } + /** + * Disable this device's push token for every user associated with the device. + * + * iOS: forwards to native `IterableAPI.disableDeviceForAllUsers()`. + * Android: logged no-op — there is no public native "all users" equivalent. + * The warning is logged in JS (Metro) as well as native (logcat); the + * native method is still invoked. + */ + static disableDeviceForAllUsers() { + if (Platform.OS === 'android') { + IterableLogger.log( + 'disableDeviceForAllUsers is not supported on Android; use disableDeviceForCurrentUser. There is no public native equivalent.' + ); + } else { + IterableLogger.log('disableDeviceForAllUsers'); + } + return RNIterableAPI.disableDeviceForAllUsers(); + } + /** * Register the device token for the current user, re-enabling push notifications. *