expo-notifications 0.28.13 → 0.28.14

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.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,12 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 0.28.14 — 2024-07-30
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - `useLastNotificationResponse` should have only one effect. ([#30653](https://github.com/expo/expo/pull/30653) by [@douglowder](https://github.com/douglowder))
18
+
13
19
  ## 0.28.13 — 2024-07-29
14
20
 
15
21
  ### 🐛 Bug fixes
@@ -1,7 +1,7 @@
1
1
  apply plugin: 'com.android.library'
2
2
 
3
3
  group = 'host.exp.exponent'
4
- version = '0.28.13'
4
+ version = '0.28.14'
5
5
 
6
6
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
7
7
  apply from: expoModulesCorePlugin
@@ -14,7 +14,7 @@ android {
14
14
  namespace "expo.modules.notifications"
15
15
  defaultConfig {
16
16
  versionCode 21
17
- versionName '0.28.13'
17
+ versionName '0.28.14'
18
18
  }
19
19
 
20
20
  buildFeatures {
@@ -1,4 +1,5 @@
1
1
  import { NotificationResponse } from './Notifications.types';
2
+ type MaybeNotificationResponse = NotificationResponse | null | undefined;
2
3
  /**
3
4
  * A React hook always returns the notification response that was received most recently
4
5
  * (a notification response designates an interaction with a notification, such as tapping on it).
@@ -35,5 +36,6 @@ import { NotificationResponse } from './Notifications.types';
35
36
  * ```
36
37
  * @header listen
37
38
  */
38
- export default function useLastNotificationResponse(): NotificationResponse | null | undefined;
39
+ export default function useLastNotificationResponse(): MaybeNotificationResponse;
40
+ export {};
39
41
  //# sourceMappingURL=useLastNotificationResponse.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useLastNotificationResponse.d.ts","sourceRoot":"","sources":["../src/useLastNotificationResponse.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAK7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,CAAC,OAAO,UAAU,2BAA2B,4CA0BlD"}
1
+ {"version":3,"file":"useLastNotificationResponse.d.ts","sourceRoot":"","sources":["../src/useLastNotificationResponse.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAM7D,KAAK,yBAAyB,GAAG,oBAAoB,GAAG,IAAI,GAAG,SAAS,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,CAAC,OAAO,UAAU,2BAA2B,8BA0ClD"}
@@ -1,7 +1,5 @@
1
- import { useEffect, useLayoutEffect, useState } from 'react';
2
- import { addNotificationResponseReceivedListener } from './NotificationsEmitter';
3
- import NotificationsEmitterModule from './NotificationsEmitterModule';
4
- import { mapNotificationResponse } from './utils/mapNotificationResponse';
1
+ import { useLayoutEffect, useState } from 'react';
2
+ import { addNotificationResponseReceivedListener, getLastNotificationResponseAsync, } from './NotificationsEmitter';
5
3
  /**
6
4
  * A React hook always returns the notification response that was received most recently
7
5
  * (a notification response designates an interaction with a notification, such as tapping on it).
@@ -40,24 +38,33 @@ import { mapNotificationResponse } from './utils/mapNotificationResponse';
40
38
  */
41
39
  export default function useLastNotificationResponse() {
42
40
  const [lastNotificationResponse, setLastNotificationResponse] = useState(undefined);
41
+ // Pure function that returns the new response if it is different from the previous,
42
+ // otherwise return the previous response
43
+ const newResponseIfNeeded = (prevResponse, newResponse) => {
44
+ // If the new response is undefined or null, no need for update
45
+ if (!newResponse) {
46
+ return prevResponse;
47
+ }
48
+ // If the previous response is undefined or null and the new response is not, we should update
49
+ if (!prevResponse) {
50
+ return newResponse;
51
+ }
52
+ return prevResponse.notification.request.identifier !==
53
+ newResponse.notification.request.identifier
54
+ ? newResponse
55
+ : prevResponse;
56
+ };
43
57
  // useLayoutEffect ensures the listener is registered as soon as possible
44
58
  useLayoutEffect(() => {
45
- const subscription = addNotificationResponseReceivedListener((response) => {
46
- const mappedResponse = mapNotificationResponse(response);
47
- setLastNotificationResponse(mappedResponse);
48
- });
59
+ // Get the last response first, in case it was set earlier (even in native code on startup)
60
+ // before this renders
61
+ getLastNotificationResponseAsync?.().then((response) => setLastNotificationResponse((prevResponse) => newResponseIfNeeded(prevResponse, response)));
62
+ // Set up listener for responses that come in, and set the last response if needed
63
+ const subscription = addNotificationResponseReceivedListener((response) => setLastNotificationResponse((prevResponse) => newResponseIfNeeded(prevResponse, response)));
49
64
  return () => {
50
65
  subscription.remove();
51
66
  };
52
67
  }, []);
53
- // On each mount of this hook we fetch last notification response
54
- // from the native module which is an "always active listener"
55
- // and always returns the most recent response.
56
- useEffect(() => {
57
- NotificationsEmitterModule.getLastNotificationResponseAsync?.().then((response) => {
58
- setLastNotificationResponse(response);
59
- });
60
- }, []);
61
68
  return lastNotificationResponse;
62
69
  }
63
70
  //# sourceMappingURL=useLastNotificationResponse.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useLastNotificationResponse.js","sourceRoot":"","sources":["../src/useLastNotificationResponse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG7D,OAAO,EAAE,uCAAuC,EAAE,MAAM,wBAAwB,CAAC;AACjF,OAAO,0BAA0B,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,CAAC,OAAO,UAAU,2BAA2B;IACjD,MAAM,CAAC,wBAAwB,EAAE,2BAA2B,CAAC,GAAG,QAAQ,CAEtE,SAAS,CAAC,CAAC;IAEb,yEAAyE;IACzE,eAAe,CAAC,GAAG,EAAE;QACnB,MAAM,YAAY,GAAG,uCAAuC,CAAC,CAAC,QAAQ,EAAE,EAAE;YACxE,MAAM,cAAc,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAC;YACzD,2BAA2B,CAAC,cAAc,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,EAAE;YACV,YAAY,CAAC,MAAM,EAAE,CAAC;QACxB,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,iEAAiE;IACjE,8DAA8D;IAC9D,+CAA+C;IAC/C,SAAS,CAAC,GAAG,EAAE;QACb,0BAA0B,CAAC,gCAAgC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;YAChF,2BAA2B,CAAC,QAAQ,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,wBAAwB,CAAC;AAClC,CAAC","sourcesContent":["import { useEffect, useLayoutEffect, useState } from 'react';\n\nimport { NotificationResponse } from './Notifications.types';\nimport { addNotificationResponseReceivedListener } from './NotificationsEmitter';\nimport NotificationsEmitterModule from './NotificationsEmitterModule';\nimport { mapNotificationResponse } from './utils/mapNotificationResponse';\n\n/**\n * A React hook always returns the notification response that was received most recently\n * (a notification response designates an interaction with a notification, such as tapping on it).\n *\n * > If you don't want to use a hook, you can use `Notifications.getLastNotificationResponseAsync()` instead.\n *\n * @return The hook may return one of these three types/values:\n * - `undefined` - until we're sure of what to return,\n * - `null` - if no notification response has been received yet,\n * - a [`NotificationResponse`](#notificationresponse) object - if a notification response was received.\n *\n * @example\n * Responding to a notification tap by opening a URL that could be put into the notification's `data`\n * (opening the URL is your responsibility and is not a part of the `expo-notifications` API):\n * ```jsx\n * import * as Notifications from 'expo-notifications';\n * import { Linking } from 'react-native';\n *\n * export default function App() {\n * const lastNotificationResponse = Notifications.useLastNotificationResponse();\n * React.useEffect(() => {\n * if (\n * lastNotificationResponse &&\n * lastNotificationResponse.notification.request.content.data.url &&\n * lastNotificationResponse.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER\n * ) {\n * Linking.openURL(lastNotificationResponse.notification.request.content.data.url);\n * }\n * }, [lastNotificationResponse]);\n * return (\n * // Your app content\n * );\n * }\n * ```\n * @header listen\n */\nexport default function useLastNotificationResponse() {\n const [lastNotificationResponse, setLastNotificationResponse] = useState<\n NotificationResponse | null | undefined\n >(undefined);\n\n // useLayoutEffect ensures the listener is registered as soon as possible\n useLayoutEffect(() => {\n const subscription = addNotificationResponseReceivedListener((response) => {\n const mappedResponse = mapNotificationResponse(response);\n setLastNotificationResponse(mappedResponse);\n });\n return () => {\n subscription.remove();\n };\n }, []);\n\n // On each mount of this hook we fetch last notification response\n // from the native module which is an \"always active listener\"\n // and always returns the most recent response.\n useEffect(() => {\n NotificationsEmitterModule.getLastNotificationResponseAsync?.().then((response) => {\n setLastNotificationResponse(response);\n });\n }, []);\n\n return lastNotificationResponse;\n}\n"]}
1
+ {"version":3,"file":"useLastNotificationResponse.js","sourceRoot":"","sources":["../src/useLastNotificationResponse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGlD,OAAO,EACL,uCAAuC,EACvC,gCAAgC,GACjC,MAAM,wBAAwB,CAAC;AAIhC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,CAAC,OAAO,UAAU,2BAA2B;IACjD,MAAM,CAAC,wBAAwB,EAAE,2BAA2B,CAAC,GAC3D,QAAQ,CAA4B,SAAS,CAAC,CAAC;IAEjD,oFAAoF;IACpF,yCAAyC;IACzC,MAAM,mBAAmB,GAAG,CAC1B,YAAuC,EACvC,WAAsC,EACtC,EAAE;QACF,+DAA+D;QAC/D,IAAI,CAAC,WAAW,EAAE;YAChB,OAAO,YAAY,CAAC;SACrB;QACD,8FAA8F;QAC9F,IAAI,CAAC,YAAY,EAAE;YACjB,OAAO,WAAW,CAAC;SACpB;QACD,OAAO,YAAY,CAAC,YAAY,CAAC,OAAO,CAAC,UAAU;YACjD,WAAW,CAAC,YAAY,CAAC,OAAO,CAAC,UAAU;YAC3C,CAAC,CAAC,WAAW;YACb,CAAC,CAAC,YAAY,CAAC;IACnB,CAAC,CAAC;IAEF,yEAAyE;IACzE,eAAe,CAAC,GAAG,EAAE;QACnB,2FAA2F;QAC3F,sBAAsB;QACtB,gCAAgC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CACrD,2BAA2B,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,mBAAmB,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAC3F,CAAC;QAEF,kFAAkF;QAClF,MAAM,YAAY,GAAG,uCAAuC,CAAC,CAAC,QAAQ,EAAE,EAAE,CACxE,2BAA2B,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,mBAAmB,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAC3F,CAAC;QACF,OAAO,GAAG,EAAE;YACV,YAAY,CAAC,MAAM,EAAE,CAAC;QACxB,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,wBAAwB,CAAC;AAClC,CAAC","sourcesContent":["import { useLayoutEffect, useState } from 'react';\n\nimport { NotificationResponse } from './Notifications.types';\nimport {\n addNotificationResponseReceivedListener,\n getLastNotificationResponseAsync,\n} from './NotificationsEmitter';\n\ntype MaybeNotificationResponse = NotificationResponse | null | undefined;\n\n/**\n * A React hook always returns the notification response that was received most recently\n * (a notification response designates an interaction with a notification, such as tapping on it).\n *\n * > If you don't want to use a hook, you can use `Notifications.getLastNotificationResponseAsync()` instead.\n *\n * @return The hook may return one of these three types/values:\n * - `undefined` - until we're sure of what to return,\n * - `null` - if no notification response has been received yet,\n * - a [`NotificationResponse`](#notificationresponse) object - if a notification response was received.\n *\n * @example\n * Responding to a notification tap by opening a URL that could be put into the notification's `data`\n * (opening the URL is your responsibility and is not a part of the `expo-notifications` API):\n * ```jsx\n * import * as Notifications from 'expo-notifications';\n * import { Linking } from 'react-native';\n *\n * export default function App() {\n * const lastNotificationResponse = Notifications.useLastNotificationResponse();\n * React.useEffect(() => {\n * if (\n * lastNotificationResponse &&\n * lastNotificationResponse.notification.request.content.data.url &&\n * lastNotificationResponse.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER\n * ) {\n * Linking.openURL(lastNotificationResponse.notification.request.content.data.url);\n * }\n * }, [lastNotificationResponse]);\n * return (\n * // Your app content\n * );\n * }\n * ```\n * @header listen\n */\nexport default function useLastNotificationResponse() {\n const [lastNotificationResponse, setLastNotificationResponse] =\n useState<MaybeNotificationResponse>(undefined);\n\n // Pure function that returns the new response if it is different from the previous,\n // otherwise return the previous response\n const newResponseIfNeeded = (\n prevResponse: MaybeNotificationResponse,\n newResponse: MaybeNotificationResponse\n ) => {\n // If the new response is undefined or null, no need for update\n if (!newResponse) {\n return prevResponse;\n }\n // If the previous response is undefined or null and the new response is not, we should update\n if (!prevResponse) {\n return newResponse;\n }\n return prevResponse.notification.request.identifier !==\n newResponse.notification.request.identifier\n ? newResponse\n : prevResponse;\n };\n\n // useLayoutEffect ensures the listener is registered as soon as possible\n useLayoutEffect(() => {\n // Get the last response first, in case it was set earlier (even in native code on startup)\n // before this renders\n getLastNotificationResponseAsync?.().then((response) =>\n setLastNotificationResponse((prevResponse) => newResponseIfNeeded(prevResponse, response))\n );\n\n // Set up listener for responses that come in, and set the last response if needed\n const subscription = addNotificationResponseReceivedListener((response) =>\n setLastNotificationResponse((prevResponse) => newResponseIfNeeded(prevResponse, response))\n );\n return () => {\n subscription.remove();\n };\n }, []);\n\n return lastNotificationResponse;\n}\n"]}
@@ -12,6 +12,8 @@ NS_ASSUME_NONNULL_BEGIN
12
12
 
13
13
  - (void)addDelegate:(id<EXNotificationsDelegate>)delegate;
14
14
  - (void)removeDelegate:(id<EXNotificationsDelegate>)delegate;
15
+ - (nullable UNNotificationResponse *)lastNotificationResponse;
16
+ - (void)setLastNotificationResponse:(nullable UNNotificationResponse *)response;
15
17
 
16
18
  @end
17
19
 
@@ -24,6 +26,8 @@ NS_ASSUME_NONNULL_BEGIN
24
26
  - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler;
25
27
  - (void)userNotificationCenter:(UNUserNotificationCenter *)center openSettingsForNotification:(nullable UNNotification *)notification;
26
28
 
29
+ @property (nonatomic, strong, nullable) UNNotificationResponse *lastNotificationResponse;
30
+
27
31
  @end
28
32
 
29
33
  NS_ASSUME_NONNULL_END
@@ -123,6 +123,8 @@ EX_REGISTER_SINGLETON_MODULE(NotificationCenterDelegate);
123
123
 
124
124
  - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
125
125
  {
126
+ // Save last response here for use by EXNotificationsEmitter
127
+ self.lastNotificationResponse = response;
126
128
  // Save response to pending responses array if none of the handlers will handle it.
127
129
  BOOL responseWillBeHandledByAppropriateDelegate = NO;
128
130
  for (int i = 0; i < _delegates.count; i++) {
@@ -15,8 +15,6 @@
15
15
 
16
16
  @property (nonatomic, weak) id<EXEventEmitterService> eventEmitter;
17
17
 
18
- @property (nonatomic, strong) UNNotificationResponse *lastNotificationResponse;
19
-
20
18
  @end
21
19
 
22
20
  @implementation EXNotificationsEmitter
@@ -26,7 +24,8 @@ EX_EXPORT_MODULE(ExpoNotificationsEmitter);
26
24
  EX_EXPORT_METHOD_AS(getLastNotificationResponseAsync,
27
25
  getLastNotificationResponseAsyncWithResolver:(EXPromiseResolveBlock)resolve reject:(EXPromiseRejectBlock)reject)
28
26
  {
29
- resolve(_lastNotificationResponse ? [self serializedNotificationResponse:_lastNotificationResponse] : [NSNull null]);
27
+ UNNotificationResponse* lastResponse = _notificationCenterDelegate.lastNotificationResponse;
28
+ resolve(lastResponse ? [self serializedNotificationResponse:lastResponse] : [NSNull null]);
30
29
  }
31
30
 
32
31
  # pragma mark - EXModuleRegistryConsumer
@@ -77,7 +76,7 @@ EX_EXPORT_METHOD_AS(getLastNotificationResponseAsync,
77
76
 
78
77
  - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
79
78
  {
80
- _lastNotificationResponse = response;
79
+ _notificationCenterDelegate.lastNotificationResponse = response;
81
80
  [self sendEventWithName:onDidReceiveNotificationResponse body:[self serializedNotificationResponse:response]];
82
81
  completionHandler();
83
82
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-notifications",
3
- "version": "0.28.13",
3
+ "version": "0.28.14",
4
4
  "description": "Notifications module",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -55,5 +55,5 @@
55
55
  "peerDependencies": {
56
56
  "expo": "*"
57
57
  },
58
- "gitHead": "89cb316cd1f16193ae9841d2aec2ae39e0ee00bb"
58
+ "gitHead": "10eaf729822bf46ab821a000eb961cf8df40b35b"
59
59
  }
@@ -1,9 +1,12 @@
1
- import { useEffect, useLayoutEffect, useState } from 'react';
1
+ import { useLayoutEffect, useState } from 'react';
2
2
 
3
3
  import { NotificationResponse } from './Notifications.types';
4
- import { addNotificationResponseReceivedListener } from './NotificationsEmitter';
5
- import NotificationsEmitterModule from './NotificationsEmitterModule';
6
- import { mapNotificationResponse } from './utils/mapNotificationResponse';
4
+ import {
5
+ addNotificationResponseReceivedListener,
6
+ getLastNotificationResponseAsync,
7
+ } from './NotificationsEmitter';
8
+
9
+ type MaybeNotificationResponse = NotificationResponse | null | undefined;
7
10
 
8
11
  /**
9
12
  * A React hook always returns the notification response that was received most recently
@@ -42,29 +45,45 @@ import { mapNotificationResponse } from './utils/mapNotificationResponse';
42
45
  * @header listen
43
46
  */
44
47
  export default function useLastNotificationResponse() {
45
- const [lastNotificationResponse, setLastNotificationResponse] = useState<
46
- NotificationResponse | null | undefined
47
- >(undefined);
48
+ const [lastNotificationResponse, setLastNotificationResponse] =
49
+ useState<MaybeNotificationResponse>(undefined);
50
+
51
+ // Pure function that returns the new response if it is different from the previous,
52
+ // otherwise return the previous response
53
+ const newResponseIfNeeded = (
54
+ prevResponse: MaybeNotificationResponse,
55
+ newResponse: MaybeNotificationResponse
56
+ ) => {
57
+ // If the new response is undefined or null, no need for update
58
+ if (!newResponse) {
59
+ return prevResponse;
60
+ }
61
+ // If the previous response is undefined or null and the new response is not, we should update
62
+ if (!prevResponse) {
63
+ return newResponse;
64
+ }
65
+ return prevResponse.notification.request.identifier !==
66
+ newResponse.notification.request.identifier
67
+ ? newResponse
68
+ : prevResponse;
69
+ };
48
70
 
49
71
  // useLayoutEffect ensures the listener is registered as soon as possible
50
72
  useLayoutEffect(() => {
51
- const subscription = addNotificationResponseReceivedListener((response) => {
52
- const mappedResponse = mapNotificationResponse(response);
53
- setLastNotificationResponse(mappedResponse);
54
- });
73
+ // Get the last response first, in case it was set earlier (even in native code on startup)
74
+ // before this renders
75
+ getLastNotificationResponseAsync?.().then((response) =>
76
+ setLastNotificationResponse((prevResponse) => newResponseIfNeeded(prevResponse, response))
77
+ );
78
+
79
+ // Set up listener for responses that come in, and set the last response if needed
80
+ const subscription = addNotificationResponseReceivedListener((response) =>
81
+ setLastNotificationResponse((prevResponse) => newResponseIfNeeded(prevResponse, response))
82
+ );
55
83
  return () => {
56
84
  subscription.remove();
57
85
  };
58
86
  }, []);
59
87
 
60
- // On each mount of this hook we fetch last notification response
61
- // from the native module which is an "always active listener"
62
- // and always returns the most recent response.
63
- useEffect(() => {
64
- NotificationsEmitterModule.getLastNotificationResponseAsync?.().then((response) => {
65
- setLastNotificationResponse(response);
66
- });
67
- }, []);
68
-
69
88
  return lastNotificationResponse;
70
89
  }