From 03846faa9ab18c3899b9966951637d550b4c561a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Chmiela?= Date: Wed, 12 Feb 2020 09:42:44 +0100 Subject: [PATCH] [expo-notifications] Handling notifications (#6796) # Why Next `expo-notifications` feature. # How - `NotificationsHandlerModule` registers at singleton for new notifications/messages - for each message it _starts up_ a task which emits an event to JS - in response to the JS event, delegate responds with the appropriate behavior (eg. `shouldShowAlert: true`) - the behavior is pushed to native side using `NotificationsHandler.handleNotificationAsync` call - which directs it to the appropriate task - task handles the behavior (on iOS calls `completionHandler`, on Android it will show the notification once implemented) and finishes - if for whatever reason delegate didn't respond in 3 seconds, `onTimeout` is called on task, which emits another event to JS (for debugging purposes) and the task finishes ![excalidraw-202012311929-7](https://user-images.githubusercontent.com/1151041/73078318-3ca14180-3ec2-11ea-9220-c7a2f3c1e558.png) # Test Plan Tested manually by sending notifications and logging messages that the scheme works both when the delegate responds and when it does not. --- .../.project_cache/installation_cache.yaml | 4 + .../EXNotifications.xcodeproj/project.pbxproj | 255 ++++++++++-------- .../EXNotificationsHandlerModule.h | 1 + .../EXSingleNotificationHandlerTask.h | 1 + .../EXNotificationsHandlerModule.h | 1 + .../EXSingleNotificationHandlerTask.h | 1 + apps/test-suite/tests/NewNotifications.js | 192 +++++++++++++ .../notifications/NotificationsPackage.java | 4 +- .../handling/NotificationsHandler.java | 152 +++++++++++ .../SingleNotificationHandlerTask.java | 141 ++++++++++ .../interfaces/NotificationBehavior.java | 29 ++ .../build/NotificationsHandler.d.ts | 17 ++ .../build/NotificationsHandler.js | 42 +++ .../build/NotificationsHandler.js.map | 1 + .../build/NotificationsHandlerModule.d.ts | 11 + .../build/NotificationsHandlerModule.js | 3 + .../build/NotificationsHandlerModule.js.map | 1 + packages/expo-notifications/build/index.d.ts | 1 + packages/expo-notifications/build/index.js | 1 + .../expo-notifications/build/index.js.map | 2 +- .../Emitter/EXNotificationsEmitter.m | 2 +- .../Handler/EXNotificationsHandlerModule.h | 12 + .../Handler/EXNotificationsHandlerModule.m | 109 ++++++++ .../Handler/EXSingleNotificationHandlerTask.h | 33 +++ .../Handler/EXSingleNotificationHandlerTask.m | 123 +++++++++ .../src/NotificationsHandler.ts | 71 +++++ .../src/NotificationsHandlerModule.ts | 16 ++ packages/expo-notifications/src/index.ts | 1 + 28 files changed, 1109 insertions(+), 118 deletions(-) create mode 120000 apps/bare-expo/ios/Pods/Headers/Private/EXNotifications/EXNotificationsHandlerModule.h create mode 120000 apps/bare-expo/ios/Pods/Headers/Private/EXNotifications/EXSingleNotificationHandlerTask.h create mode 120000 apps/bare-expo/ios/Pods/Headers/Public/EXNotifications/EXNotificationsHandlerModule.h create mode 120000 apps/bare-expo/ios/Pods/Headers/Public/EXNotifications/EXSingleNotificationHandlerTask.h create mode 100644 packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/handling/NotificationsHandler.java create mode 100644 packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/handling/SingleNotificationHandlerTask.java create mode 100644 packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationBehavior.java create mode 100644 packages/expo-notifications/build/NotificationsHandler.d.ts create mode 100644 packages/expo-notifications/build/NotificationsHandler.js create mode 100644 packages/expo-notifications/build/NotificationsHandler.js.map create mode 100644 packages/expo-notifications/build/NotificationsHandlerModule.d.ts create mode 100644 packages/expo-notifications/build/NotificationsHandlerModule.js create mode 100644 packages/expo-notifications/build/NotificationsHandlerModule.js.map create mode 100644 packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXNotificationsHandlerModule.h create mode 100644 packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXNotificationsHandlerModule.m create mode 100644 packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXSingleNotificationHandlerTask.h create mode 100644 packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXSingleNotificationHandlerTask.m create mode 100644 packages/expo-notifications/src/NotificationsHandler.ts create mode 100644 packages/expo-notifications/src/NotificationsHandlerModule.ts diff --git a/apps/bare-expo/ios/Pods/.project_cache/installation_cache.yaml b/apps/bare-expo/ios/Pods/.project_cache/installation_cache.yaml index 9f223ff5afefc..888edc72bd719 100644 --- a/apps/bare-expo/ios/Pods/.project_cache/installation_cache.yaml +++ b/apps/bare-expo/ios/Pods/.project_cache/installation_cache.yaml @@ -538,6 +538,10 @@ CACHE_KEYS: - "../../../../packages/expo-notifications/ios/EXNotifications/Notifications/EXNotificationsDelegate.h" - "../../../../packages/expo-notifications/ios/EXNotifications/Notifications/EXNotificationSerializer.h" - "../../../../packages/expo-notifications/ios/EXNotifications/Notifications/EXNotificationSerializer.m" + - "../../../../packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXNotificationsHandlerModule.h" + - "../../../../packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXNotificationsHandlerModule.m" + - "../../../../packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXSingleNotificationHandlerTask.h" + - "../../../../packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXSingleNotificationHandlerTask.m" - "../../../../packages/expo-notifications/ios/EXNotifications/PushToken/EXPushTokenListener.h" - "../../../../packages/expo-notifications/ios/EXNotifications/PushToken/EXPushTokenManager.h" - "../../../../packages/expo-notifications/ios/EXNotifications/PushToken/EXPushTokenManager.m" diff --git a/apps/bare-expo/ios/Pods/EXNotifications.xcodeproj/project.pbxproj b/apps/bare-expo/ios/Pods/EXNotifications.xcodeproj/project.pbxproj index 2bc99b689502b..36edc834db6b8 100644 --- a/apps/bare-expo/ios/Pods/EXNotifications.xcodeproj/project.pbxproj +++ b/apps/bare-expo/ios/Pods/EXNotifications.xcodeproj/project.pbxproj @@ -7,25 +7,29 @@ objects = { /* Begin PBXBuildFile section */ - 2403DA92B2AB424356AC7C0A36F852B2 /* EXNotificationsEmitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 95F5C4C8DB075210B467121DEC8ED50E /* EXNotificationsEmitter.m */; }; - 2B759016E5CD3BC497FC000E436B1FE4 /* EXNotificationSerializer.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E95AE9DF87010FFE89BC3B82524DF42 /* EXNotificationSerializer.m */; }; - 57E385C9DFEEC3DF92CEF1F8BAC1CF19 /* EXPushTokenManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FC8854A995EFA90AC97180618201101 /* EXPushTokenManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; - 8BF8948071BBBCC350ED9AD5A6EA3A3F /* EXNotificationsDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 6D8A6D19214799E27F430CAB77089C5B /* EXNotificationsDelegate.h */; settings = {ATTRIBUTES = (Project, ); }; }; - 9E60E265AA0CEE4595002599C024E5C2 /* EXPushTokenModule.h in Headers */ = {isa = PBXBuildFile; fileRef = C7A66B706E967B07FCB8AF1C502104A5 /* EXPushTokenModule.h */; settings = {ATTRIBUTES = (Project, ); }; }; - A77FFD3D8608E069C8E762C93517F99C /* EXPushTokenModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C16A883C91D437781C3DA1C51788E55 /* EXPushTokenModule.m */; }; - A8A2C0A487684DFE1F4943405D6F815E /* EXNotifications-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = EC235B1D6FB76AFF357AE11E853A43E3 /* EXNotifications-dummy.m */; }; - AAEEADDB80B8D0E200BA6873BA16EB36 /* EXInstallationIdProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = D5D23AB0C5481A2F38E01A24D112640B /* EXInstallationIdProvider.h */; settings = {ATTRIBUTES = (Project, ); }; }; - ABF5F729820C4EF02B73CB6C383F5D80 /* EXNotificationCenterDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76C48E1EDF257B1A46E4030F09E26CAC /* EXNotificationCenterDelegate.m */; }; - B589C5C7A1296F78DD4F777C26082AD8 /* EXPushTokenListener.h in Headers */ = {isa = PBXBuildFile; fileRef = 83074864F366215C58F0FE38E267B795 /* EXPushTokenListener.h */; settings = {ATTRIBUTES = (Project, ); }; }; - B5D6026C75854B4214E7E633FF6C1DC9 /* EXInstallationIdProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DF6FD7A1BFE3D1F972739FFE25ABA91 /* EXInstallationIdProvider.m */; }; - C5D336198E19808B95CBD4B836538377 /* EXNotificationSerializer.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C40D8645119CF8993F4D8E1A05E4E5E /* EXNotificationSerializer.h */; settings = {ATTRIBUTES = (Project, ); }; }; - D6792D001F48E8D80D1E437A3919D46B /* EXNotificationsEmitter.h in Headers */ = {isa = PBXBuildFile; fileRef = CCDE00C0C4E95802A8518BCBB213DC79 /* EXNotificationsEmitter.h */; settings = {ATTRIBUTES = (Project, ); }; }; - E1153B29228819FDEE381E4C4DE7B4ED /* EXNotificationCenterDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = C5B1A6953047FFCFFEEC0CC9D23C73CF /* EXNotificationCenterDelegate.h */; settings = {ATTRIBUTES = (Project, ); }; }; - F083BF85702BCF8220A30F28CA19CB14 /* EXPushTokenManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 6FAD029FAC0D833E6746CA5A5B668A41 /* EXPushTokenManager.m */; }; + 0A9F02D29FAAF79BFE4EFF5BC350C959 /* EXPushTokenManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F8BE426EA812A6A184A741938A40A048 /* EXPushTokenManager.m */; }; + 1BB38E271192F4DC3E0248D136F39E71 /* EXNotificationCenterDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AFEED92029A0C485C7EFACEB4EED2CB /* EXNotificationCenterDelegate.m */; }; + 1D196422E60ED8DAF37D2DBE4F83EC8A /* EXNotificationsHandlerModule.h in Headers */ = {isa = PBXBuildFile; fileRef = 68F4C8EFD3AD6916C0E2485491057577 /* EXNotificationsHandlerModule.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 285A73EBD92BB65500ED8D35E89D7909 /* EXNotificationsDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 6AAE105DB2D2AAD77B671597F79CB3D4 /* EXNotificationsDelegate.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3385B443BE2D816E57F8E11EB836D990 /* EXNotifications-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = CE1CA2299D24B84B742BC2797ACF8EEF /* EXNotifications-dummy.m */; }; + 4674C63655FD4DEC773EFE5815C5D9AD /* EXInstallationIdProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B320E0DA9B55BCB8CC5CDBF4C31DADD /* EXInstallationIdProvider.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4F82584F6D9012B31C8A574F74956910 /* EXPushTokenManager.h in Headers */ = {isa = PBXBuildFile; fileRef = A435C33CCABC59E6CB8E139F726EB685 /* EXPushTokenManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5A1C0183CCD2EE78C5604C2BF0482320 /* EXSingleNotificationHandlerTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 30252D2FD019B4E6EAC25011E43A742C /* EXSingleNotificationHandlerTask.m */; }; + 60717E866FA2C7D622E48667D3604045 /* EXNotificationsEmitter.h in Headers */ = {isa = PBXBuildFile; fileRef = B68CAD835200FAF5BC1F9FBA094BDB82 /* EXNotificationsEmitter.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 6C471E802BA2B8970F6DAC01FFCED653 /* EXPushTokenListener.h in Headers */ = {isa = PBXBuildFile; fileRef = 3BF57E0783D2EDC78C03851A33994C42 /* EXPushTokenListener.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 7ED0109EA6513BF855B0D1BC5C8D1681 /* EXPushTokenModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EB5949B308D6CCC759A44BF56C62FC3 /* EXPushTokenModule.m */; }; + 908CAD70193FA81AD89417B650AE4E31 /* EXInstallationIdProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = BAFFA7B049E4FB833936325D9093E0B2 /* EXInstallationIdProvider.m */; }; + 93BECF2E5C5366AD8FC8B4F67C50146B /* EXPushTokenModule.h in Headers */ = {isa = PBXBuildFile; fileRef = 505839B3CCC732557CD743A88430BAC6 /* EXPushTokenModule.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 95F2AB42789C22726C3201D94EDDED42 /* EXSingleNotificationHandlerTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 6D3270C41990310C48F7E60C82E4DAB9 /* EXSingleNotificationHandlerTask.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 9D4C7A28BF30EE08548BFD0F7A803556 /* EXNotificationsHandlerModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 78B7882E94A5EE5CBA35C8CB11E18436 /* EXNotificationsHandlerModule.m */; }; + A731FE89B3A06321EBB66041F59B272A /* EXNotificationCenterDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 5A444B0DE38FFDD1D8F9268309AB99F6 /* EXNotificationCenterDelegate.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D63110D99F6FC237A16383D01058AF90 /* EXNotificationSerializer.m in Sources */ = {isa = PBXBuildFile; fileRef = 9789B3BA470EB606A6EC873B44E9FFD4 /* EXNotificationSerializer.m */; }; + EA085F37FD7E2A69E2714E63AD651A68 /* EXNotificationsEmitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 913637EC106B10E4DDD6933C493BD1A9 /* EXNotificationsEmitter.m */; }; + F8F45D0135DDF5B5DE93E316999A95A9 /* EXNotificationSerializer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC260D931637EC286C641E8BE55EE16D /* EXNotificationSerializer.h */; settings = {ATTRIBUTES = (Project, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 70B999E09C718FCAD7580FF36BC8DEFD /* PBXContainerItemProxy */ = { + 40D6170A8F43E31F8A3BD3A188179825 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 6B4054ABD2AFCAF4186CB91DD1245387 /* UMCore.xcodeproj */; proxyType = 1; @@ -35,30 +39,34 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 1E95AE9DF87010FFE89BC3B82524DF42 /* EXNotificationSerializer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = EXNotificationSerializer.m; sourceTree = ""; }; - 1FC8854A995EFA90AC97180618201101 /* EXPushTokenManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = EXPushTokenManager.h; sourceTree = ""; }; - 20F4ABA44B9E7A0CB3804A5F60BD6427 /* EXNotifications.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; path = EXNotifications.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - 3C16A883C91D437781C3DA1C51788E55 /* EXPushTokenModule.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = EXPushTokenModule.m; sourceTree = ""; }; - 4C40D8645119CF8993F4D8E1A05E4E5E /* EXNotificationSerializer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = EXNotificationSerializer.h; sourceTree = ""; }; + 129D095CF082A413DECE89C457BF5C16 /* EXNotifications-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "EXNotifications-prefix.pch"; sourceTree = ""; }; + 1AFEED92029A0C485C7EFACEB4EED2CB /* EXNotificationCenterDelegate.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = EXNotificationCenterDelegate.m; sourceTree = ""; }; + 1EB5949B308D6CCC759A44BF56C62FC3 /* EXPushTokenModule.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = EXPushTokenModule.m; sourceTree = ""; }; + 30252D2FD019B4E6EAC25011E43A742C /* EXSingleNotificationHandlerTask.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = EXSingleNotificationHandlerTask.m; sourceTree = ""; }; + 37CE36768BBE9431FB034D2F3C3D1455 /* EXNotifications.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; path = EXNotifications.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 3BF57E0783D2EDC78C03851A33994C42 /* EXPushTokenListener.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = EXPushTokenListener.h; sourceTree = ""; }; + 505839B3CCC732557CD743A88430BAC6 /* EXPushTokenModule.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = EXPushTokenModule.h; sourceTree = ""; }; + 5A444B0DE38FFDD1D8F9268309AB99F6 /* EXNotificationCenterDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = EXNotificationCenterDelegate.h; sourceTree = ""; }; + 68F4C8EFD3AD6916C0E2485491057577 /* EXNotificationsHandlerModule.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = EXNotificationsHandlerModule.h; sourceTree = ""; }; + 6AAE105DB2D2AAD77B671597F79CB3D4 /* EXNotificationsDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = EXNotificationsDelegate.h; sourceTree = ""; }; 6B4054ABD2AFCAF4186CB91DD1245387 /* UMCore */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = UMCore; path = UMCore.xcodeproj; sourceTree = ""; }; - 6D8A6D19214799E27F430CAB77089C5B /* EXNotificationsDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = EXNotificationsDelegate.h; sourceTree = ""; }; - 6FAD029FAC0D833E6746CA5A5B668A41 /* EXPushTokenManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = EXPushTokenManager.m; sourceTree = ""; }; - 76C48E1EDF257B1A46E4030F09E26CAC /* EXNotificationCenterDelegate.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = EXNotificationCenterDelegate.m; sourceTree = ""; }; - 7DF6FD7A1BFE3D1F972739FFE25ABA91 /* EXInstallationIdProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = EXInstallationIdProvider.m; path = EXNotifications/EXInstallationIdProvider.m; sourceTree = ""; }; - 83074864F366215C58F0FE38E267B795 /* EXPushTokenListener.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = EXPushTokenListener.h; sourceTree = ""; }; - 95F5C4C8DB075210B467121DEC8ED50E /* EXNotificationsEmitter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = EXNotificationsEmitter.m; sourceTree = ""; }; - 9ECD98D5B9A9844D449C9DBF79BDD71C /* EXNotifications.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = EXNotifications.xcconfig; sourceTree = ""; }; - A41E09EE2ACC3D5730BA743C40CBD30E /* EXNotifications-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "EXNotifications-prefix.pch"; sourceTree = ""; }; + 6D3270C41990310C48F7E60C82E4DAB9 /* EXSingleNotificationHandlerTask.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = EXSingleNotificationHandlerTask.h; sourceTree = ""; }; + 7573E122AAE7B6566A3A120ADE722FBE /* EXNotifications.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = EXNotifications.xcconfig; sourceTree = ""; }; + 78B7882E94A5EE5CBA35C8CB11E18436 /* EXNotificationsHandlerModule.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = EXNotificationsHandlerModule.m; sourceTree = ""; }; + 7B320E0DA9B55BCB8CC5CDBF4C31DADD /* EXInstallationIdProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = EXInstallationIdProvider.h; path = EXNotifications/EXInstallationIdProvider.h; sourceTree = ""; }; + 913637EC106B10E4DDD6933C493BD1A9 /* EXNotificationsEmitter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = EXNotificationsEmitter.m; sourceTree = ""; }; + 9789B3BA470EB606A6EC873B44E9FFD4 /* EXNotificationSerializer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = EXNotificationSerializer.m; sourceTree = ""; }; + A435C33CCABC59E6CB8E139F726EB685 /* EXPushTokenManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = EXPushTokenManager.h; sourceTree = ""; }; AB4F468E1EA9333F76F8937368ACD772 /* libEXNotifications.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; name = libEXNotifications.a; path = libEXNotifications.a; sourceTree = BUILT_PRODUCTS_DIR; }; - C5B1A6953047FFCFFEEC0CC9D23C73CF /* EXNotificationCenterDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = EXNotificationCenterDelegate.h; sourceTree = ""; }; - C7A66B706E967B07FCB8AF1C502104A5 /* EXPushTokenModule.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = EXPushTokenModule.h; sourceTree = ""; }; - CCDE00C0C4E95802A8518BCBB213DC79 /* EXNotificationsEmitter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = EXNotificationsEmitter.h; sourceTree = ""; }; - D5D23AB0C5481A2F38E01A24D112640B /* EXInstallationIdProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = EXInstallationIdProvider.h; path = EXNotifications/EXInstallationIdProvider.h; sourceTree = ""; }; - EC235B1D6FB76AFF357AE11E853A43E3 /* EXNotifications-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "EXNotifications-dummy.m"; sourceTree = ""; }; + B68CAD835200FAF5BC1F9FBA094BDB82 /* EXNotificationsEmitter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = EXNotificationsEmitter.h; sourceTree = ""; }; + BAFFA7B049E4FB833936325D9093E0B2 /* EXInstallationIdProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = EXInstallationIdProvider.m; path = EXNotifications/EXInstallationIdProvider.m; sourceTree = ""; }; + CE1CA2299D24B84B742BC2797ACF8EEF /* EXNotifications-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "EXNotifications-dummy.m"; sourceTree = ""; }; + DC260D931637EC286C641E8BE55EE16D /* EXNotificationSerializer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = EXNotificationSerializer.h; sourceTree = ""; }; + F8BE426EA812A6A184A741938A40A048 /* EXPushTokenManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = EXPushTokenManager.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 2709B01D6E7ACBB9F5BA7591EAABE945 /* Frameworks */ = { + 19EB2782D3C477970E5162E2F9F98F23 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( @@ -68,28 +76,19 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 0170389CF0D6509019171BD90E6E0563 /* Emitter */ = { + 118F6BF8D82ECFBDBD2AEECF1487A388 /* Notifications */ = { isa = PBXGroup; children = ( - CCDE00C0C4E95802A8518BCBB213DC79 /* EXNotificationsEmitter.h */, - 95F5C4C8DB075210B467121DEC8ED50E /* EXNotificationsEmitter.m */, + 5A444B0DE38FFDD1D8F9268309AB99F6 /* EXNotificationCenterDelegate.h */, + 1AFEED92029A0C485C7EFACEB4EED2CB /* EXNotificationCenterDelegate.m */, + 6AAE105DB2D2AAD77B671597F79CB3D4 /* EXNotificationsDelegate.h */, + DC260D931637EC286C641E8BE55EE16D /* EXNotificationSerializer.h */, + 9789B3BA470EB606A6EC873B44E9FFD4 /* EXNotificationSerializer.m */, + 7D8BB7DB07B9E1B89A91D56186CDCC27 /* Emitter */, + 2D73AC20C32E50AD12566281C9473955 /* Handler */, ); - name = Emitter; - path = Emitter; - sourceTree = ""; - }; - 0416756CC42BD9B1730B4EE56569B42A /* EXNotifications */ = { - isa = PBXGroup; - children = ( - D5D23AB0C5481A2F38E01A24D112640B /* EXInstallationIdProvider.h */, - 7DF6FD7A1BFE3D1F972739FFE25ABA91 /* EXInstallationIdProvider.m */, - E03C99A598A6BD348BBBA458DFFF04C9 /* Notifications */, - 9A54A9D821D651B8626FDAB3C990E53F /* Pod */, - B9C06906655EF86430A4C07B7E76CDE1 /* PushToken */, - 756521F045DB00A3E07AD06C43CE0CD6 /* Support Files */, - ); - name = EXNotifications; - path = "../../../../packages/expo-notifications/ios"; + name = Notifications; + path = EXNotifications/Notifications; sourceTree = ""; }; 11C75FA9F6E7BD19025CAA04B578E04E /* Products */ = { @@ -104,42 +103,51 @@ isa = PBXGroup; children = ( DE5C7B94E024D27CF89F5138081ED80B /* Dependencies */, - 0416756CC42BD9B1730B4EE56569B42A /* EXNotifications */, + D957AB5DAC863C21004D1513B9942C4D /* EXNotifications */, CCE96D56F43E72270B9F3BEC1866196F /* Frameworks */, 11C75FA9F6E7BD19025CAA04B578E04E /* Products */, ); sourceTree = ""; }; - 756521F045DB00A3E07AD06C43CE0CD6 /* Support Files */ = { + 2B34F307A5915D01FC67F15CDB04CD30 /* Pod */ = { isa = PBXGroup; children = ( - 9ECD98D5B9A9844D449C9DBF79BDD71C /* EXNotifications.xcconfig */, - EC235B1D6FB76AFF357AE11E853A43E3 /* EXNotifications-dummy.m */, - A41E09EE2ACC3D5730BA743C40CBD30E /* EXNotifications-prefix.pch */, + 37CE36768BBE9431FB034D2F3C3D1455 /* EXNotifications.podspec */, ); - name = "Support Files"; - path = "../../../apps/bare-expo/ios/Pods/Target Support Files/EXNotifications"; + name = Pod; sourceTree = ""; }; - 9A54A9D821D651B8626FDAB3C990E53F /* Pod */ = { + 2D73AC20C32E50AD12566281C9473955 /* Handler */ = { isa = PBXGroup; children = ( - 20F4ABA44B9E7A0CB3804A5F60BD6427 /* EXNotifications.podspec */, + 68F4C8EFD3AD6916C0E2485491057577 /* EXNotificationsHandlerModule.h */, + 78B7882E94A5EE5CBA35C8CB11E18436 /* EXNotificationsHandlerModule.m */, + 6D3270C41990310C48F7E60C82E4DAB9 /* EXSingleNotificationHandlerTask.h */, + 30252D2FD019B4E6EAC25011E43A742C /* EXSingleNotificationHandlerTask.m */, ); - name = Pod; + name = Handler; + path = Handler; sourceTree = ""; }; - B9C06906655EF86430A4C07B7E76CDE1 /* PushToken */ = { + 3978926E413BD5861AB4EC828FA2CC81 /* Support Files */ = { isa = PBXGroup; children = ( - 83074864F366215C58F0FE38E267B795 /* EXPushTokenListener.h */, - 1FC8854A995EFA90AC97180618201101 /* EXPushTokenManager.h */, - 6FAD029FAC0D833E6746CA5A5B668A41 /* EXPushTokenManager.m */, - C7A66B706E967B07FCB8AF1C502104A5 /* EXPushTokenModule.h */, - 3C16A883C91D437781C3DA1C51788E55 /* EXPushTokenModule.m */, + 7573E122AAE7B6566A3A120ADE722FBE /* EXNotifications.xcconfig */, + CE1CA2299D24B84B742BC2797ACF8EEF /* EXNotifications-dummy.m */, + 129D095CF082A413DECE89C457BF5C16 /* EXNotifications-prefix.pch */, ); - name = PushToken; - path = EXNotifications/PushToken; + name = "Support Files"; + path = "../../../apps/bare-expo/ios/Pods/Target Support Files/EXNotifications"; + sourceTree = ""; + }; + 7D8BB7DB07B9E1B89A91D56186CDCC27 /* Emitter */ = { + isa = PBXGroup; + children = ( + B68CAD835200FAF5BC1F9FBA094BDB82 /* EXNotificationsEmitter.h */, + 913637EC106B10E4DDD6933C493BD1A9 /* EXNotificationsEmitter.m */, + ); + name = Emitter; + path = Emitter; sourceTree = ""; }; CCE96D56F43E72270B9F3BEC1866196F /* Frameworks */ = { @@ -149,6 +157,20 @@ name = Frameworks; sourceTree = ""; }; + D957AB5DAC863C21004D1513B9942C4D /* EXNotifications */ = { + isa = PBXGroup; + children = ( + 7B320E0DA9B55BCB8CC5CDBF4C31DADD /* EXInstallationIdProvider.h */, + BAFFA7B049E4FB833936325D9093E0B2 /* EXInstallationIdProvider.m */, + 118F6BF8D82ECFBDBD2AEECF1487A388 /* Notifications */, + 2B34F307A5915D01FC67F15CDB04CD30 /* Pod */, + F6672C74BADEEE51EFD71A2C13714F31 /* PushToken */, + 3978926E413BD5861AB4EC828FA2CC81 /* Support Files */, + ); + name = EXNotifications; + path = "../../../../packages/expo-notifications/ios"; + sourceTree = ""; + }; DE5C7B94E024D27CF89F5138081ED80B /* Dependencies */ = { isa = PBXGroup; children = ( @@ -157,35 +179,36 @@ name = Dependencies; sourceTree = ""; }; - E03C99A598A6BD348BBBA458DFFF04C9 /* Notifications */ = { + F6672C74BADEEE51EFD71A2C13714F31 /* PushToken */ = { isa = PBXGroup; children = ( - C5B1A6953047FFCFFEEC0CC9D23C73CF /* EXNotificationCenterDelegate.h */, - 76C48E1EDF257B1A46E4030F09E26CAC /* EXNotificationCenterDelegate.m */, - 6D8A6D19214799E27F430CAB77089C5B /* EXNotificationsDelegate.h */, - 4C40D8645119CF8993F4D8E1A05E4E5E /* EXNotificationSerializer.h */, - 1E95AE9DF87010FFE89BC3B82524DF42 /* EXNotificationSerializer.m */, - 0170389CF0D6509019171BD90E6E0563 /* Emitter */, + 3BF57E0783D2EDC78C03851A33994C42 /* EXPushTokenListener.h */, + A435C33CCABC59E6CB8E139F726EB685 /* EXPushTokenManager.h */, + F8BE426EA812A6A184A741938A40A048 /* EXPushTokenManager.m */, + 505839B3CCC732557CD743A88430BAC6 /* EXPushTokenModule.h */, + 1EB5949B308D6CCC759A44BF56C62FC3 /* EXPushTokenModule.m */, ); - name = Notifications; - path = EXNotifications/Notifications; + name = PushToken; + path = EXNotifications/PushToken; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ - D4C68791AB6AA26F179FA0F69263E939 /* Headers */ = { + 50301B2968C1552BD526F38E01D87BB9 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - AAEEADDB80B8D0E200BA6873BA16EB36 /* EXInstallationIdProvider.h in Headers */, - E1153B29228819FDEE381E4C4DE7B4ED /* EXNotificationCenterDelegate.h in Headers */, - 8BF8948071BBBCC350ED9AD5A6EA3A3F /* EXNotificationsDelegate.h in Headers */, - D6792D001F48E8D80D1E437A3919D46B /* EXNotificationsEmitter.h in Headers */, - C5D336198E19808B95CBD4B836538377 /* EXNotificationSerializer.h in Headers */, - B589C5C7A1296F78DD4F777C26082AD8 /* EXPushTokenListener.h in Headers */, - 57E385C9DFEEC3DF92CEF1F8BAC1CF19 /* EXPushTokenManager.h in Headers */, - 9E60E265AA0CEE4595002599C024E5C2 /* EXPushTokenModule.h in Headers */, + 4674C63655FD4DEC773EFE5815C5D9AD /* EXInstallationIdProvider.h in Headers */, + A731FE89B3A06321EBB66041F59B272A /* EXNotificationCenterDelegate.h in Headers */, + 285A73EBD92BB65500ED8D35E89D7909 /* EXNotificationsDelegate.h in Headers */, + 60717E866FA2C7D622E48667D3604045 /* EXNotificationsEmitter.h in Headers */, + F8F45D0135DDF5B5DE93E316999A95A9 /* EXNotificationSerializer.h in Headers */, + 1D196422E60ED8DAF37D2DBE4F83EC8A /* EXNotificationsHandlerModule.h in Headers */, + 6C471E802BA2B8970F6DAC01FFCED653 /* EXPushTokenListener.h in Headers */, + 4F82584F6D9012B31C8A574F74956910 /* EXPushTokenManager.h in Headers */, + 93BECF2E5C5366AD8FC8B4F67C50146B /* EXPushTokenModule.h in Headers */, + 95F2AB42789C22726C3201D94EDDED42 /* EXSingleNotificationHandlerTask.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -194,16 +217,16 @@ /* Begin PBXNativeTarget section */ 6C660F81CE4F0EC96D208B20C3E8B154 /* EXNotifications */ = { isa = PBXNativeTarget; - buildConfigurationList = 9ADEEF7B1FD5178A19A06F75BBF20EBF /* Build configuration list for PBXNativeTarget "EXNotifications" */; + buildConfigurationList = A2864D4C607A4AA5BF88451B4926A0F6 /* Build configuration list for PBXNativeTarget "EXNotifications" */; buildPhases = ( - D4C68791AB6AA26F179FA0F69263E939 /* Headers */, - 4382E20A3FBC96BDEBC66E9605D95EB8 /* Sources */, - 2709B01D6E7ACBB9F5BA7591EAABE945 /* Frameworks */, + 50301B2968C1552BD526F38E01D87BB9 /* Headers */, + 1A9DF3CA3D65CB36AE2487C2FB299080 /* Sources */, + 19EB2782D3C477970E5162E2F9F98F23 /* Frameworks */, ); buildRules = ( ); dependencies = ( - B42CB4F7998E768C52EE0E521EB14C72 /* PBXTargetDependency */, + 206103F568683491C3A7190CFAB3FEC5 /* PBXTargetDependency */, ); name = EXNotifications; productName = EXNotifications; @@ -243,27 +266,29 @@ /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ - 4382E20A3FBC96BDEBC66E9605D95EB8 /* Sources */ = { + 1A9DF3CA3D65CB36AE2487C2FB299080 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - B5D6026C75854B4214E7E633FF6C1DC9 /* EXInstallationIdProvider.m in Sources */, - ABF5F729820C4EF02B73CB6C383F5D80 /* EXNotificationCenterDelegate.m in Sources */, - A8A2C0A487684DFE1F4943405D6F815E /* EXNotifications-dummy.m in Sources */, - 2403DA92B2AB424356AC7C0A36F852B2 /* EXNotificationsEmitter.m in Sources */, - 2B759016E5CD3BC497FC000E436B1FE4 /* EXNotificationSerializer.m in Sources */, - F083BF85702BCF8220A30F28CA19CB14 /* EXPushTokenManager.m in Sources */, - A77FFD3D8608E069C8E762C93517F99C /* EXPushTokenModule.m in Sources */, + 908CAD70193FA81AD89417B650AE4E31 /* EXInstallationIdProvider.m in Sources */, + 1BB38E271192F4DC3E0248D136F39E71 /* EXNotificationCenterDelegate.m in Sources */, + 3385B443BE2D816E57F8E11EB836D990 /* EXNotifications-dummy.m in Sources */, + EA085F37FD7E2A69E2714E63AD651A68 /* EXNotificationsEmitter.m in Sources */, + D63110D99F6FC237A16383D01058AF90 /* EXNotificationSerializer.m in Sources */, + 9D4C7A28BF30EE08548BFD0F7A803556 /* EXNotificationsHandlerModule.m in Sources */, + 0A9F02D29FAAF79BFE4EFF5BC350C959 /* EXPushTokenManager.m in Sources */, + 7ED0109EA6513BF855B0D1BC5C8D1681 /* EXPushTokenModule.m in Sources */, + 5A1C0183CCD2EE78C5604C2BF0482320 /* EXSingleNotificationHandlerTask.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - B42CB4F7998E768C52EE0E521EB14C72 /* PBXTargetDependency */ = { + 206103F568683491C3A7190CFAB3FEC5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = UMCore; - targetProxy = 70B999E09C718FCAD7580FF36BC8DEFD /* PBXContainerItemProxy */; + targetProxy = 40D6170A8F43E31F8A3BD3A188179825 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -332,9 +357,9 @@ }; name = Debug; }; - 1AD94136EB52E503A54426B1218F21F6 /* Debug */ = { + 2C23E34FA4FE11CB0FA4DB8090D9F173 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9ECD98D5B9A9844D449C9DBF79BDD71C /* EXNotifications.xcconfig */; + baseConfigurationReference = 7573E122AAE7B6566A3A120ADE722FBE /* EXNotifications.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; CODE_SIGN_IDENTITY = "iPhone Developer"; @@ -353,12 +378,13 @@ SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; }; - name = Debug; + name = Release; }; - 1F8B2A2CB046AFFB81B5B8A929E397AB /* Release */ = { + 80622D7D0B068E66EDB6468DFDA165A3 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9ECD98D5B9A9844D449C9DBF79BDD71C /* EXNotifications.xcconfig */; + baseConfigurationReference = 7573E122AAE7B6566A3A120ADE722FBE /* EXNotifications.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; CODE_SIGN_IDENTITY = "iPhone Developer"; @@ -377,9 +403,8 @@ SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; }; - name = Release; + name = Debug; }; C713B0E6DDE114F062471662491E7F99 /* Release */ = { isa = XCBuildConfiguration; @@ -453,11 +478,11 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 9ADEEF7B1FD5178A19A06F75BBF20EBF /* Build configuration list for PBXNativeTarget "EXNotifications" */ = { + A2864D4C607A4AA5BF88451B4926A0F6 /* Build configuration list for PBXNativeTarget "EXNotifications" */ = { isa = XCConfigurationList; buildConfigurations = ( - 1AD94136EB52E503A54426B1218F21F6 /* Debug */, - 1F8B2A2CB046AFFB81B5B8A929E397AB /* Release */, + 80622D7D0B068E66EDB6468DFDA165A3 /* Debug */, + 2C23E34FA4FE11CB0FA4DB8090D9F173 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/apps/bare-expo/ios/Pods/Headers/Private/EXNotifications/EXNotificationsHandlerModule.h b/apps/bare-expo/ios/Pods/Headers/Private/EXNotifications/EXNotificationsHandlerModule.h new file mode 120000 index 0000000000000..11ad701360e8d --- /dev/null +++ b/apps/bare-expo/ios/Pods/Headers/Private/EXNotifications/EXNotificationsHandlerModule.h @@ -0,0 +1 @@ +../../../../../../../packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXNotificationsHandlerModule.h \ No newline at end of file diff --git a/apps/bare-expo/ios/Pods/Headers/Private/EXNotifications/EXSingleNotificationHandlerTask.h b/apps/bare-expo/ios/Pods/Headers/Private/EXNotifications/EXSingleNotificationHandlerTask.h new file mode 120000 index 0000000000000..7b1a5ae5cc070 --- /dev/null +++ b/apps/bare-expo/ios/Pods/Headers/Private/EXNotifications/EXSingleNotificationHandlerTask.h @@ -0,0 +1 @@ +../../../../../../../packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXSingleNotificationHandlerTask.h \ No newline at end of file diff --git a/apps/bare-expo/ios/Pods/Headers/Public/EXNotifications/EXNotificationsHandlerModule.h b/apps/bare-expo/ios/Pods/Headers/Public/EXNotifications/EXNotificationsHandlerModule.h new file mode 120000 index 0000000000000..11ad701360e8d --- /dev/null +++ b/apps/bare-expo/ios/Pods/Headers/Public/EXNotifications/EXNotificationsHandlerModule.h @@ -0,0 +1 @@ +../../../../../../../packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXNotificationsHandlerModule.h \ No newline at end of file diff --git a/apps/bare-expo/ios/Pods/Headers/Public/EXNotifications/EXSingleNotificationHandlerTask.h b/apps/bare-expo/ios/Pods/Headers/Public/EXNotifications/EXSingleNotificationHandlerTask.h new file mode 120000 index 0000000000000..7b1a5ae5cc070 --- /dev/null +++ b/apps/bare-expo/ios/Pods/Headers/Public/EXNotifications/EXSingleNotificationHandlerTask.h @@ -0,0 +1 @@ +../../../../../../../packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXSingleNotificationHandlerTask.h \ No newline at end of file diff --git a/apps/test-suite/tests/NewNotifications.js b/apps/test-suite/tests/NewNotifications.js index 3b3cfb82415f6..e6a7fc7836eb8 100644 --- a/apps/test-suite/tests/NewNotifications.js +++ b/apps/test-suite/tests/NewNotifications.js @@ -71,6 +71,198 @@ export async function test(t) { t.expect(expoPushToken.type).toBe('expo'); t.expect(typeof expoPushToken.data).toBe('string'); }); + + // Not running those tests on web since Expo push notification doesn't yet support web. + const describeWithExpoPushToken = ['ios', 'android'].includes(Platform.OS) + ? t.describe + : t.xdescribe; + + describeWithExpoPushToken('when a push notification is sent', () => { + let notificationToHandle; + let handleSuccessEvent; + let handleErrorEvent; + + let expoPushToken; + + let handleFuncOverride; + + t.beforeAll(async () => { + let experienceId = undefined; + if (!Constants.manifest) { + // Absence of manifest means we're running out of managed workflow + // in bare-expo. @exponent/bare-expo "experience" has been configured + // to use Apple Push Notification key that will work in bare-expo. + experienceId = '@exponent/bare-expo'; + } + const pushToken = await Notifications.getExpoPushTokenAsync({ + experienceId, + }); + expoPushToken = pushToken.data; + + Notifications.setNotificationHandler({ + handleNotification: async notification => { + notificationToHandle = notification; + if (handleFuncOverride) { + return await handleFuncOverride(notification); + } else { + return { + shouldPlaySound: false, + shouldSetBadge: false, + shouldShowAlert: true, + }; + } + }, + handleSuccess: event => { + handleSuccessEvent = event; + }, + handleError: event => { + handleErrorEvent = event; + }, + }); + }); + + t.beforeEach(async () => { + handleErrorEvent = null; + handleSuccessEvent = null; + notificationToHandle = null; + await sendTestPushNotification(expoPushToken); + }); + + t.afterAll(() => { + Notifications.setNotificationHandler(null); + }); + + t.it('calls the `handleNotification` callback of the notification handler', async () => { + let iterations = 0; + while (iterations < 5) { + iterations += 1; + if (notificationToHandle) { + break; + } + await waitFor(1000); + } + t.expect(notificationToHandle).not.toBeNull(); + }); + + t.describe('if handler responds in time', async () => { + t.beforeAll(() => { + // Overriding handler to return a no-effect behavior + // for Android not to reject the promise with + // "Notification presenting not implemented." + // TODO: Remove override when notification presenting + // is implemented. + handleFuncOverride = async () => { + return { + shouldPlaySound: false, + shouldSetBadge: false, + shouldShowAlert: false, + }; + }; + }); + + t.afterAll(() => { + handleFuncOverride = null; + }); + + t.it( + 'calls `handleSuccess` callback of the notification handler', + async () => { + let iterations = 0; + while (iterations < 5) { + iterations += 1; + if (handleSuccessEvent) { + break; + } + await waitFor(1000); + } + t.expect(handleSuccessEvent).not.toBeNull(); + t.expect(handleErrorEvent).toBeNull(); + }, + 10000 + ); + }); + + t.describe('if handler fails to respond in time', async () => { + t.beforeAll(() => { + handleFuncOverride = async () => { + await waitFor(3000); + return { + shouldPlaySound: false, + shouldSetBadge: false, + shouldShowAlert: true, + }; + }; + }); + + t.afterAll(() => { + handleFuncOverride = null; + }); + + t.it( + 'calls `handleError` callback of the notification handler', + async () => { + let iterations = 0; + while (iterations < 5) { + iterations += 1; + if (handleErrorEvent) { + break; + } + await waitFor(1000); + } + t.expect(handleErrorEvent).not.toBeNull(); + t.expect(handleSuccessEvent).toBeNull(); + }, + 10000 + ); + }); + }); }); }); } + +// In this test app we contact the Expo push service directly. You *never* +// should do this in a real app. You should always store the push tokens on your +// own server or use the local notification API if you want to notify this user. +const PUSH_ENDPOINT = 'https://expo.io/--/api/v2/push/send'; + +async function sendTestPushNotification(expoPushToken, notificationOverrides) { + // POST the token to the Expo push server + const response = await fetch(PUSH_ENDPOINT, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify([ + { + to: expoPushToken, + title: 'Hello from Expo server!', + ...notificationOverrides, + }, + ]), + }); + + const result = await response.json(); + if (result.errors) { + for (const error of result.errors) { + console.warn(`API error sending push notification:`, error); + } + throw new Error('API error has occurred.'); + } + + const receipts = result.data; + if (receipts) { + const receipt = receipts[0]; + if (receipt.status === 'error') { + if (receipt.details) { + console.warn( + `Expo push service reported an error sending a notification: ${receipt.details.error}` + ); + } + if (receipt.__debug) { + console.warn(receipt.__debug); + } + throw new Error(`API error has occurred: ${receipt.details.error}`); + } + } +} diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/NotificationsPackage.java b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/NotificationsPackage.java index 0912b210eb38c..d72220c875aa8 100644 --- a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/NotificationsPackage.java +++ b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/NotificationsPackage.java @@ -10,9 +10,10 @@ import java.util.List; import expo.modules.notifications.installationid.InstallationIdProvider; -import expo.modules.notifications.notifications.channels.ExpoNotificationChannelsManager; import expo.modules.notifications.notifications.NotificationManager; +import expo.modules.notifications.notifications.channels.ExpoNotificationChannelsManager; import expo.modules.notifications.notifications.emitting.NotificationsEmitter; +import expo.modules.notifications.notifications.handling.NotificationsHandler; import expo.modules.notifications.tokens.PushTokenManager; import expo.modules.notifications.tokens.PushTokenModule; @@ -22,6 +23,7 @@ public List createExportedModules(Context context) { return Arrays.asList( new PushTokenModule(context), new NotificationsEmitter(context), + new NotificationsHandler(context), new InstallationIdProvider(context) ); } diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/handling/NotificationsHandler.java b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/handling/NotificationsHandler.java new file mode 100644 index 0000000000000..901d81caa239e --- /dev/null +++ b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/handling/NotificationsHandler.java @@ -0,0 +1,152 @@ +package expo.modules.notifications.notifications.handling; + +import android.content.Context; + +import com.google.firebase.messaging.RemoteMessage; + +import org.unimodules.core.ExportedModule; +import org.unimodules.core.ModuleRegistry; +import org.unimodules.core.Promise; +import org.unimodules.core.arguments.ReadableArguments; +import org.unimodules.core.interfaces.ExpoMethod; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import expo.modules.notifications.notifications.emitting.NotificationsEmitter; +import expo.modules.notifications.notifications.interfaces.NotificationBehavior; +import expo.modules.notifications.notifications.interfaces.NotificationListener; +import expo.modules.notifications.notifications.interfaces.NotificationManager; + +/** + * {@link NotificationListener} responsible for managing app's reaction to incoming + * notification. + *

+ * It is responsible for managing lifecycles of {@link SingleNotificationHandlerTask}s + * which are responsible: one for each notification. This module serves as holder + * for all of them and a proxy through which app responds with the behavior. + */ +public class NotificationsHandler extends ExportedModule implements NotificationListener { + private final static String EXPORTED_NAME = "ExpoNotificationsHandlerModule"; + + private NotificationManager mNotificationManager; + private ModuleRegistry mModuleRegistry; + + private Map mTasksMap = new HashMap<>(); + + public NotificationsHandler(Context context) { + super(context); + } + + @Override + public String getName() { + return EXPORTED_NAME; + } + + @Override + public void onCreate(ModuleRegistry moduleRegistry) { + mModuleRegistry = moduleRegistry; + + // Register the module as a listener in NotificationManager singleton module. + // Deregistration happens in onDestroy callback. + mNotificationManager = moduleRegistry.getSingletonModule("NotificationManager", NotificationManager.class); + mNotificationManager.addListener(this); + } + + @Override + public void onDestroy() { + mNotificationManager.removeListener(this); + Collection tasks = mTasksMap.values(); + for (SingleNotificationHandlerTask task : tasks) { + task.stop(); + } + } + + /** + * Called by the app with {@link ReadableArguments} representing requested behavior + * that should be applied to the notification. + * + * @param identifier Identifier of the task which asked for behavior. + * @param behavior Behavior to apply to the notification. + * @param promise Promise to resolve once the notification is successfully presented + * or fails to be presented. + */ + @ExpoMethod + public void handleNotificationAsync(String identifier, final ReadableArguments behavior, Promise promise) { + SingleNotificationHandlerTask task = mTasksMap.get(identifier); + if (task == null) { + String message = String.format("Failed to handle notification %s, it has already been handled.", identifier); + promise.reject("ERR_NOTIFICATION_HANDLED", message); + return; + } + task.handleResponse(new ArgumentsNotificationBehavior(behavior), promise); + } + + /** + * Callback called by {@link NotificationManager} to inform its listeners of new messages. + * Starts up a new {@link SingleNotificationHandlerTask} which will take it on from here. + * + * @param message Received message + */ + @Override + public void onMessage(RemoteMessage message) { + SingleNotificationHandlerTask task = new SingleNotificationHandlerTask(mModuleRegistry, message, this); + mTasksMap.put(task.getIdentifier(), task); + task.start(); + } + + /** + * Callback called by {@link NotificationManager} to inform that some push notifications + * haven't been delivered to the app. It doesn't make sense to react to this event in this class. + * Apps get notified of this event by {@link NotificationsEmitter}. + */ + @Override + public void onDeletedMessages() { + // do nothing + } + + /** + * Callback called once {@link SingleNotificationHandlerTask} finishes. + * A cue for removal of the task. + * + * @param task Task that just fulfilled its responsibility. + */ + void onTaskFinished(SingleNotificationHandlerTask task) { + mTasksMap.remove(task.getIdentifier()); + } + + /** + * An implementation of {@link NotificationBehavior} capable of + * "deserialization" of behavior objects with which the app responds. + *

+ * Used in {@link #handleNotificationAsync(String, ReadableArguments, Promise)} + * to pass the behavior to {@link SingleNotificationHandlerTask}. + */ + class ArgumentsNotificationBehavior extends NotificationBehavior { + private static final String SHOULD_SHOW_ALERT_KEY = "shouldShowAlert"; + private static final String SHOULD_PLAY_SOUND_KEY = "shouldPlaySound"; + private static final String SHOULD_SET_BADGE_KEY = "shouldSetBadge"; + + private ReadableArguments mArguments; + + ArgumentsNotificationBehavior(ReadableArguments arguments) { + mArguments = arguments; + } + + @Override + public boolean shouldShowAlert() { + return mArguments.getBoolean(SHOULD_SHOW_ALERT_KEY); + } + + @Override + public boolean shouldPlaySound() { + return mArguments.getBoolean(SHOULD_PLAY_SOUND_KEY); + } + + @Override + public boolean shouldSetBadge() { + return mArguments.getBoolean(SHOULD_SET_BADGE_KEY); + } + } +} diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/handling/SingleNotificationHandlerTask.java b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/handling/SingleNotificationHandlerTask.java new file mode 100644 index 0000000000000..cdd760aee25cc --- /dev/null +++ b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/handling/SingleNotificationHandlerTask.java @@ -0,0 +1,141 @@ +package expo.modules.notifications.notifications.handling; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import com.google.firebase.messaging.RemoteMessage; + +import org.unimodules.core.ModuleRegistry; +import org.unimodules.core.Promise; +import org.unimodules.core.interfaces.services.EventEmitter; + +import java.util.UUID; + +import expo.modules.notifications.notifications.RemoteMessageSerializer; +import expo.modules.notifications.notifications.interfaces.NotificationBehavior; + +/** + * A "task" responsible for managing response to a single notification. + */ +/* package */ class SingleNotificationHandlerTask { + /** + * {@link Handler} on which lifecycle events are executed. + */ + private final static Handler HANDLER = new Handler(Looper.getMainLooper()); + + /** + * Name of the event asking the delegate for behavior. + */ + private final static String HANDLE_NOTIFICATION_EVENT_NAME = "onHandleNotification"; + /** + * Name of the event emitted if the delegate doesn't respond in time. + */ + private final static String HANDLE_NOTIFICATION_TIMEOUT_EVENT_NAME = "onHandleNotificationTimeout"; + + /** + * Seconds since sending the {@link #HANDLE_NOTIFICATION_EVENT_NAME} until the task + * is considered timed out. + */ + private final static int SECONDS_TO_TIMEOUT = 3; + + private EventEmitter mEventEmitter; + private RemoteMessage mRemoteMessage; + private NotificationBehavior mBehavior; + private NotificationsHandler mDelegate; + private String mIdentifier; + + private Runnable mTimeoutRunnable = new Runnable() { + @Override + public void run() { + SingleNotificationHandlerTask.this.handleTimeout(); + } + }; + + /* package */ SingleNotificationHandlerTask(ModuleRegistry moduleRegistry, RemoteMessage remoteMessage, NotificationsHandler delegate) { + mEventEmitter = moduleRegistry.getModule(EventEmitter.class); + mRemoteMessage = remoteMessage; + mDelegate = delegate; + + mIdentifier = remoteMessage.getMessageId(); + if (mIdentifier == null) { + mIdentifier = UUID.randomUUID().toString(); + } + } + + /** + * @return Identifier of the task ({@link RemoteMessage#getMessageId()} or a random {@link UUID} + * if {@link RemoteMessage#getMessageId() is null. + */ + /* package */ String getIdentifier() { + return mIdentifier; + } + + /** + * Starts the task, i.e. sends an event to the app's delegate and starts a timeout + * after which the task finishes itself. + */ + /* package */ void start() { + Bundle eventBody = new Bundle(); + eventBody.putString("id", getIdentifier()); + eventBody.putBundle("notification", RemoteMessageSerializer.toBundle(mRemoteMessage)); + mEventEmitter.emit(HANDLE_NOTIFICATION_EVENT_NAME, eventBody); + + HANDLER.postDelayed(mTimeoutRunnable, SECONDS_TO_TIMEOUT * 1000); + } + + /** + * Stops the task abruptly (in case the app is being destroyed and there is no reason + * to wait for the response anymore). + */ + /* package */ void stop() { + finish(); + } + + /** + * Informs the task of a response - behavior requested by the app. + * + * @param behavior Behavior requested by the app + * @param promise Promise to fulfill once the behavior is applied to the notification. + */ + /* package */ void handleResponse(final NotificationBehavior behavior, final Promise promise) { + mBehavior = behavior; + HANDLER.post(new Runnable() { + @Override + public void run() { + // here we would show the notification + Log.d("NotificationHandlerTask", String.format("Showing notification %s with params: %s", getIdentifier(), mBehavior)); + if (behavior.hasAnyEffect()) { + promise.reject("ERR_NOTIFICATION_PRESENTATION_IMPL", "Notification presenting not implemented."); + } else { + promise.resolve(null); + } + finish(); + } + }); + } + + /** + * Callback called by {@link #mTimeoutRunnable} after timeout time elapses. + *

+ * Sends a timeout event to the app. + */ + private void handleTimeout() { + Bundle eventBody = new Bundle(); + eventBody.putString("id", getIdentifier()); + eventBody.putBundle("notification", RemoteMessageSerializer.toBundle(mRemoteMessage)); + mEventEmitter.emit(HANDLE_NOTIFICATION_TIMEOUT_EVENT_NAME, eventBody); + + finish(); + } + + /** + * Callback called when the task fulfills its responsibility. Clears up {@link #HANDLER} + * and informs {@link #mDelegate} of the task's state. + */ + private void finish() { + HANDLER.removeCallbacks(mTimeoutRunnable); + mDelegate.onTaskFinished(this); + } +} diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationBehavior.java b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationBehavior.java new file mode 100644 index 0000000000000..1a2417ddc8ee2 --- /dev/null +++ b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationBehavior.java @@ -0,0 +1,29 @@ +package expo.modules.notifications.notifications.interfaces; + +/** + * Class representing behavior which should be applied + * to a notification. + */ +public abstract class NotificationBehavior { + /** + * @return Whether to show a heads-up alert. + */ + public abstract boolean shouldShowAlert(); + + /** + * @return Whether the notification should be accompanied by a sound. + */ + public abstract boolean shouldPlaySound(); + + /** + * @return Whether badge count that may be contained in the notification should be applied. + */ + public abstract boolean shouldSetBadge(); + + /** + * @return Whether the notification may have any user-facing effect. + */ + public boolean hasAnyEffect() { + return shouldShowAlert() || shouldPlaySound() || shouldSetBadge(); + } +} diff --git a/packages/expo-notifications/build/NotificationsHandler.d.ts b/packages/expo-notifications/build/NotificationsHandler.d.ts new file mode 100644 index 0000000000000..a32ccc4959ce3 --- /dev/null +++ b/packages/expo-notifications/build/NotificationsHandler.d.ts @@ -0,0 +1,17 @@ +import { CodedError } from '@unimodules/core'; +import { NotificationBehavior } from './NotificationsHandlerModule'; +import { Notification } from './NotificationsEmitter.types'; +export declare class NotificationTimeoutError extends CodedError { + info: { + notification: Notification; + id: string; + }; + constructor(notificationId: string, notification: Notification); +} +export declare type NotificationHandlingError = NotificationTimeoutError | Error; +export interface NotificationHandler { + handleNotification: (notification: Notification) => Promise; + handleSuccess?: (notificationId: string) => void; + handleError?: (error: NotificationHandlingError) => void; +} +export declare function setNotificationHandler(handler: NotificationHandler | null): void; diff --git a/packages/expo-notifications/build/NotificationsHandler.js b/packages/expo-notifications/build/NotificationsHandler.js new file mode 100644 index 0000000000000..fad7e89cb87ac --- /dev/null +++ b/packages/expo-notifications/build/NotificationsHandler.js @@ -0,0 +1,42 @@ +import { EventEmitter, CodedError } from '@unimodules/core'; +import NotificationsHandlerModule from './NotificationsHandlerModule'; +export class NotificationTimeoutError extends CodedError { + constructor(notificationId, notification) { + super('ERR_NOTIFICATION_TIMEOUT', `Notification handling timed out for ID ${notificationId}.`); + this.info = { id: notificationId, notification }; + } +} +// Web uses SyntheticEventEmitter +const notificationEmitter = new EventEmitter(NotificationsHandlerModule); +const handleNotificationEventName = 'onHandleNotification'; +const handleNotificationTimeoutEventName = 'onHandleNotificationTimeout'; +let handleSubscription = null; +let handleTimeoutSubscription = null; +export function setNotificationHandler(handler) { + if (handleSubscription) { + handleSubscription.remove(); + handleSubscription = null; + } + if (handleTimeoutSubscription) { + handleTimeoutSubscription.remove(); + handleTimeoutSubscription = null; + } + if (handler) { + handleSubscription = notificationEmitter.addListener(handleNotificationEventName, async ({ id, notification }) => { + try { + const requestedBehavior = await handler.handleNotification(notification); + await NotificationsHandlerModule.handleNotificationAsync(id, requestedBehavior); + // TODO: Remove eslint-disable once we upgrade to a version that supports ?. notation. + // eslint-disable-next-line + handler.handleSuccess?.(id); + } + catch (error) { + // TODO: Remove eslint-disable once we upgrade to a version that supports ?. notation. + // eslint-disable-next-line + handler.handleError?.(error); + } + }); + handleTimeoutSubscription = notificationEmitter.addListener(handleNotificationTimeoutEventName, ({ id, notification }) => handler.handleError?.(new NotificationTimeoutError(id, notification))); + } +} +//# sourceMappingURL=NotificationsHandler.js.map \ No newline at end of file diff --git a/packages/expo-notifications/build/NotificationsHandler.js.map b/packages/expo-notifications/build/NotificationsHandler.js.map new file mode 100644 index 0000000000000..66b0e480abed2 --- /dev/null +++ b/packages/expo-notifications/build/NotificationsHandler.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NotificationsHandler.js","sourceRoot":"","sources":["../src/NotificationsHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAgB,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,0BAAoD,MAAM,8BAA8B,CAAC;AAGhG,MAAM,OAAO,wBAAyB,SAAQ,UAAU;IAEtD,YAAY,cAAsB,EAAE,YAA0B;QAC5D,KAAK,CAAC,0BAA0B,EAAE,0CAA0C,cAAc,GAAG,CAAC,CAAC;QAC/F,IAAI,CAAC,IAAI,GAAG,EAAE,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC;IACnD,CAAC;CACF;AAiBD,iCAAiC;AACjC,MAAM,mBAAmB,GAAG,IAAI,YAAY,CAAC,0BAA0B,CAAC,CAAC;AAEzE,MAAM,2BAA2B,GAAG,sBAAsB,CAAC;AAC3D,MAAM,kCAAkC,GAAG,6BAA6B,CAAC;AAEzE,IAAI,kBAAkB,GAAwB,IAAI,CAAC;AACnD,IAAI,yBAAyB,GAAwB,IAAI,CAAC;AAE1D,MAAM,UAAU,sBAAsB,CAAC,OAAmC;IACxE,IAAI,kBAAkB,EAAE;QACtB,kBAAkB,CAAC,MAAM,EAAE,CAAC;QAC5B,kBAAkB,GAAG,IAAI,CAAC;KAC3B;IACD,IAAI,yBAAyB,EAAE;QAC7B,yBAAyB,CAAC,MAAM,EAAE,CAAC;QACnC,yBAAyB,GAAG,IAAI,CAAC;KAClC;IAED,IAAI,OAAO,EAAE;QACX,kBAAkB,GAAG,mBAAmB,CAAC,WAAW,CAClD,2BAA2B,EAC3B,KAAK,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE;YAC7B,IAAI;gBACF,MAAM,iBAAiB,GAAG,MAAM,OAAO,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC;gBACzE,MAAM,0BAA0B,CAAC,uBAAuB,CAAC,EAAE,EAAE,iBAAiB,CAAC,CAAC;gBAChF,sFAAsF;gBACtF,2BAA2B;gBAC3B,OAAO,CAAC,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC;aAC7B;YAAC,OAAO,KAAK,EAAE;gBACd,sFAAsF;gBACtF,2BAA2B;gBAC3B,OAAO,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC;aAC9B;QACH,CAAC,CACF,CAAC;QAEF,yBAAyB,GAAG,mBAAmB,CAAC,WAAW,CACzD,kCAAkC,EAClC,CAAC,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,CACvB,OAAO,CAAC,WAAW,EAAE,CAAC,IAAI,wBAAwB,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC,CACxE,CAAC;KACH;AACH,CAAC","sourcesContent":["import { EventEmitter, Subscription, CodedError } from '@unimodules/core';\nimport NotificationsHandlerModule, { NotificationBehavior } from './NotificationsHandlerModule';\nimport { Notification } from './NotificationsEmitter.types';\n\nexport class NotificationTimeoutError extends CodedError {\n info: { notification: Notification; id: string };\n constructor(notificationId: string, notification: Notification) {\n super('ERR_NOTIFICATION_TIMEOUT', `Notification handling timed out for ID ${notificationId}.`);\n this.info = { id: notificationId, notification };\n }\n}\n\nexport type NotificationHandlingError = NotificationTimeoutError | Error;\n\nexport interface NotificationHandler {\n handleNotification: (notification: Notification) => Promise;\n handleSuccess?: (notificationId: string) => void;\n handleError?: (error: NotificationHandlingError) => void;\n}\n\ntype HandleNotificationEvent = {\n id: string;\n notification: Notification;\n};\n\ntype HandleNotificationTimeoutEvent = HandleNotificationEvent;\n\n// Web uses SyntheticEventEmitter\nconst notificationEmitter = new EventEmitter(NotificationsHandlerModule);\n\nconst handleNotificationEventName = 'onHandleNotification';\nconst handleNotificationTimeoutEventName = 'onHandleNotificationTimeout';\n\nlet handleSubscription: Subscription | null = null;\nlet handleTimeoutSubscription: Subscription | null = null;\n\nexport function setNotificationHandler(handler: NotificationHandler | null): void {\n if (handleSubscription) {\n handleSubscription.remove();\n handleSubscription = null;\n }\n if (handleTimeoutSubscription) {\n handleTimeoutSubscription.remove();\n handleTimeoutSubscription = null;\n }\n\n if (handler) {\n handleSubscription = notificationEmitter.addListener(\n handleNotificationEventName,\n async ({ id, notification }) => {\n try {\n const requestedBehavior = await handler.handleNotification(notification);\n await NotificationsHandlerModule.handleNotificationAsync(id, requestedBehavior);\n // TODO: Remove eslint-disable once we upgrade to a version that supports ?. notation.\n // eslint-disable-next-line\n handler.handleSuccess?.(id);\n } catch (error) {\n // TODO: Remove eslint-disable once we upgrade to a version that supports ?. notation.\n // eslint-disable-next-line\n handler.handleError?.(error);\n }\n }\n );\n\n handleTimeoutSubscription = notificationEmitter.addListener(\n handleNotificationTimeoutEventName,\n ({ id, notification }) =>\n handler.handleError?.(new NotificationTimeoutError(id, notification))\n );\n }\n}\n"]} \ No newline at end of file diff --git a/packages/expo-notifications/build/NotificationsHandlerModule.d.ts b/packages/expo-notifications/build/NotificationsHandlerModule.d.ts new file mode 100644 index 0000000000000..31d4c92f89dfb --- /dev/null +++ b/packages/expo-notifications/build/NotificationsHandlerModule.d.ts @@ -0,0 +1,11 @@ +import { ProxyNativeModule } from '@unimodules/core'; +export interface NotificationBehavior { + shouldShowAlert: boolean; + shouldPlaySound: boolean; + shouldSetBadge: boolean; +} +export interface NotificationsHandlerModule extends ProxyNativeModule { + handleNotificationAsync: (notificationId: string, notificationBehavior: NotificationBehavior) => Promise; +} +declare const _default: NotificationsHandlerModule; +export default _default; diff --git a/packages/expo-notifications/build/NotificationsHandlerModule.js b/packages/expo-notifications/build/NotificationsHandlerModule.js new file mode 100644 index 0000000000000..c109faec4b025 --- /dev/null +++ b/packages/expo-notifications/build/NotificationsHandlerModule.js @@ -0,0 +1,3 @@ +import { NativeModulesProxy } from '@unimodules/core'; +export default NativeModulesProxy.ExpoNotificationsHandlerModule; +//# sourceMappingURL=NotificationsHandlerModule.js.map \ No newline at end of file diff --git a/packages/expo-notifications/build/NotificationsHandlerModule.js.map b/packages/expo-notifications/build/NotificationsHandlerModule.js.map new file mode 100644 index 0000000000000..4966c24acbcf7 --- /dev/null +++ b/packages/expo-notifications/build/NotificationsHandlerModule.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NotificationsHandlerModule.js","sourceRoot":"","sources":["../src/NotificationsHandlerModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAqB,MAAM,kBAAkB,CAAC;AAezE,eAAgB,kBAAkB,CAAC,8BAAoE,CAAC","sourcesContent":["import { NativeModulesProxy, ProxyNativeModule } from '@unimodules/core';\n\nexport interface NotificationBehavior {\n shouldShowAlert: boolean;\n shouldPlaySound: boolean;\n shouldSetBadge: boolean;\n}\n\nexport interface NotificationsHandlerModule extends ProxyNativeModule {\n handleNotificationAsync: (\n notificationId: string,\n notificationBehavior: NotificationBehavior\n ) => Promise;\n}\n\nexport default (NativeModulesProxy.ExpoNotificationsHandlerModule as any) as NotificationsHandlerModule;\n"]} \ No newline at end of file diff --git a/packages/expo-notifications/build/index.d.ts b/packages/expo-notifications/build/index.d.ts index 821fda06625ec..6e0c18fad4bf4 100644 --- a/packages/expo-notifications/build/index.d.ts +++ b/packages/expo-notifications/build/index.d.ts @@ -2,3 +2,4 @@ export { default as getDevicePushTokenAsync } from './getDevicePushTokenAsync'; export { default as getExpoPushTokenAsync } from './getExpoPushTokenAsync'; export * from './TokenEmitter'; export * from './NotificationsEmitter'; +export * from './NotificationsHandler'; diff --git a/packages/expo-notifications/build/index.js b/packages/expo-notifications/build/index.js index eaf37a505bba5..103bcc9c1c977 100644 --- a/packages/expo-notifications/build/index.js +++ b/packages/expo-notifications/build/index.js @@ -2,4 +2,5 @@ export { default as getDevicePushTokenAsync } from './getDevicePushTokenAsync'; export { default as getExpoPushTokenAsync } from './getExpoPushTokenAsync'; export * from './TokenEmitter'; export * from './NotificationsEmitter'; +export * from './NotificationsHandler'; //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/expo-notifications/build/index.js.map b/packages/expo-notifications/build/index.js.map index 5365b88c45edb..60f0fc3b083d7 100644 --- a/packages/expo-notifications/build/index.js.map +++ b/packages/expo-notifications/build/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC","sourcesContent":["export { default as getDevicePushTokenAsync } from './getDevicePushTokenAsync';\nexport { default as getExpoPushTokenAsync } from './getExpoPushTokenAsync';\nexport * from './TokenEmitter';\nexport * from './NotificationsEmitter';\n"]} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC","sourcesContent":["export { default as getDevicePushTokenAsync } from './getDevicePushTokenAsync';\nexport { default as getExpoPushTokenAsync } from './getExpoPushTokenAsync';\nexport * from './TokenEmitter';\nexport * from './NotificationsEmitter';\nexport * from './NotificationsHandler';\n"]} \ No newline at end of file diff --git a/packages/expo-notifications/ios/EXNotifications/Notifications/Emitter/EXNotificationsEmitter.m b/packages/expo-notifications/ios/EXNotifications/Notifications/Emitter/EXNotificationsEmitter.m index ad3d67ee69bd2..49b2cddb95682 100644 --- a/packages/expo-notifications/ios/EXNotifications/Notifications/Emitter/EXNotificationsEmitter.m +++ b/packages/expo-notifications/ios/EXNotifications/Notifications/Emitter/EXNotificationsEmitter.m @@ -80,7 +80,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNoti - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { [_eventEmitter sendEventWithName:onDidReceiveNotification body:[EXNotificationSerializer serializedNotification:notification]]; - completionHandler(UNNotificationPresentationOptionAlert); + completionHandler(UNNotificationPresentationOptionNone); } @end diff --git a/packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXNotificationsHandlerModule.h b/packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXNotificationsHandlerModule.h new file mode 100644 index 0000000000000..07b42fa3cd6ed --- /dev/null +++ b/packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXNotificationsHandlerModule.h @@ -0,0 +1,12 @@ +// Copyright 2018-present 650 Industries. All rights reserved. + +#import +#import +#import + +#import +#import + +@interface EXNotificationsHandlerModule : UMExportedModule + +@end diff --git a/packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXNotificationsHandlerModule.m b/packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXNotificationsHandlerModule.m new file mode 100644 index 0000000000000..81927d99b18cd --- /dev/null +++ b/packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXNotificationsHandlerModule.m @@ -0,0 +1,109 @@ +// Copyright 2018-present 650 Industries. All rights reserved. + +#import +#import +#import + +#import + +@interface EXNotificationsHandlerModule () + +@property (nonatomic, weak) id notificationCenterDelegate; + +@property (nonatomic, assign) BOOL isListening; +@property (nonatomic, assign) BOOL isBeingObserved; + +@property (nonatomic, weak) id eventEmitter; + +@property (nonatomic, strong) NSMutableDictionary *tasksMap; + +@end + +@implementation EXNotificationsHandlerModule + +UM_EXPORT_MODULE(ExpoNotificationsHandlerModule); + +- (instancetype)init +{ + if (self = [super init]) { + _tasksMap = [NSMutableDictionary dictionary]; + } + return self; +} + +# pragma mark - Exported methods + +UM_EXPORT_METHOD_AS(handleNotificationAsync, + handleNotificationAsync:(NSString *)identifier withBehavior:(NSDictionary *)behavior resolver:(UMPromiseResolveBlock)resolve rejecter:(UMPromiseRejectBlock)reject) +{ + EXSingleNotificationHandlerTask *task = _tasksMap[identifier]; + if (!task) { + NSString *message = [NSString stringWithFormat:@"Failed to handle notification %@, it has already been handled.", identifier]; + return reject(@"ERR_NOTIFICATION_HANDLED", message, nil); + } + NSError *error = [task handleResponse:behavior]; + if (error) { + return reject(error.userInfo[@"code"], error.userInfo[@"message"], error); + } else { + resolve(nil); + } +} + +# pragma mark - UMModuleRegistryConsumer + +- (void)setModuleRegistry:(UMModuleRegistry *)moduleRegistry +{ + _eventEmitter = [moduleRegistry getModuleImplementingProtocol:@protocol(UMEventEmitterService)]; + _notificationCenterDelegate = [moduleRegistry getSingletonModuleForName:@"NotificationCenterDelegate"]; +} + +# pragma mark - UMEventEmitter + +- (NSArray *)supportedEvents +{ + return [EXSingleNotificationHandlerTask eventNames]; +} + +- (void)startObserving +{ + [self setIsBeingObserved:YES]; +} + +- (void)stopObserving +{ + [self setIsBeingObserved:NO]; +} + +- (void)setIsBeingObserved:(BOOL)isBeingObserved +{ + _isBeingObserved = isBeingObserved; + BOOL shouldListen = _isBeingObserved; + if (shouldListen && !_isListening) { + [_notificationCenterDelegate addDelegate:self]; + _isListening = YES; + } else if (!shouldListen && _isListening) { + [_notificationCenterDelegate removeDelegate:self]; + _isListening = NO; + } +} + +# pragma mark - EXNotificationsDelegate + +- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler +{ + EXSingleNotificationHandlerTask *task = [[EXSingleNotificationHandlerTask alloc] initWithEventEmitter:_eventEmitter + notification:notification + completionHandler:completionHandler + delegate:self]; + [_tasksMap setObject:task forKey:task.identifier]; + [task start]; +} + +# pragma mark - EXSingleNotificationHandlerTaskDelegate + +- (void)taskDidFinish:(EXSingleNotificationHandlerTask *)task +{ + [_tasksMap removeObjectForKey:task.identifier]; +} + +@end diff --git a/packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXSingleNotificationHandlerTask.h b/packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXSingleNotificationHandlerTask.h new file mode 100644 index 0000000000000..6a3d95d28bf7c --- /dev/null +++ b/packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXSingleNotificationHandlerTask.h @@ -0,0 +1,33 @@ +// Copyright 2018-present 650 Industries. All rights reserved. + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class EXSingleNotificationHandlerTask; + +@protocol EXSingleNotificationHandlerTaskDelegate + +- (void)taskDidFinish:(EXSingleNotificationHandlerTask *)task; + +@end + +@interface EXSingleNotificationHandlerTask : NSObject + ++ (NSArray *)eventNames; + +- (instancetype)initWithEventEmitter:(id)eventEmitter + notification:(UNNotification *)notification + completionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler + delegate:(id)delegate; + +- (NSString *)identifier; + +- (void)start; +- (nullable NSError *)handleResponse:(NSDictionary *)response; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXSingleNotificationHandlerTask.m b/packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXSingleNotificationHandlerTask.m new file mode 100644 index 0000000000000..53ac5ed11a816 --- /dev/null +++ b/packages/expo-notifications/ios/EXNotifications/Notifications/Handler/EXSingleNotificationHandlerTask.m @@ -0,0 +1,123 @@ +// Copyright 2018-present 650 Industries. All rights reserved. + +#import +#import + +static NSString * const onHandleNotification = @"onHandleNotification"; +static NSString * const onHandleNotificationTimeout = @"onHandleNotificationTimeout"; + +static NSString * const shouldShowAlertKey = @"shouldShowAlert"; +static NSString * const shouldPlaySoundKey = @"shouldPlaySound"; +static NSString * const shouldSetBadgeKey = @"shouldSetBadge"; + +static NSTimeInterval const secondsToTimeout = 3; + +static NSString * const EXNotificationHandlerErrorDomain = @"expo.notifications.handler"; + +@interface EXSingleNotificationHandlerTask () + +@property (nonatomic, weak) id eventEmitter; +@property (nonatomic, strong) UNNotification *notification; +@property (nonatomic, copy) void (^completionHandler)(UNNotificationPresentationOptions); + +@property (nonatomic, weak) id delegate; + +@property (nonatomic, strong) NSTimer *timer; + +@end + +@implementation EXSingleNotificationHandlerTask + ++ (NSArray *)eventNames +{ + return @[onHandleNotification, onHandleNotificationTimeout]; +} + +- (instancetype)initWithEventEmitter:(id)eventEmitter + notification:(UNNotification *)notification + completionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler + delegate:(nonnull id)delegate +{ + if (self = [super init]) { + _eventEmitter = eventEmitter; + _notification = notification; + _completionHandler = completionHandler; + _delegate = delegate; + } + return self; +} + +- (NSString *)identifier +{ + return _notification.request.identifier; +} + +- (void)start +{ + [_eventEmitter sendEventWithName:onHandleNotification body:@{ + @"id": _notification.request.identifier, + @"notification": [EXNotificationSerializer serializedNotification:_notification] + }]; + _timer = [NSTimer scheduledTimerWithTimeInterval:secondsToTimeout target:self selector:@selector(handleTimeout) userInfo:nil repeats:NO]; +} + +- (nullable NSError *)handleResponse:(NSDictionary *)response +{ + @synchronized (self) { + NSError *maybeError = [self callCompletionHandlerWithOptions:[self presentationOptionsFromResponse:response]]; + [self finish]; + return maybeError; + } +} + +- (void)handleTimeout +{ + @synchronized (self) { + [_eventEmitter sendEventWithName:onHandleNotificationTimeout body:@{ + @"id": _notification.request.identifier, + @"notification": [EXNotificationSerializer serializedNotification:_notification] + }]; + [self callCompletionHandlerWithOptions:UNNotificationPresentationOptionNone]; + [self finish]; + } +} + +- (nullable NSError *)callCompletionHandlerWithOptions:(UNNotificationPresentationOptions)options +{ + if (_completionHandler) { + _completionHandler(options); + _completionHandler = nil; + return nil; + } else { + return [NSError errorWithDomain:EXNotificationHandlerErrorDomain code:-1 userInfo:@{ + @"code": @"ERR_NOTIFICATION_RESPONSE_TIMEOUT", + @"message": @"Notification has already been handled. Most probably the request has timed out." + }]; + } +} + +- (void)finish +{ + [_timer invalidate]; + _timer = nil; + [_delegate taskDidFinish:self]; +} + +- (UNNotificationPresentationOptions)presentationOptionsFromResponse:(NSDictionary *)response +{ + UNNotificationPresentationOptions options = UNNotificationPresentationOptionNone; + + if ([response[shouldShowAlertKey] boolValue]) { + options |= UNNotificationPresentationOptionAlert; + } + if ([response[shouldPlaySoundKey] boolValue]) { + options |= UNNotificationPresentationOptionSound; + } + if ([response[shouldSetBadgeKey] boolValue]) { + options |= UNNotificationPresentationOptionBadge; + } + + return options; +} + +@end diff --git a/packages/expo-notifications/src/NotificationsHandler.ts b/packages/expo-notifications/src/NotificationsHandler.ts new file mode 100644 index 0000000000000..eec6f812f81bb --- /dev/null +++ b/packages/expo-notifications/src/NotificationsHandler.ts @@ -0,0 +1,71 @@ +import { EventEmitter, Subscription, CodedError } from '@unimodules/core'; +import NotificationsHandlerModule, { NotificationBehavior } from './NotificationsHandlerModule'; +import { Notification } from './NotificationsEmitter.types'; + +export class NotificationTimeoutError extends CodedError { + info: { notification: Notification; id: string }; + constructor(notificationId: string, notification: Notification) { + super('ERR_NOTIFICATION_TIMEOUT', `Notification handling timed out for ID ${notificationId}.`); + this.info = { id: notificationId, notification }; + } +} + +export type NotificationHandlingError = NotificationTimeoutError | Error; + +export interface NotificationHandler { + handleNotification: (notification: Notification) => Promise; + handleSuccess?: (notificationId: string) => void; + handleError?: (error: NotificationHandlingError) => void; +} + +type HandleNotificationEvent = { + id: string; + notification: Notification; +}; + +type HandleNotificationTimeoutEvent = HandleNotificationEvent; + +// Web uses SyntheticEventEmitter +const notificationEmitter = new EventEmitter(NotificationsHandlerModule); + +const handleNotificationEventName = 'onHandleNotification'; +const handleNotificationTimeoutEventName = 'onHandleNotificationTimeout'; + +let handleSubscription: Subscription | null = null; +let handleTimeoutSubscription: Subscription | null = null; + +export function setNotificationHandler(handler: NotificationHandler | null): void { + if (handleSubscription) { + handleSubscription.remove(); + handleSubscription = null; + } + if (handleTimeoutSubscription) { + handleTimeoutSubscription.remove(); + handleTimeoutSubscription = null; + } + + if (handler) { + handleSubscription = notificationEmitter.addListener( + handleNotificationEventName, + async ({ id, notification }) => { + try { + const requestedBehavior = await handler.handleNotification(notification); + await NotificationsHandlerModule.handleNotificationAsync(id, requestedBehavior); + // TODO: Remove eslint-disable once we upgrade to a version that supports ?. notation. + // eslint-disable-next-line + handler.handleSuccess?.(id); + } catch (error) { + // TODO: Remove eslint-disable once we upgrade to a version that supports ?. notation. + // eslint-disable-next-line + handler.handleError?.(error); + } + } + ); + + handleTimeoutSubscription = notificationEmitter.addListener( + handleNotificationTimeoutEventName, + ({ id, notification }) => + handler.handleError?.(new NotificationTimeoutError(id, notification)) + ); + } +} diff --git a/packages/expo-notifications/src/NotificationsHandlerModule.ts b/packages/expo-notifications/src/NotificationsHandlerModule.ts new file mode 100644 index 0000000000000..c2a3f47fcf739 --- /dev/null +++ b/packages/expo-notifications/src/NotificationsHandlerModule.ts @@ -0,0 +1,16 @@ +import { NativeModulesProxy, ProxyNativeModule } from '@unimodules/core'; + +export interface NotificationBehavior { + shouldShowAlert: boolean; + shouldPlaySound: boolean; + shouldSetBadge: boolean; +} + +export interface NotificationsHandlerModule extends ProxyNativeModule { + handleNotificationAsync: ( + notificationId: string, + notificationBehavior: NotificationBehavior + ) => Promise; +} + +export default (NativeModulesProxy.ExpoNotificationsHandlerModule as any) as NotificationsHandlerModule; diff --git a/packages/expo-notifications/src/index.ts b/packages/expo-notifications/src/index.ts index 821fda06625ec..6e0c18fad4bf4 100644 --- a/packages/expo-notifications/src/index.ts +++ b/packages/expo-notifications/src/index.ts @@ -2,3 +2,4 @@ export { default as getDevicePushTokenAsync } from './getDevicePushTokenAsync'; export { default as getExpoPushTokenAsync } from './getExpoPushTokenAsync'; export * from './TokenEmitter'; export * from './NotificationsEmitter'; +export * from './NotificationsHandler';