expo-notifications 55.0.19 → 55.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/android/build.gradle +2 -2
  3. package/build/DevicePushTokenAutoRegistration.fx.d.ts.map +1 -1
  4. package/build/DevicePushTokenAutoRegistration.fx.js +20 -2
  5. package/build/DevicePushTokenAutoRegistration.fx.js.map +1 -1
  6. package/build/utils/updateDevicePushTokenAsync.d.ts +5 -0
  7. package/build/utils/updateDevicePushTokenAsync.d.ts.map +1 -1
  8. package/build/utils/updateDevicePushTokenAsync.js +68 -0
  9. package/build/utils/updateDevicePushTokenAsync.js.map +1 -1
  10. package/expo-module.config.json +1 -1
  11. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/{55.0.19/expo.modules.notifications-55.0.19.module → 55.0.21/expo.modules.notifications-55.0.21.module} +7 -7
  12. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/55.0.21/expo.modules.notifications-55.0.21.module.md5 +1 -0
  13. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/55.0.21/expo.modules.notifications-55.0.21.module.sha1 +1 -0
  14. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/55.0.21/expo.modules.notifications-55.0.21.module.sha256 +1 -0
  15. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/55.0.21/expo.modules.notifications-55.0.21.module.sha512 +1 -0
  16. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/{55.0.19/expo.modules.notifications-55.0.19.pom → 55.0.21/expo.modules.notifications-55.0.21.pom} +1 -1
  17. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/55.0.21/expo.modules.notifications-55.0.21.pom.md5 +1 -0
  18. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/55.0.21/expo.modules.notifications-55.0.21.pom.sha1 +1 -0
  19. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/55.0.21/expo.modules.notifications-55.0.21.pom.sha256 +1 -0
  20. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/55.0.21/expo.modules.notifications-55.0.21.pom.sha512 +1 -0
  21. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/maven-metadata.xml +4 -4
  22. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/maven-metadata.xml.md5 +1 -1
  23. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/maven-metadata.xml.sha1 +1 -1
  24. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/maven-metadata.xml.sha256 +1 -1
  25. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/maven-metadata.xml.sha512 +1 -1
  26. package/package.json +3 -3
  27. package/src/DevicePushTokenAutoRegistration.fx.ts +22 -4
  28. package/src/utils/updateDevicePushTokenAsync.ts +86 -0
  29. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/55.0.19/expo.modules.notifications-55.0.19.module.md5 +0 -1
  30. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/55.0.19/expo.modules.notifications-55.0.19.module.sha1 +0 -1
  31. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/55.0.19/expo.modules.notifications-55.0.19.module.sha256 +0 -1
  32. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/55.0.19/expo.modules.notifications-55.0.19.module.sha512 +0 -1
  33. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/55.0.19/expo.modules.notifications-55.0.19.pom.md5 +0 -1
  34. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/55.0.19/expo.modules.notifications-55.0.19.pom.sha1 +0 -1
  35. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/55.0.19/expo.modules.notifications-55.0.19.pom.sha256 +0 -1
  36. package/local-maven-repo/host/exp/exponent/expo.modules.notifications/55.0.19/expo.modules.notifications-55.0.19.pom.sha512 +0 -1
  37. /package/local-maven-repo/host/exp/exponent/expo.modules.notifications/{55.0.19/expo.modules.notifications-55.0.19-sources.jar → 55.0.21/expo.modules.notifications-55.0.21-sources.jar} +0 -0
  38. /package/local-maven-repo/host/exp/exponent/expo.modules.notifications/{55.0.19/expo.modules.notifications-55.0.19-sources.jar.md5 → 55.0.21/expo.modules.notifications-55.0.21-sources.jar.md5} +0 -0
  39. /package/local-maven-repo/host/exp/exponent/expo.modules.notifications/{55.0.19/expo.modules.notifications-55.0.19-sources.jar.sha1 → 55.0.21/expo.modules.notifications-55.0.21-sources.jar.sha1} +0 -0
  40. /package/local-maven-repo/host/exp/exponent/expo.modules.notifications/{55.0.19/expo.modules.notifications-55.0.19-sources.jar.sha256 → 55.0.21/expo.modules.notifications-55.0.21-sources.jar.sha256} +0 -0
  41. /package/local-maven-repo/host/exp/exponent/expo.modules.notifications/{55.0.19/expo.modules.notifications-55.0.19-sources.jar.sha512 → 55.0.21/expo.modules.notifications-55.0.21-sources.jar.sha512} +0 -0
  42. /package/local-maven-repo/host/exp/exponent/expo.modules.notifications/{55.0.19/expo.modules.notifications-55.0.19.aar → 55.0.21/expo.modules.notifications-55.0.21.aar} +0 -0
  43. /package/local-maven-repo/host/exp/exponent/expo.modules.notifications/{55.0.19/expo.modules.notifications-55.0.19.aar.md5 → 55.0.21/expo.modules.notifications-55.0.21.aar.md5} +0 -0
  44. /package/local-maven-repo/host/exp/exponent/expo.modules.notifications/{55.0.19/expo.modules.notifications-55.0.19.aar.sha1 → 55.0.21/expo.modules.notifications-55.0.21.aar.sha1} +0 -0
  45. /package/local-maven-repo/host/exp/exponent/expo.modules.notifications/{55.0.19/expo.modules.notifications-55.0.19.aar.sha256 → 55.0.21/expo.modules.notifications-55.0.21.aar.sha256} +0 -0
  46. /package/local-maven-repo/host/exp/exponent/expo.modules.notifications/{55.0.19/expo.modules.notifications-55.0.19.aar.sha512 → 55.0.21/expo.modules.notifications-55.0.21.aar.sha512} +0 -0
package/CHANGELOG.md CHANGED
@@ -10,6 +10,16 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 55.0.21 — 2026-04-28
14
+
15
+ ### 💡 Others
16
+
17
+ - Skip redundant device push token registration when token and metadata are unchanged since last successful registration. ([#44836](https://github.com/expo/expo/pull/44836) by [@stephanepham](https://github.com/stephanepham))
18
+
19
+ ## 55.0.20 — 2026-04-21
20
+
21
+ _This version does not introduce any user-facing changes._
22
+
13
23
  ## 55.0.19 — 2026-04-13
14
24
 
15
25
  _This version does not introduce any user-facing changes._
@@ -5,13 +5,13 @@ plugins {
5
5
  }
6
6
 
7
7
  group = 'host.exp.exponent'
8
- version = '55.0.19'
8
+ version = '55.0.21'
9
9
 
10
10
  android {
11
11
  namespace "expo.modules.notifications"
12
12
  defaultConfig {
13
13
  versionCode 21
14
- versionName '55.0.19'
14
+ versionName '55.0.21'
15
15
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16
16
  }
17
17
 
@@ -1 +1 @@
1
- {"version":3,"file":"DevicePushTokenAutoRegistration.fx.d.ts","sourceRoot":"","sources":["../src/DevicePushTokenAutoRegistration.fx.ts"],"names":[],"mappings":"AAAA,OAAO,2BAA2B,CAAC;AAiBnC;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,qCAAqC,CAAC,OAAO,EAAE,OAAO,iBAY3E;AAGD,wBAAsB,sCAAsC,CAC1D,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,iBAiC5C"}
1
+ {"version":3,"file":"DevicePushTokenAutoRegistration.fx.d.ts","sourceRoot":"","sources":["../src/DevicePushTokenAutoRegistration.fx.ts"],"names":[],"mappings":"AAAA,OAAO,2BAA2B,CAAC;AAyBnC;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,qCAAqC,CAAC,OAAO,EAAE,OAAO,iBAsB3E;AAGD,wBAAsB,sCAAsC,CAC1D,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,iBAiC5C"}
@@ -3,9 +3,13 @@ import { UnavailabilityError } from 'expo-modules-core';
3
3
  import ServerRegistrationModule from './ServerRegistrationModule';
4
4
  import { addPushTokenListener } from './TokenEmitter';
5
5
  import { getDevicePushTokenAsync } from './getDevicePushTokenAsync';
6
- import { updateDevicePushTokenAsync as updateDevicePushTokenAsyncWithSignal } from './utils/updateDevicePushTokenAsync';
6
+ import { updateDevicePushTokenAsync as updateDevicePushTokenAsyncWithSignal, hasDeviceTokenChangedAsync, } from './utils/updateDevicePushTokenAsync';
7
7
  let lastAbortController = null;
8
8
  async function updatePushTokenAsync(token) {
9
+ const changed = await hasDeviceTokenChangedAsync(token);
10
+ if (!changed) {
11
+ return;
12
+ }
9
13
  // Abort current update process
10
14
  lastAbortController?.abort();
11
15
  lastAbortController = new AbortController();
@@ -25,7 +29,21 @@ export async function setAutoServerRegistrationEnabledAsync(enabled) {
25
29
  if (!ServerRegistrationModule.setRegistrationInfoAsync) {
26
30
  throw new UnavailabilityError('ServerRegistrationModule', 'setRegistrationInfoAsync');
27
31
  }
28
- await ServerRegistrationModule.setRegistrationInfoAsync(enabled ? JSON.stringify({ isEnabled: enabled }) : null);
32
+ if (!enabled) {
33
+ await ServerRegistrationModule.setRegistrationInfoAsync(null);
34
+ }
35
+ else {
36
+ let existing = {};
37
+ try {
38
+ const info = await ServerRegistrationModule.getRegistrationInfoAsync?.();
39
+ if (info) {
40
+ existing = JSON.parse(info);
41
+ }
42
+ }
43
+ catch { }
44
+ existing.isEnabled = true;
45
+ await ServerRegistrationModule.setRegistrationInfoAsync(JSON.stringify(existing));
46
+ }
29
47
  }
30
48
  // note(Chmiela): This function is exported only for testing purposes.
31
49
  export async function __handlePersistedRegistrationInfoAsync(registrationInfo) {
@@ -1 +1 @@
1
- {"version":3,"file":"DevicePushTokenAutoRegistration.fx.js","sourceRoot":"","sources":["../src/DevicePushTokenAutoRegistration.fx.ts"],"names":[],"mappings":"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","sourcesContent":["import 'abort-controller/polyfill';\nimport { UnavailabilityError } from 'expo-modules-core';\n\nimport ServerRegistrationModule from './ServerRegistrationModule';\nimport { addPushTokenListener } from './TokenEmitter';\nimport { DevicePushToken } from './Tokens.types';\nimport { getDevicePushTokenAsync } from './getDevicePushTokenAsync';\nimport { updateDevicePushTokenAsync as updateDevicePushTokenAsyncWithSignal } from './utils/updateDevicePushTokenAsync';\n\nlet lastAbortController: AbortController | null = null;\nasync function updatePushTokenAsync(token: DevicePushToken) {\n // Abort current update process\n lastAbortController?.abort();\n lastAbortController = new AbortController();\n return await updateDevicePushTokenAsyncWithSignal(lastAbortController.signal, token);\n}\n\n/**\n * Encapsulates device server registration data\n */\nexport type DevicePushTokenRegistration = {\n isEnabled: boolean;\n};\n\n/**\n * @hidden - the comment is misleading and the purpose of the function needs to be reevaluated\n *\n * Sets the registration information so that the device push token gets pushed\n * to the given registration endpoint\n * @param enabled\n */\nexport async function setAutoServerRegistrationEnabledAsync(enabled: boolean) {\n // We are overwriting registration, so we shouldn't let\n // any pending request complete.\n lastAbortController?.abort();\n\n if (!ServerRegistrationModule.setRegistrationInfoAsync) {\n throw new UnavailabilityError('ServerRegistrationModule', 'setRegistrationInfoAsync');\n }\n\n await ServerRegistrationModule.setRegistrationInfoAsync(\n enabled ? JSON.stringify({ isEnabled: enabled }) : null\n );\n}\n\n// note(Chmiela): This function is exported only for testing purposes.\nexport async function __handlePersistedRegistrationInfoAsync(\n registrationInfo: string | null | undefined\n) {\n if (!registrationInfo) {\n // No registration info, nothing to do\n return;\n }\n\n let registration: DevicePushTokenRegistration | null = null;\n try {\n registration = JSON.parse(registrationInfo);\n } catch (e) {\n console.warn(\n '[expo-notifications] Error encountered while fetching registration information for auto token updates.',\n e\n );\n }\n\n if (!registration?.isEnabled) {\n // Registration is invalid or not enabled, nothing more to do\n return;\n }\n\n try {\n // Since the registration is enabled, fetching a \"new\" device token\n // shouldn't be a problem.\n const latestDevicePushToken = await getDevicePushTokenAsync();\n await updatePushTokenAsync(latestDevicePushToken);\n } catch (e) {\n console.warn(\n '[expo-notifications] Error encountered while updating server registration with latest device push token.',\n e\n );\n }\n}\n\nif (ServerRegistrationModule.getRegistrationInfoAsync) {\n // A global scope (to get all the updates) device push token\n // subscription, never cleared.\n addPushTokenListener(async (token) => {\n try {\n // Before updating the push token on server we always check if we should\n // Since modules can't change their method availability while running, we\n // can assert it's defined.\n const registrationInfo = await ServerRegistrationModule.getRegistrationInfoAsync!();\n\n if (!registrationInfo) {\n // Registration is not enabled\n return;\n }\n\n const registration: DevicePushTokenRegistration | null = JSON.parse(registrationInfo);\n if (registration?.isEnabled) {\n // Dispatch an abortable task to update\n // registration with new token.\n await updatePushTokenAsync(token);\n }\n } catch (e) {\n console.warn(\n '[expo-notifications] Error encountered while updating server registration with latest device push token.',\n e\n );\n }\n });\n\n // Verify if persisted registration\n // has successfully uploaded last known\n // device push token. If not, retry.\n ServerRegistrationModule.getRegistrationInfoAsync().then(\n __handlePersistedRegistrationInfoAsync,\n (e) => {\n console.error('[expo-notifications] Error reading persisted server registration info: ', e);\n }\n );\n} else {\n console.warn(\n `[expo-notifications] Error encountered while fetching auto-registration state, new tokens will not be automatically registered on server.`,\n new UnavailabilityError('ServerRegistrationModule', 'getRegistrationInfoAsync')\n );\n}\n"]}
1
+ {"version":3,"file":"DevicePushTokenAutoRegistration.fx.js","sourceRoot":"","sources":["../src/DevicePushTokenAutoRegistration.fx.ts"],"names":[],"mappings":"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","sourcesContent":["import 'abort-controller/polyfill';\nimport { UnavailabilityError } from 'expo-modules-core';\n\nimport ServerRegistrationModule from './ServerRegistrationModule';\nimport { addPushTokenListener } from './TokenEmitter';\nimport { DevicePushToken } from './Tokens.types';\nimport { getDevicePushTokenAsync } from './getDevicePushTokenAsync';\nimport {\n updateDevicePushTokenAsync as updateDevicePushTokenAsyncWithSignal,\n hasDeviceTokenChangedAsync,\n} from './utils/updateDevicePushTokenAsync';\n\nlet lastAbortController: AbortController | null = null;\nasync function updatePushTokenAsync(token: DevicePushToken) {\n const changed = await hasDeviceTokenChangedAsync(token);\n if (!changed) {\n return;\n }\n\n // Abort current update process\n lastAbortController?.abort();\n lastAbortController = new AbortController();\n return await updateDevicePushTokenAsyncWithSignal(lastAbortController.signal, token);\n}\n\n/**\n * Encapsulates device server registration data\n */\nexport type DevicePushTokenRegistration = {\n isEnabled: boolean;\n};\n\n/**\n * @hidden - the comment is misleading and the purpose of the function needs to be reevaluated\n *\n * Sets the registration information so that the device push token gets pushed\n * to the given registration endpoint\n * @param enabled\n */\nexport async function setAutoServerRegistrationEnabledAsync(enabled: boolean) {\n // We are overwriting registration, so we shouldn't let\n // any pending request complete.\n lastAbortController?.abort();\n\n if (!ServerRegistrationModule.setRegistrationInfoAsync) {\n throw new UnavailabilityError('ServerRegistrationModule', 'setRegistrationInfoAsync');\n }\n\n if (!enabled) {\n await ServerRegistrationModule.setRegistrationInfoAsync(null);\n } else {\n let existing: Record<string, unknown> = {};\n try {\n const info = await ServerRegistrationModule.getRegistrationInfoAsync?.();\n if (info) {\n existing = JSON.parse(info);\n }\n } catch {}\n existing.isEnabled = true;\n await ServerRegistrationModule.setRegistrationInfoAsync(JSON.stringify(existing));\n }\n}\n\n// note(Chmiela): This function is exported only for testing purposes.\nexport async function __handlePersistedRegistrationInfoAsync(\n registrationInfo: string | null | undefined\n) {\n if (!registrationInfo) {\n // No registration info, nothing to do\n return;\n }\n\n let registration: DevicePushTokenRegistration | null = null;\n try {\n registration = JSON.parse(registrationInfo);\n } catch (e) {\n console.warn(\n '[expo-notifications] Error encountered while fetching registration information for auto token updates.',\n e\n );\n }\n\n if (!registration?.isEnabled) {\n // Registration is invalid or not enabled, nothing more to do\n return;\n }\n\n try {\n // Since the registration is enabled, fetching a \"new\" device token\n // shouldn't be a problem.\n const latestDevicePushToken = await getDevicePushTokenAsync();\n await updatePushTokenAsync(latestDevicePushToken);\n } catch (e) {\n console.warn(\n '[expo-notifications] Error encountered while updating server registration with latest device push token.',\n e\n );\n }\n}\n\nif (ServerRegistrationModule.getRegistrationInfoAsync) {\n // A global scope (to get all the updates) device push token\n // subscription, never cleared.\n addPushTokenListener(async (token) => {\n try {\n // Before updating the push token on server we always check if we should\n // Since modules can't change their method availability while running, we\n // can assert it's defined.\n const registrationInfo = await ServerRegistrationModule.getRegistrationInfoAsync!();\n\n if (!registrationInfo) {\n // Registration is not enabled\n return;\n }\n\n const registration: DevicePushTokenRegistration | null = JSON.parse(registrationInfo);\n if (registration?.isEnabled) {\n // Dispatch an abortable task to update\n // registration with new token.\n await updatePushTokenAsync(token);\n }\n } catch (e) {\n console.warn(\n '[expo-notifications] Error encountered while updating server registration with latest device push token.',\n e\n );\n }\n });\n\n // Verify if persisted registration\n // has successfully uploaded last known\n // device push token. If not, retry.\n ServerRegistrationModule.getRegistrationInfoAsync().then(\n __handlePersistedRegistrationInfoAsync,\n (e) => {\n console.error('[expo-notifications] Error reading persisted server registration info: ', e);\n }\n );\n} else {\n console.warn(\n `[expo-notifications] Error encountered while fetching auto-registration state, new tokens will not be automatically registered on server.`,\n new UnavailabilityError('ServerRegistrationModule', 'getRegistrationInfoAsync')\n );\n}\n"]}
@@ -1,3 +1,8 @@
1
1
  import { DevicePushToken } from '../Tokens.types';
2
+ /**
3
+ * Returns `true` if the device token or metadata has changed since the last
4
+ * successful registration, or if the check cannot be performed (fail-open).
5
+ */
6
+ export declare function hasDeviceTokenChangedAsync(token: DevicePushToken): Promise<boolean>;
2
7
  export declare function updateDevicePushTokenAsync(signal: AbortSignal, token: DevicePushToken): Promise<void>;
3
8
  //# sourceMappingURL=updateDevicePushTokenAsync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"updateDevicePushTokenAsync.d.ts","sourceRoot":"","sources":["../../src/utils/updateDevicePushTokenAsync.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAIlD,wBAAsB,0BAA0B,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,eAAe,iBA2F3F"}
1
+ {"version":3,"file":"updateDevicePushTokenAsync.d.ts","sourceRoot":"","sources":["../../src/utils/updateDevicePushTokenAsync.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAmDlD;;;GAGG;AACH,wBAAsB,0BAA0B,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAuBzF;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,eAAe,iBAqG3F"}
@@ -3,6 +3,65 @@ import { CodedError, Platform, UnavailabilityError } from 'expo-modules-core';
3
3
  import { computeNextBackoffInterval } from './backoff';
4
4
  import ServerRegistrationModule from '../ServerRegistrationModule';
5
5
  const updateDevicePushTokenUrl = 'https://exp.host/--/api/v2/push/updateDeviceToken';
6
+ const LAST_TOKEN_KEY = 'lastRegisteredDeviceToken';
7
+ // Force re-registration after 7 days even if nothing changed, in case the
8
+ // server lost the device record (cleanup, migration, etc.).
9
+ const REGISTRATION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
10
+ async function getLastRegisteredTokenDataAsync() {
11
+ try {
12
+ if (!ServerRegistrationModule.getRegistrationInfoAsync) {
13
+ return null;
14
+ }
15
+ const info = await ServerRegistrationModule.getRegistrationInfoAsync();
16
+ if (!info) {
17
+ return null;
18
+ }
19
+ const parsed = JSON.parse(info);
20
+ return parsed?.[LAST_TOKEN_KEY] ?? null;
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ async function setLastRegisteredTokenDataAsync(tokenData) {
27
+ try {
28
+ if (!ServerRegistrationModule.getRegistrationInfoAsync ||
29
+ !ServerRegistrationModule.setRegistrationInfoAsync) {
30
+ return;
31
+ }
32
+ const info = await ServerRegistrationModule.getRegistrationInfoAsync();
33
+ const existing = info ? JSON.parse(info) : {};
34
+ existing[LAST_TOKEN_KEY] = tokenData;
35
+ await ServerRegistrationModule.setRegistrationInfoAsync(JSON.stringify(existing));
36
+ }
37
+ catch {
38
+ // Best-effort — next app open will re-register
39
+ }
40
+ }
41
+ /**
42
+ * Returns `true` if the device token or metadata has changed since the last
43
+ * successful registration, or if the check cannot be performed (fail-open).
44
+ */
45
+ export async function hasDeviceTokenChangedAsync(token) {
46
+ try {
47
+ const development = await shouldUseDevelopmentNotificationService();
48
+ const lastTokenData = await getLastRegisteredTokenDataAsync();
49
+ if (lastTokenData == null) {
50
+ return true;
51
+ }
52
+ const age = Date.now() - (lastTokenData.registeredAt ?? 0);
53
+ if (age < 0 || age >= REGISTRATION_TTL_MS) {
54
+ return true;
55
+ }
56
+ return (token.data !== lastTokenData.deviceToken ||
57
+ Application.applicationId !== lastTokenData.appId ||
58
+ development !== lastTokenData.development ||
59
+ getTypeOfToken(token) !== lastTokenData.type);
60
+ }
61
+ catch {
62
+ return true;
63
+ }
64
+ }
6
65
  export async function updateDevicePushTokenAsync(signal, token) {
7
66
  const doUpdateDevicePushTokenAsync = async (retry) => {
8
67
  const [development, deviceId] = await Promise.all([
@@ -29,6 +88,15 @@ export async function updateDevicePushTokenAsync(signal, token) {
29
88
  if (!response.ok) {
30
89
  console.debug('[expo-notifications] Error encountered while updating the device push token with the server:', await response.text());
31
90
  }
91
+ if (response.ok) {
92
+ await setLastRegisteredTokenDataAsync({
93
+ deviceToken: token.data,
94
+ appId: Application.applicationId,
95
+ development,
96
+ type: getTypeOfToken(token),
97
+ registeredAt: Date.now(),
98
+ });
99
+ }
32
100
  // Retry if request failed
33
101
  if (!response.ok) {
34
102
  retry();
@@ -1 +1 @@
1
- {"version":3,"file":"updateDevicePushTokenAsync.js","sourceRoot":"","sources":["../../src/utils/updateDevicePushTokenAsync.ts"],"names":[],"mappings":"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","sourcesContent":["import * as Application from 'expo-application';\nimport { CodedError, Platform, UnavailabilityError } from 'expo-modules-core';\n\nimport { computeNextBackoffInterval } from './backoff';\nimport ServerRegistrationModule from '../ServerRegistrationModule';\nimport { DevicePushToken } from '../Tokens.types';\n\nconst updateDevicePushTokenUrl = 'https://exp.host/--/api/v2/push/updateDeviceToken';\n\nexport async function updateDevicePushTokenAsync(signal: AbortSignal, token: DevicePushToken) {\n const doUpdateDevicePushTokenAsync = async (retry: () => void) => {\n const [development, deviceId] = await Promise.all([\n shouldUseDevelopmentNotificationService(),\n getDeviceIdAsync(),\n ]);\n const body = {\n deviceId: deviceId.toLowerCase(),\n development,\n deviceToken: token.data,\n appId: Application.applicationId,\n type: getTypeOfToken(token),\n };\n\n try {\n const response = await fetch(updateDevicePushTokenUrl, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n });\n\n // Help debug erroring servers\n if (!response.ok) {\n console.debug(\n '[expo-notifications] Error encountered while updating the device push token with the server:',\n await response.text()\n );\n }\n\n // Retry if request failed\n if (!response.ok) {\n retry();\n }\n } catch (error: any) {\n // Error returned if the request is aborted should be an 'AbortError'. In\n // React Native fetch is polyfilled using `whatwg-fetch` which:\n // - creates `AbortError`s like this\n // https://github.com/github/fetch/blob/75d9455d380f365701151f3ac85c5bda4bbbde76/fetch.js#L505\n // - which creates exceptions like\n // https://github.com/github/fetch/blob/75d9455d380f365701151f3ac85c5bda4bbbde76/fetch.js#L490-L494\n if (typeof error === 'object' && error?.name === 'AbortError') {\n // We don't consider AbortError a failure, it's a sign somewhere else the\n // request is expected to succeed and we don't need this one, so let's\n // just return.\n return;\n }\n\n console.warn(\n '[expo-notifications] Error thrown while updating the device push token with the server:',\n error\n );\n\n retry();\n }\n };\n\n let shouldTry = true;\n const retry = () => {\n shouldTry = true;\n };\n\n let retriesCount = 0;\n const initialBackoff = 500; // 0.5 s\n const backoffOptions = {\n maxBackoff: 2 * 60 * 1000, // 2 minutes\n };\n let nextBackoffInterval = computeNextBackoffInterval(\n initialBackoff,\n retriesCount,\n backoffOptions\n );\n\n while (shouldTry && !signal.aborted) {\n // Will be set to true by `retry` if it's called\n shouldTry = false;\n await doUpdateDevicePushTokenAsync(retry);\n\n // Do not wait if we won't retry\n if (shouldTry && !signal.aborted) {\n nextBackoffInterval = computeNextBackoffInterval(\n initialBackoff,\n retriesCount,\n backoffOptions\n );\n retriesCount += 1;\n await new Promise((resolve) => setTimeout(resolve, nextBackoffInterval));\n }\n }\n}\n\n// Same as in getExpoPushTokenAsync\nasync function getDeviceIdAsync() {\n try {\n if (!ServerRegistrationModule.getInstallationIdAsync) {\n throw new UnavailabilityError('ExpoServerRegistrationModule', 'getInstallationIdAsync');\n }\n\n return await ServerRegistrationModule.getInstallationIdAsync();\n } catch (e) {\n throw new CodedError(\n 'ERR_NOTIFICATIONS_DEVICE_ID',\n `Could not fetch the installation ID of the application: ${e}.`\n );\n }\n}\n\n// Same as in getExpoPushTokenAsync\nfunction getTypeOfToken(devicePushToken: DevicePushToken) {\n switch (devicePushToken.type) {\n case 'ios':\n return 'apns';\n case 'android':\n return 'fcm';\n // This probably will error on server, but let's make this function future-safe.\n default:\n return devicePushToken.type;\n }\n}\n\n// Same as in getExpoPushTokenAsync\nasync function shouldUseDevelopmentNotificationService() {\n if (Platform.OS === 'ios') {\n try {\n const notificationServiceEnvironment =\n await Application.getIosPushNotificationServiceEnvironmentAsync();\n if (notificationServiceEnvironment === 'development') {\n return true;\n }\n } catch {\n // We can't do anything here, we'll fallback to false then.\n }\n }\n\n return false;\n}\n"]}
1
+ {"version":3,"file":"updateDevicePushTokenAsync.js","sourceRoot":"","sources":["../../src/utils/updateDevicePushTokenAsync.ts"],"names":[],"mappings":"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","sourcesContent":["import * as Application from 'expo-application';\nimport { CodedError, Platform, UnavailabilityError } from 'expo-modules-core';\n\nimport { computeNextBackoffInterval } from './backoff';\nimport ServerRegistrationModule from '../ServerRegistrationModule';\nimport { DevicePushToken } from '../Tokens.types';\n\nconst updateDevicePushTokenUrl = 'https://exp.host/--/api/v2/push/updateDeviceToken';\n\nconst LAST_TOKEN_KEY = 'lastRegisteredDeviceToken';\n\ntype StoredTokenData = {\n deviceToken: string;\n appId: string | null;\n development: boolean;\n type: string;\n registeredAt: number;\n};\n\n// Force re-registration after 7 days even if nothing changed, in case the\n// server lost the device record (cleanup, migration, etc.).\nconst REGISTRATION_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n\nasync function getLastRegisteredTokenDataAsync(): Promise<StoredTokenData | null> {\n try {\n if (!ServerRegistrationModule.getRegistrationInfoAsync) {\n return null;\n }\n const info = await ServerRegistrationModule.getRegistrationInfoAsync();\n if (!info) {\n return null;\n }\n const parsed = JSON.parse(info);\n return parsed?.[LAST_TOKEN_KEY] ?? null;\n } catch {\n return null;\n }\n}\n\nasync function setLastRegisteredTokenDataAsync(tokenData: StoredTokenData): Promise<void> {\n try {\n if (\n !ServerRegistrationModule.getRegistrationInfoAsync ||\n !ServerRegistrationModule.setRegistrationInfoAsync\n ) {\n return;\n }\n const info = await ServerRegistrationModule.getRegistrationInfoAsync();\n const existing = info ? JSON.parse(info) : {};\n existing[LAST_TOKEN_KEY] = tokenData;\n await ServerRegistrationModule.setRegistrationInfoAsync(JSON.stringify(existing));\n } catch {\n // Best-effort — next app open will re-register\n }\n}\n\n/**\n * Returns `true` if the device token or metadata has changed since the last\n * successful registration, or if the check cannot be performed (fail-open).\n */\nexport async function hasDeviceTokenChangedAsync(token: DevicePushToken): Promise<boolean> {\n try {\n const development = await shouldUseDevelopmentNotificationService();\n const lastTokenData = await getLastRegisteredTokenDataAsync();\n\n if (lastTokenData == null) {\n return true;\n }\n\n const age = Date.now() - (lastTokenData.registeredAt ?? 0);\n if (age < 0 || age >= REGISTRATION_TTL_MS) {\n return true;\n }\n\n return (\n token.data !== lastTokenData.deviceToken ||\n Application.applicationId !== lastTokenData.appId ||\n development !== lastTokenData.development ||\n getTypeOfToken(token) !== lastTokenData.type\n );\n } catch {\n return true;\n }\n}\n\nexport async function updateDevicePushTokenAsync(signal: AbortSignal, token: DevicePushToken) {\n const doUpdateDevicePushTokenAsync = async (retry: () => void) => {\n const [development, deviceId] = await Promise.all([\n shouldUseDevelopmentNotificationService(),\n getDeviceIdAsync(),\n ]);\n const body = {\n deviceId: deviceId.toLowerCase(),\n development,\n deviceToken: token.data,\n appId: Application.applicationId,\n type: getTypeOfToken(token),\n };\n\n try {\n const response = await fetch(updateDevicePushTokenUrl, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n });\n\n // Help debug erroring servers\n if (!response.ok) {\n console.debug(\n '[expo-notifications] Error encountered while updating the device push token with the server:',\n await response.text()\n );\n }\n\n if (response.ok) {\n await setLastRegisteredTokenDataAsync({\n deviceToken: token.data,\n appId: Application.applicationId,\n development,\n type: getTypeOfToken(token),\n registeredAt: Date.now(),\n });\n }\n\n // Retry if request failed\n if (!response.ok) {\n retry();\n }\n } catch (error: any) {\n // Error returned if the request is aborted should be an 'AbortError'. In\n // React Native fetch is polyfilled using `whatwg-fetch` which:\n // - creates `AbortError`s like this\n // https://github.com/github/fetch/blob/75d9455d380f365701151f3ac85c5bda4bbbde76/fetch.js#L505\n // - which creates exceptions like\n // https://github.com/github/fetch/blob/75d9455d380f365701151f3ac85c5bda4bbbde76/fetch.js#L490-L494\n if (typeof error === 'object' && error?.name === 'AbortError') {\n // We don't consider AbortError a failure, it's a sign somewhere else the\n // request is expected to succeed and we don't need this one, so let's\n // just return.\n return;\n }\n\n console.warn(\n '[expo-notifications] Error thrown while updating the device push token with the server:',\n error\n );\n\n retry();\n }\n };\n\n let shouldTry = true;\n const retry = () => {\n shouldTry = true;\n };\n\n let retriesCount = 0;\n const initialBackoff = 500; // 0.5 s\n const backoffOptions = {\n maxBackoff: 2 * 60 * 1000, // 2 minutes\n };\n let nextBackoffInterval = computeNextBackoffInterval(\n initialBackoff,\n retriesCount,\n backoffOptions\n );\n\n while (shouldTry && !signal.aborted) {\n // Will be set to true by `retry` if it's called\n shouldTry = false;\n await doUpdateDevicePushTokenAsync(retry);\n\n // Do not wait if we won't retry\n if (shouldTry && !signal.aborted) {\n nextBackoffInterval = computeNextBackoffInterval(\n initialBackoff,\n retriesCount,\n backoffOptions\n );\n retriesCount += 1;\n await new Promise((resolve) => setTimeout(resolve, nextBackoffInterval));\n }\n }\n}\n\n// Same as in getExpoPushTokenAsync\nasync function getDeviceIdAsync() {\n try {\n if (!ServerRegistrationModule.getInstallationIdAsync) {\n throw new UnavailabilityError('ExpoServerRegistrationModule', 'getInstallationIdAsync');\n }\n\n return await ServerRegistrationModule.getInstallationIdAsync();\n } catch (e) {\n throw new CodedError(\n 'ERR_NOTIFICATIONS_DEVICE_ID',\n `Could not fetch the installation ID of the application: ${e}.`\n );\n }\n}\n\n// Same as in getExpoPushTokenAsync\nfunction getTypeOfToken(devicePushToken: DevicePushToken) {\n switch (devicePushToken.type) {\n case 'ios':\n return 'apns';\n case 'android':\n return 'fcm';\n // This probably will error on server, but let's make this function future-safe.\n default:\n return devicePushToken.type;\n }\n}\n\n// Same as in getExpoPushTokenAsync\nasync function shouldUseDevelopmentNotificationService() {\n if (Platform.OS === 'ios') {\n try {\n const notificationServiceEnvironment =\n await Application.getIosPushNotificationServiceEnvironmentAsync();\n if (notificationServiceEnvironment === 'development') {\n return true;\n }\n } catch {\n // We can't do anything here, we'll fallback to false then.\n }\n }\n\n return false;\n}\n"]}
@@ -35,7 +35,7 @@
35
35
  "publication": {
36
36
  "groupId": "host.exp.exponent",
37
37
  "artifactId": "expo.modules.notifications",
38
- "version": "55.0.19",
38
+ "version": "55.0.21",
39
39
  "repository": "local-maven-repo"
40
40
  }
41
41
  }
@@ -3,7 +3,7 @@
3
3
  "component": {
4
4
  "group": "host.exp.exponent",
5
5
  "module": "expo.modules.notifications",
6
- "version": "55.0.19",
6
+ "version": "55.0.21",
7
7
  "attributes": {
8
8
  "org.gradle.status": "release"
9
9
  }
@@ -24,8 +24,8 @@
24
24
  },
25
25
  "files": [
26
26
  {
27
- "name": "expo.modules.notifications-55.0.19.aar",
28
- "url": "expo.modules.notifications-55.0.19.aar",
27
+ "name": "expo.modules.notifications-55.0.21.aar",
28
+ "url": "expo.modules.notifications-55.0.21.aar",
29
29
  "size": 402983,
30
30
  "sha512": "e4c3409cfecc6c9a8049529caf9fd17f34f2a8be55e5b1a345b52544079b8a62ad48e55e47179519e150b8296c814dda0cf951fbd53ced1f80da17365fcc457b",
31
31
  "sha256": "a96065f543fc9f57cf6eb4871137f339854dec0541e8277eb237e0d48abefeda",
@@ -122,8 +122,8 @@
122
122
  ],
123
123
  "files": [
124
124
  {
125
- "name": "expo.modules.notifications-55.0.19.aar",
126
- "url": "expo.modules.notifications-55.0.19.aar",
125
+ "name": "expo.modules.notifications-55.0.21.aar",
126
+ "url": "expo.modules.notifications-55.0.21.aar",
127
127
  "size": 402983,
128
128
  "sha512": "e4c3409cfecc6c9a8049529caf9fd17f34f2a8be55e5b1a345b52544079b8a62ad48e55e47179519e150b8296c814dda0cf951fbd53ced1f80da17365fcc457b",
129
129
  "sha256": "a96065f543fc9f57cf6eb4871137f339854dec0541e8277eb237e0d48abefeda",
@@ -142,8 +142,8 @@
142
142
  },
143
143
  "files": [
144
144
  {
145
- "name": "expo.modules.notifications-55.0.19-sources.jar",
146
- "url": "expo.modules.notifications-55.0.19-sources.jar",
145
+ "name": "expo.modules.notifications-55.0.21-sources.jar",
146
+ "url": "expo.modules.notifications-55.0.21-sources.jar",
147
147
  "size": 100001,
148
148
  "sha512": "6bc8c1210d364179f454b9fe28fd71a79f1e28fc34dc615e61cdbb89c6afea3991b04b76b77ae42cb9ce6abc6e36a861caddba68d900fa6a09880b960300444d",
149
149
  "sha256": "c6920c792c28a4f9785956f71822697398385da86af1c6da362785e41f329140",
@@ -0,0 +1 @@
1
+ 67af83ef1a812724c70cac553b2b40a475cb1434f87126126df497bbead95e51
@@ -0,0 +1 @@
1
+ 76677ca056e91e0c56708334fbf0aaf67c94f9119a51be3ff88f1d2c257e434ef88332b678f2142612b1b92f90352ac04ee3566790ac1efba3ac352eb5bda15a
@@ -9,7 +9,7 @@
9
9
  <modelVersion>4.0.0</modelVersion>
10
10
  <groupId>host.exp.exponent</groupId>
11
11
  <artifactId>expo.modules.notifications</artifactId>
12
- <version>55.0.19</version>
12
+ <version>55.0.21</version>
13
13
  <packaging>aar</packaging>
14
14
  <name>expo.modules.notifications</name>
15
15
  <url>https://github.com/expo/expo</url>
@@ -0,0 +1 @@
1
+ 7ccaaf2e3cab1295e3aec37efb4b551d81cddbca119878740ce630260a1bffb2
@@ -0,0 +1 @@
1
+ 4f70873dc594e81efd2b183e83068f3e47c4e75da4322abf85e2071bf9abd9b4219c6983290e50ea944bbdc8ab2cc51481891a8bacb3fdcdaa6104153e6269cf
@@ -3,11 +3,11 @@
3
3
  <groupId>host.exp.exponent</groupId>
4
4
  <artifactId>expo.modules.notifications</artifactId>
5
5
  <versioning>
6
- <latest>55.0.19</latest>
7
- <release>55.0.19</release>
6
+ <latest>55.0.21</latest>
7
+ <release>55.0.21</release>
8
8
  <versions>
9
- <version>55.0.19</version>
9
+ <version>55.0.21</version>
10
10
  </versions>
11
- <lastUpdated>20260413113043</lastUpdated>
11
+ <lastUpdated>20260428153947</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- 5bbea4d0122668df140388db9a39f6a5
1
+ 3b54b27032ae9516bcefd0397a796fff
@@ -1 +1 @@
1
- f2433443ba55cbc469662315d2caf5291cf4eff6
1
+ b045b5a99f3e0926a580b3f61ccbe7598fccd03d
@@ -1 +1 @@
1
- 89fb3eed769dc07e9064dc025c882e8ca6445544a619255330523fa545457d78
1
+ b2f4eab032f9ead6be917f8a39e89436cac601b88acbcc91784574388caef6bb
@@ -1 +1 @@
1
- c4b20f6fed8a2dd3eac6a510d83fdc601ff08349b837aa43eddff71b370a2e961dfbc74b3563fa7aed9109adedab6ca0ea20d7d2c7c63c04c8357158ec111673
1
+ d9dc7a64278b76cb02a5e879c1e00be26de4216b50e068837c7595ac8b7f3b4b258b8364724d6c1cacc4b1c0ff04107168927c03e5320052d6d0e4d885554b09
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-notifications",
3
- "version": "55.0.19",
3
+ "version": "55.0.21",
4
4
  "description": "Provides an API to fetch push notification tokens and to present, schedule, receive, and respond to notifications.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -47,7 +47,7 @@
47
47
  "abort-controller": "^3.0.0",
48
48
  "badgin": "^1.1.5",
49
49
  "expo-application": "~55.0.14",
50
- "expo-constants": "~55.0.14"
50
+ "expo-constants": "~55.0.15"
51
51
  },
52
52
  "devDependencies": {
53
53
  "expo-module-scripts": "^55.0.2",
@@ -58,5 +58,5 @@
58
58
  "react": "*",
59
59
  "react-native": "*"
60
60
  },
61
- "gitHead": "ae852eb2ffd8888dc4fa6ef36ac332bbf8bb2fe9"
61
+ "gitHead": "be06cb45cb9eb8076b6910daa98813a6a3b03287"
62
62
  }
@@ -5,10 +5,18 @@ import ServerRegistrationModule from './ServerRegistrationModule';
5
5
  import { addPushTokenListener } from './TokenEmitter';
6
6
  import { DevicePushToken } from './Tokens.types';
7
7
  import { getDevicePushTokenAsync } from './getDevicePushTokenAsync';
8
- import { updateDevicePushTokenAsync as updateDevicePushTokenAsyncWithSignal } from './utils/updateDevicePushTokenAsync';
8
+ import {
9
+ updateDevicePushTokenAsync as updateDevicePushTokenAsyncWithSignal,
10
+ hasDeviceTokenChangedAsync,
11
+ } from './utils/updateDevicePushTokenAsync';
9
12
 
10
13
  let lastAbortController: AbortController | null = null;
11
14
  async function updatePushTokenAsync(token: DevicePushToken) {
15
+ const changed = await hasDeviceTokenChangedAsync(token);
16
+ if (!changed) {
17
+ return;
18
+ }
19
+
12
20
  // Abort current update process
13
21
  lastAbortController?.abort();
14
22
  lastAbortController = new AbortController();
@@ -38,9 +46,19 @@ export async function setAutoServerRegistrationEnabledAsync(enabled: boolean) {
38
46
  throw new UnavailabilityError('ServerRegistrationModule', 'setRegistrationInfoAsync');
39
47
  }
40
48
 
41
- await ServerRegistrationModule.setRegistrationInfoAsync(
42
- enabled ? JSON.stringify({ isEnabled: enabled }) : null
43
- );
49
+ if (!enabled) {
50
+ await ServerRegistrationModule.setRegistrationInfoAsync(null);
51
+ } else {
52
+ let existing: Record<string, unknown> = {};
53
+ try {
54
+ const info = await ServerRegistrationModule.getRegistrationInfoAsync?.();
55
+ if (info) {
56
+ existing = JSON.parse(info);
57
+ }
58
+ } catch {}
59
+ existing.isEnabled = true;
60
+ await ServerRegistrationModule.setRegistrationInfoAsync(JSON.stringify(existing));
61
+ }
44
62
  }
45
63
 
46
64
  // note(Chmiela): This function is exported only for testing purposes.
@@ -7,6 +7,82 @@ import { DevicePushToken } from '../Tokens.types';
7
7
 
8
8
  const updateDevicePushTokenUrl = 'https://exp.host/--/api/v2/push/updateDeviceToken';
9
9
 
10
+ const LAST_TOKEN_KEY = 'lastRegisteredDeviceToken';
11
+
12
+ type StoredTokenData = {
13
+ deviceToken: string;
14
+ appId: string | null;
15
+ development: boolean;
16
+ type: string;
17
+ registeredAt: number;
18
+ };
19
+
20
+ // Force re-registration after 7 days even if nothing changed, in case the
21
+ // server lost the device record (cleanup, migration, etc.).
22
+ const REGISTRATION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
23
+
24
+ async function getLastRegisteredTokenDataAsync(): Promise<StoredTokenData | null> {
25
+ try {
26
+ if (!ServerRegistrationModule.getRegistrationInfoAsync) {
27
+ return null;
28
+ }
29
+ const info = await ServerRegistrationModule.getRegistrationInfoAsync();
30
+ if (!info) {
31
+ return null;
32
+ }
33
+ const parsed = JSON.parse(info);
34
+ return parsed?.[LAST_TOKEN_KEY] ?? null;
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ async function setLastRegisteredTokenDataAsync(tokenData: StoredTokenData): Promise<void> {
41
+ try {
42
+ if (
43
+ !ServerRegistrationModule.getRegistrationInfoAsync ||
44
+ !ServerRegistrationModule.setRegistrationInfoAsync
45
+ ) {
46
+ return;
47
+ }
48
+ const info = await ServerRegistrationModule.getRegistrationInfoAsync();
49
+ const existing = info ? JSON.parse(info) : {};
50
+ existing[LAST_TOKEN_KEY] = tokenData;
51
+ await ServerRegistrationModule.setRegistrationInfoAsync(JSON.stringify(existing));
52
+ } catch {
53
+ // Best-effort — next app open will re-register
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Returns `true` if the device token or metadata has changed since the last
59
+ * successful registration, or if the check cannot be performed (fail-open).
60
+ */
61
+ export async function hasDeviceTokenChangedAsync(token: DevicePushToken): Promise<boolean> {
62
+ try {
63
+ const development = await shouldUseDevelopmentNotificationService();
64
+ const lastTokenData = await getLastRegisteredTokenDataAsync();
65
+
66
+ if (lastTokenData == null) {
67
+ return true;
68
+ }
69
+
70
+ const age = Date.now() - (lastTokenData.registeredAt ?? 0);
71
+ if (age < 0 || age >= REGISTRATION_TTL_MS) {
72
+ return true;
73
+ }
74
+
75
+ return (
76
+ token.data !== lastTokenData.deviceToken ||
77
+ Application.applicationId !== lastTokenData.appId ||
78
+ development !== lastTokenData.development ||
79
+ getTypeOfToken(token) !== lastTokenData.type
80
+ );
81
+ } catch {
82
+ return true;
83
+ }
84
+ }
85
+
10
86
  export async function updateDevicePushTokenAsync(signal: AbortSignal, token: DevicePushToken) {
11
87
  const doUpdateDevicePushTokenAsync = async (retry: () => void) => {
12
88
  const [development, deviceId] = await Promise.all([
@@ -39,6 +115,16 @@ export async function updateDevicePushTokenAsync(signal: AbortSignal, token: Dev
39
115
  );
40
116
  }
41
117
 
118
+ if (response.ok) {
119
+ await setLastRegisteredTokenDataAsync({
120
+ deviceToken: token.data,
121
+ appId: Application.applicationId,
122
+ development,
123
+ type: getTypeOfToken(token),
124
+ registeredAt: Date.now(),
125
+ });
126
+ }
127
+
42
128
  // Retry if request failed
43
129
  if (!response.ok) {
44
130
  retry();
@@ -1 +0,0 @@
1
- bccfd8b2373cfd3d0ffbb7189d623d983a257eb823f473db402120a0354dce38
@@ -1 +0,0 @@
1
- 899be90bb811cdf108351dcf88a808ae5ea2ad9d35b8fccb270b10b4352b5a00761b8317bbc8c4f32ce3f091aebfac44ea369b118a06e968c0200f2c98d04b74
@@ -1 +0,0 @@
1
- 04ee18d00b24a0a4493ae3a7c562ff9d5c165ad6386fc517ffe869e71580cba6
@@ -1 +0,0 @@
1
- 0b17ac8f475fbf20f5cad7c6798f21d835aeefe522f539e55ab8e8fae642a13cf70c37be4c3ee59eb244128e5fc64982518408ea251315f09360f5188def9708