@usergist/feedback-react-native 0.1.0
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 +12 -0
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/UserGistFeedback.podspec +31 -0
- package/UserGistFeedbackExtension.podspec +23 -0
- package/android/build.gradle +92 -0
- package/android/consumer-rules.pro +5 -0
- package/android/src/main/AndroidManifest.xml +31 -0
- package/android/src/main/java/studio/usergist/feedback/UserGistFirebaseMessagingService.kt +153 -0
- package/android/src/main/java/studio/usergist/feedback/UserGistPushModule.kt +344 -0
- package/android/src/main/java/studio/usergist/feedback/UserGistPushPackage.kt +18 -0
- package/dist/NativeUserGistPush.d.ts +35 -0
- package/dist/NativeUserGistPush.d.ts.map +1 -0
- package/dist/NativeUserGistPush.js +13 -0
- package/dist/NativeUserGistPush.js.map +1 -0
- package/dist/Provider.d.ts +7 -0
- package/dist/Provider.d.ts.map +1 -0
- package/dist/Provider.js +300 -0
- package/dist/Provider.js.map +1 -0
- package/dist/Push.d.ts +103 -0
- package/dist/Push.d.ts.map +1 -0
- package/dist/Push.js +225 -0
- package/dist/Push.js.map +1 -0
- package/dist/UserGist.d.ts +210 -0
- package/dist/UserGist.d.ts.map +1 -0
- package/dist/UserGist.js +1246 -0
- package/dist/UserGist.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/consent.d.ts +17 -0
- package/dist/internal/consent.d.ts.map +1 -0
- package/dist/internal/consent.js +98 -0
- package/dist/internal/consent.js.map +1 -0
- package/dist/internal/context.d.ts +12 -0
- package/dist/internal/context.d.ts.map +1 -0
- package/dist/internal/context.js +70 -0
- package/dist/internal/context.js.map +1 -0
- package/dist/internal/debug.d.ts +14 -0
- package/dist/internal/debug.d.ts.map +1 -0
- package/dist/internal/debug.js +73 -0
- package/dist/internal/debug.js.map +1 -0
- package/dist/internal/engine.d.ts +93 -0
- package/dist/internal/engine.d.ts.map +1 -0
- package/dist/internal/engine.js +904 -0
- package/dist/internal/engine.js.map +1 -0
- package/dist/internal/events.d.ts +49 -0
- package/dist/internal/events.d.ts.map +1 -0
- package/dist/internal/events.js +71 -0
- package/dist/internal/events.js.map +1 -0
- package/dist/internal/frequency-cap.d.ts +15 -0
- package/dist/internal/frequency-cap.d.ts.map +1 -0
- package/dist/internal/frequency-cap.js +84 -0
- package/dist/internal/frequency-cap.js.map +1 -0
- package/dist/internal/identity.d.ts +14 -0
- package/dist/internal/identity.d.ts.map +1 -0
- package/dist/internal/identity.js +113 -0
- package/dist/internal/identity.js.map +1 -0
- package/dist/internal/inapp-matcher.d.ts +13 -0
- package/dist/internal/inapp-matcher.d.ts.map +1 -0
- package/dist/internal/inapp-matcher.js +65 -0
- package/dist/internal/inapp-matcher.js.map +1 -0
- package/dist/internal/inapp-rules-cache.d.ts +16 -0
- package/dist/internal/inapp-rules-cache.d.ts.map +1 -0
- package/dist/internal/inapp-rules-cache.js +88 -0
- package/dist/internal/inapp-rules-cache.js.map +1 -0
- package/dist/internal/instruction-dedupe.d.ts +16 -0
- package/dist/internal/instruction-dedupe.d.ts.map +1 -0
- package/dist/internal/instruction-dedupe.js +52 -0
- package/dist/internal/instruction-dedupe.js.map +1 -0
- package/dist/internal/lifecycle.d.ts +12 -0
- package/dist/internal/lifecycle.d.ts.map +1 -0
- package/dist/internal/lifecycle.js +97 -0
- package/dist/internal/lifecycle.js.map +1 -0
- package/dist/internal/modal-coordinator.d.ts +4 -0
- package/dist/internal/modal-coordinator.d.ts.map +1 -0
- package/dist/internal/modal-coordinator.js +43 -0
- package/dist/internal/modal-coordinator.js.map +1 -0
- package/dist/internal/modal-queue.d.ts +9 -0
- package/dist/internal/modal-queue.d.ts.map +1 -0
- package/dist/internal/modal-queue.js +32 -0
- package/dist/internal/modal-queue.js.map +1 -0
- package/dist/internal/mutation-queue.d.ts +23 -0
- package/dist/internal/mutation-queue.d.ts.map +1 -0
- package/dist/internal/mutation-queue.js +66 -0
- package/dist/internal/mutation-queue.js.map +1 -0
- package/dist/internal/prompt-answers.d.ts +6 -0
- package/dist/internal/prompt-answers.d.ts.map +1 -0
- package/dist/internal/prompt-answers.js +10 -0
- package/dist/internal/prompt-answers.js.map +1 -0
- package/dist/internal/prompt-flow.d.ts +8 -0
- package/dist/internal/prompt-flow.d.ts.map +1 -0
- package/dist/internal/prompt-flow.js +26 -0
- package/dist/internal/prompt-flow.js.map +1 -0
- package/dist/internal/queue.d.ts +17 -0
- package/dist/internal/queue.d.ts.map +1 -0
- package/dist/internal/queue.js +172 -0
- package/dist/internal/queue.js.map +1 -0
- package/dist/internal/requests.d.ts +22 -0
- package/dist/internal/requests.d.ts.map +1 -0
- package/dist/internal/requests.js +146 -0
- package/dist/internal/requests.js.map +1 -0
- package/dist/internal/rules-cache.d.ts +16 -0
- package/dist/internal/rules-cache.d.ts.map +1 -0
- package/dist/internal/rules-cache.js +86 -0
- package/dist/internal/rules-cache.js.map +1 -0
- package/dist/internal/storage.d.ts +36 -0
- package/dist/internal/storage.d.ts.map +1 -0
- package/dist/internal/storage.js +213 -0
- package/dist/internal/storage.js.map +1 -0
- package/dist/internal/survey-completion.d.ts +10 -0
- package/dist/internal/survey-completion.d.ts.map +1 -0
- package/dist/internal/survey-completion.js +11 -0
- package/dist/internal/survey-completion.js.map +1 -0
- package/dist/internal/survey-matcher.d.ts +21 -0
- package/dist/internal/survey-matcher.d.ts.map +1 -0
- package/dist/internal/survey-matcher.js +139 -0
- package/dist/internal/survey-matcher.js.map +1 -0
- package/dist/internal/survey-rules-cache.d.ts +17 -0
- package/dist/internal/survey-rules-cache.d.ts.map +1 -0
- package/dist/internal/survey-rules-cache.js +91 -0
- package/dist/internal/survey-rules-cache.js.map +1 -0
- package/dist/internal/survey-store.d.ts +19 -0
- package/dist/internal/survey-store.d.ts.map +1 -0
- package/dist/internal/survey-store.js +59 -0
- package/dist/internal/survey-store.js.map +1 -0
- package/dist/internal/transport.d.ts +210 -0
- package/dist/internal/transport.d.ts.map +1 -0
- package/dist/internal/transport.js +498 -0
- package/dist/internal/transport.js.map +1 -0
- package/dist/internal/trigger-matcher.d.ts +21 -0
- package/dist/internal/trigger-matcher.d.ts.map +1 -0
- package/dist/internal/trigger-matcher.js +114 -0
- package/dist/internal/trigger-matcher.js.map +1 -0
- package/dist/internal/types.d.ts +89 -0
- package/dist/internal/types.d.ts.map +1 -0
- package/dist/internal/types.js +2 -0
- package/dist/internal/types.js.map +1 -0
- package/dist/internal/user-state.d.ts +11 -0
- package/dist/internal/user-state.d.ts.map +1 -0
- package/dist/internal/user-state.js +118 -0
- package/dist/internal/user-state.js.map +1 -0
- package/dist/native/events.d.ts +27 -0
- package/dist/native/events.d.ts.map +1 -0
- package/dist/native/events.js +12 -0
- package/dist/native/events.js.map +1 -0
- package/dist/native/push-bridge.d.ts +20 -0
- package/dist/native/push-bridge.d.ts.map +1 -0
- package/dist/native/push-bridge.js +92 -0
- package/dist/native/push-bridge.js.map +1 -0
- package/dist/preview/PromptShell.d.ts +9 -0
- package/dist/preview/PromptShell.d.ts.map +1 -0
- package/dist/preview/PromptShell.js +128 -0
- package/dist/preview/PromptShell.js.map +1 -0
- package/dist/preview/SurveyShell.d.ts +15 -0
- package/dist/preview/SurveyShell.d.ts.map +1 -0
- package/dist/preview/SurveyShell.js +197 -0
- package/dist/preview/SurveyShell.js.map +1 -0
- package/dist/preview/index.d.ts +4 -0
- package/dist/preview/index.d.ts.map +1 -0
- package/dist/preview/index.js +16 -0
- package/dist/preview/index.js.map +1 -0
- package/dist/ui/InAppMessageView.d.ts +12 -0
- package/dist/ui/InAppMessageView.d.ts.map +1 -0
- package/dist/ui/InAppMessageView.js +260 -0
- package/dist/ui/InAppMessageView.js.map +1 -0
- package/dist/ui/PromptSheet.d.ts +12 -0
- package/dist/ui/PromptSheet.d.ts.map +1 -0
- package/dist/ui/PromptSheet.js +285 -0
- package/dist/ui/PromptSheet.js.map +1 -0
- package/dist/ui/QuestionImageHeader.d.ts +12 -0
- package/dist/ui/QuestionImageHeader.d.ts.map +1 -0
- package/dist/ui/QuestionImageHeader.js +22 -0
- package/dist/ui/QuestionImageHeader.js.map +1 -0
- package/dist/ui/SurveyView.d.ts +25 -0
- package/dist/ui/SurveyView.d.ts.map +1 -0
- package/dist/ui/SurveyView.js +478 -0
- package/dist/ui/SurveyView.js.map +1 -0
- package/dist/ui/animations.d.ts +4 -0
- package/dist/ui/animations.d.ts.map +1 -0
- package/dist/ui/animations.js +24 -0
- package/dist/ui/animations.js.map +1 -0
- package/dist/ui/questions/MultipleChoiceQuestion.d.ts +12 -0
- package/dist/ui/questions/MultipleChoiceQuestion.d.ts.map +1 -0
- package/dist/ui/questions/MultipleChoiceQuestion.js +41 -0
- package/dist/ui/questions/MultipleChoiceQuestion.js.map +1 -0
- package/dist/ui/questions/NpsQuestion.d.ts +14 -0
- package/dist/ui/questions/NpsQuestion.d.ts.map +1 -0
- package/dist/ui/questions/NpsQuestion.js +73 -0
- package/dist/ui/questions/NpsQuestion.js.map +1 -0
- package/dist/ui/questions/RatingQuestion.d.ts +21 -0
- package/dist/ui/questions/RatingQuestion.d.ts.map +1 -0
- package/dist/ui/questions/RatingQuestion.js +91 -0
- package/dist/ui/questions/RatingQuestion.js.map +1 -0
- package/dist/ui/questions/ShortTextQuestion.d.ts +12 -0
- package/dist/ui/questions/ShortTextQuestion.d.ts.map +1 -0
- package/dist/ui/questions/ShortTextQuestion.js +25 -0
- package/dist/ui/questions/ShortTextQuestion.js.map +1 -0
- package/dist/ui/requests/BoardView.d.ts +9 -0
- package/dist/ui/requests/BoardView.d.ts.map +1 -0
- package/dist/ui/requests/BoardView.js +270 -0
- package/dist/ui/requests/BoardView.js.map +1 -0
- package/dist/ui/requests/CommentsSection.d.ts +7 -0
- package/dist/ui/requests/CommentsSection.d.ts.map +1 -0
- package/dist/ui/requests/CommentsSection.js +254 -0
- package/dist/ui/requests/CommentsSection.js.map +1 -0
- package/dist/ui/requests/DetailView.d.ts +8 -0
- package/dist/ui/requests/DetailView.d.ts.map +1 -0
- package/dist/ui/requests/DetailView.js +206 -0
- package/dist/ui/requests/DetailView.js.map +1 -0
- package/dist/ui/requests/RequestsHost.d.ts +12 -0
- package/dist/ui/requests/RequestsHost.d.ts.map +1 -0
- package/dist/ui/requests/RequestsHost.js +58 -0
- package/dist/ui/requests/RequestsHost.js.map +1 -0
- package/dist/ui/requests/StatusPill.d.ts +6 -0
- package/dist/ui/requests/StatusPill.d.ts.map +1 -0
- package/dist/ui/requests/StatusPill.js +25 -0
- package/dist/ui/requests/StatusPill.js.map +1 -0
- package/dist/ui/requests/SubmitView.d.ts +8 -0
- package/dist/ui/requests/SubmitView.d.ts.map +1 -0
- package/dist/ui/requests/SubmitView.js +94 -0
- package/dist/ui/requests/SubmitView.js.map +1 -0
- package/dist/ui/requests/shared.d.ts +21 -0
- package/dist/ui/requests/shared.d.ts.map +1 -0
- package/dist/ui/requests/shared.js +74 -0
- package/dist/ui/requests/shared.js.map +1 -0
- package/dist/ui/survey-questions.d.ts +20 -0
- package/dist/ui/survey-questions.d.ts.map +1 -0
- package/dist/ui/survey-questions.js +211 -0
- package/dist/ui/survey-questions.js.map +1 -0
- package/dist/ui/theme.d.ts +15 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +31 -0
- package/dist/ui/theme.js.map +1 -0
- package/ios/Extension/UserGistNotificationService.swift +292 -0
- package/ios/UserGistPush.h +16 -0
- package/ios/UserGistPush.mm +158 -0
- package/ios/UserGistPushImpl.swift +399 -0
- package/ios/UserGistPushSwizzle.m +168 -0
- package/package.json +89 -0
- package/react-native.config.js +16 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
package studio.usergist.feedback
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.app.Activity
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.content.pm.PackageManager
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import androidx.core.app.ActivityCompat
|
|
9
|
+
import androidx.core.app.NotificationManagerCompat
|
|
10
|
+
import androidx.security.crypto.EncryptedSharedPreferences
|
|
11
|
+
import androidx.security.crypto.MasterKey
|
|
12
|
+
import com.facebook.react.bridge.Arguments
|
|
13
|
+
import com.facebook.react.bridge.Promise
|
|
14
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
15
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
16
|
+
import com.facebook.react.bridge.ReactMethod
|
|
17
|
+
import com.facebook.react.bridge.ReadableMap
|
|
18
|
+
import com.facebook.react.bridge.ReadableArray
|
|
19
|
+
import com.facebook.react.bridge.WritableMap
|
|
20
|
+
import com.facebook.react.bridge.ActivityEventListener
|
|
21
|
+
import com.facebook.react.bridge.LifecycleEventListener
|
|
22
|
+
import com.facebook.react.modules.core.PermissionAwareActivity
|
|
23
|
+
import com.facebook.react.modules.core.PermissionListener
|
|
24
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
25
|
+
import com.google.android.gms.tasks.OnCompleteListener
|
|
26
|
+
import com.google.firebase.messaging.FirebaseMessaging
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Native bridge for the UserGist push pipeline on Android.
|
|
30
|
+
*
|
|
31
|
+
* Responsibilities:
|
|
32
|
+
* - Request POST_NOTIFICATIONS at runtime on Android 13+.
|
|
33
|
+
* - Surface the current FCM token via FirebaseMessaging.getInstance().token.
|
|
34
|
+
* - Forward token-refresh + RemoteMessage delivery to JS. The actual
|
|
35
|
+
* RemoteMessage handling lives in UserGistFirebaseMessagingService;
|
|
36
|
+
* this module exposes only the imperative side (JS -> native calls).
|
|
37
|
+
*/
|
|
38
|
+
@Suppress("UNUSED_PARAMETER")
|
|
39
|
+
class UserGistPushModule(reactContext: ReactApplicationContext) :
|
|
40
|
+
ReactContextBaseJavaModule(reactContext), LifecycleEventListener, ActivityEventListener {
|
|
41
|
+
|
|
42
|
+
init {
|
|
43
|
+
// Wire ourselves into the static event-emit helper so the Firebase
|
|
44
|
+
// service can hand events back without holding a strong reference to
|
|
45
|
+
// the React module.
|
|
46
|
+
UserGistPushEventBus.attach(reactContext)
|
|
47
|
+
reactContext.addLifecycleEventListener(this)
|
|
48
|
+
reactContext.addActivityEventListener(this)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override fun getName(): String = NAME
|
|
52
|
+
|
|
53
|
+
private val securePreferences by lazy {
|
|
54
|
+
val masterKey = MasterKey.Builder(reactApplicationContext)
|
|
55
|
+
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
|
56
|
+
.build()
|
|
57
|
+
EncryptedSharedPreferences.create(
|
|
58
|
+
reactApplicationContext,
|
|
59
|
+
"usergist_react_native_secure",
|
|
60
|
+
masterKey,
|
|
61
|
+
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
|
62
|
+
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun onHostResume() {
|
|
67
|
+
currentActivity?.intent?.let { consumeNotificationIntent(it) }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override fun onHostPause() = Unit
|
|
71
|
+
override fun onHostDestroy() = Unit
|
|
72
|
+
override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) = Unit
|
|
73
|
+
|
|
74
|
+
override fun onNewIntent(intent: Intent) {
|
|
75
|
+
consumeNotificationIntent(intent)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private fun consumeNotificationIntent(intent: Intent) {
|
|
79
|
+
val extras = intent.extras ?: return
|
|
80
|
+
val keys = extras.keySet().filter { it.startsWith("usergist_") }
|
|
81
|
+
if (keys.isEmpty()) return
|
|
82
|
+
val body = Arguments.createMap()
|
|
83
|
+
val data = Arguments.createMap()
|
|
84
|
+
for (key in keys) {
|
|
85
|
+
val value = extras.getString(key) ?: continue
|
|
86
|
+
val normalized = key.removePrefix("usergist_")
|
|
87
|
+
when (normalized) {
|
|
88
|
+
"title" -> body.putString("title", value)
|
|
89
|
+
"body" -> body.putString("body", value)
|
|
90
|
+
"delivery_id" -> body.putString("deliveryId", value)
|
|
91
|
+
"action_identifier" -> body.putString("actionIdentifier", value)
|
|
92
|
+
else -> data.putString(key, value)
|
|
93
|
+
}
|
|
94
|
+
intent.removeExtra(key)
|
|
95
|
+
}
|
|
96
|
+
body.putMap("data", data)
|
|
97
|
+
UserGistPushEventBus.emitNotificationOpened(body)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// MARK: - Exported methods
|
|
101
|
+
|
|
102
|
+
@ReactMethod
|
|
103
|
+
fun enablePush(_options: ReadableMap?, promise: Promise) {
|
|
104
|
+
// Android < 13: notification permission is install-time, no runtime
|
|
105
|
+
// prompt. We can short-circuit straight to the token fetch.
|
|
106
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
|
107
|
+
val areEnabled = NotificationManagerCompat.from(reactApplicationContext).areNotificationsEnabled()
|
|
108
|
+
if (!areEnabled) {
|
|
109
|
+
promise.resolve(buildResult(granted = false, status = "denied", token = null))
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
fetchTokenAndResolve(promise, status = "authorized")
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
val activity = currentActivity
|
|
116
|
+
if (activity == null) {
|
|
117
|
+
// No activity: can't ask for permission. Resolve with current state.
|
|
118
|
+
val granted = ActivityCompat.checkSelfPermission(
|
|
119
|
+
reactApplicationContext,
|
|
120
|
+
Manifest.permission.POST_NOTIFICATIONS
|
|
121
|
+
) == PackageManager.PERMISSION_GRANTED
|
|
122
|
+
if (granted) {
|
|
123
|
+
fetchTokenAndResolve(promise, status = "authorized")
|
|
124
|
+
} else {
|
|
125
|
+
promise.resolve(buildResult(granted = false, status = "not_determined", token = null))
|
|
126
|
+
}
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
val already = ActivityCompat.checkSelfPermission(
|
|
130
|
+
activity,
|
|
131
|
+
Manifest.permission.POST_NOTIFICATIONS
|
|
132
|
+
) == PackageManager.PERMISSION_GRANTED
|
|
133
|
+
if (already) {
|
|
134
|
+
fetchTokenAndResolve(promise, status = "authorized")
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
val permAware = activity as? PermissionAwareActivity
|
|
138
|
+
if (permAware == null) {
|
|
139
|
+
// Fallback: classic ActivityCompat, no result callback we can hook.
|
|
140
|
+
ActivityCompat.requestPermissions(
|
|
141
|
+
activity,
|
|
142
|
+
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
|
143
|
+
REQUEST_CODE
|
|
144
|
+
)
|
|
145
|
+
// Without a callback we can't know the outcome — resolve as
|
|
146
|
+
// not_determined so the caller can re-call after the dialog closes.
|
|
147
|
+
promise.resolve(buildResult(granted = false, status = "not_determined", token = null))
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
permAware.requestPermissions(
|
|
151
|
+
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
|
152
|
+
REQUEST_CODE,
|
|
153
|
+
PermissionListener { _, _, grantResults ->
|
|
154
|
+
val grantedNow = grantResults.isNotEmpty() &&
|
|
155
|
+
grantResults[0] == PackageManager.PERMISSION_GRANTED
|
|
156
|
+
if (grantedNow) {
|
|
157
|
+
fetchTokenAndResolve(promise, status = "authorized")
|
|
158
|
+
} else {
|
|
159
|
+
promise.resolve(buildResult(granted = false, status = "denied", token = null))
|
|
160
|
+
}
|
|
161
|
+
true
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@ReactMethod
|
|
167
|
+
fun disablePush(promise: Promise) {
|
|
168
|
+
FirebaseMessaging.getInstance().deleteToken()
|
|
169
|
+
.addOnCompleteListener { promise.resolve(null) }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@ReactMethod
|
|
173
|
+
fun getPushPermissionStatus(promise: Promise) {
|
|
174
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
|
175
|
+
val areEnabled = NotificationManagerCompat.from(reactApplicationContext).areNotificationsEnabled()
|
|
176
|
+
promise.resolve(if (areEnabled) "authorized" else "denied")
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
val granted = ActivityCompat.checkSelfPermission(
|
|
180
|
+
reactApplicationContext,
|
|
181
|
+
Manifest.permission.POST_NOTIFICATIONS
|
|
182
|
+
) == PackageManager.PERMISSION_GRANTED
|
|
183
|
+
promise.resolve(if (granted) "authorized" else "denied")
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@ReactMethod
|
|
187
|
+
fun setBadgeCount(_count: Double, promise: Promise) {
|
|
188
|
+
// Android does not expose a portable badge API; some launchers honour
|
|
189
|
+
// a ShortcutBadger-style broadcast but it's launcher-specific. No-op
|
|
190
|
+
// for now; consumers who need it can use ShortcutBadger directly.
|
|
191
|
+
promise.resolve(null)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@ReactMethod
|
|
195
|
+
fun getInitialNotification(promise: Promise) {
|
|
196
|
+
promise.resolve(UserGistPushEventBus.takeInitialNotification())
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@ReactMethod
|
|
200
|
+
fun secureGetItem(key: String, promise: Promise) {
|
|
201
|
+
runCatching { securePreferences.getString(key, null) }
|
|
202
|
+
.onSuccess(promise::resolve)
|
|
203
|
+
.onFailure { promise.reject("secure_read_failed", it) }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@ReactMethod
|
|
207
|
+
fun secureSetItem(key: String, value: String, promise: Promise) {
|
|
208
|
+
runCatching {
|
|
209
|
+
check(securePreferences.edit().putString(key, value).commit())
|
|
210
|
+
}.onSuccess { promise.resolve(null) }
|
|
211
|
+
.onFailure { promise.reject("secure_write_failed", it) }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
@ReactMethod
|
|
215
|
+
fun secureRemoveItem(key: String, promise: Promise) {
|
|
216
|
+
runCatching { check(securePreferences.edit().remove(key).commit()) }
|
|
217
|
+
.onSuccess { promise.resolve(null) }
|
|
218
|
+
.onFailure { promise.reject("secure_remove_failed", it) }
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@ReactMethod
|
|
222
|
+
fun secureMultiRemove(keys: ReadableArray, promise: Promise) {
|
|
223
|
+
runCatching {
|
|
224
|
+
val edit = securePreferences.edit()
|
|
225
|
+
for (index in 0 until keys.size()) {
|
|
226
|
+
edit.remove(keys.getString(index))
|
|
227
|
+
}
|
|
228
|
+
check(edit.commit())
|
|
229
|
+
}.onSuccess { promise.resolve(null) }
|
|
230
|
+
.onFailure { promise.reject("secure_remove_failed", it) }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Required for RN's NativeEventEmitter API even though we emit from a
|
|
234
|
+
// separate static bus — RN will warn otherwise.
|
|
235
|
+
@ReactMethod
|
|
236
|
+
fun addListener(_eventName: String?) { /* no-op */ }
|
|
237
|
+
|
|
238
|
+
@ReactMethod
|
|
239
|
+
fun removeListeners(_count: Double) { /* no-op */ }
|
|
240
|
+
|
|
241
|
+
// MARK: - Internal
|
|
242
|
+
|
|
243
|
+
private fun fetchTokenAndResolve(promise: Promise, status: String) {
|
|
244
|
+
FirebaseMessaging.getInstance().token.addOnCompleteListener(
|
|
245
|
+
OnCompleteListener { task ->
|
|
246
|
+
if (!task.isSuccessful) {
|
|
247
|
+
val msg = task.exception?.localizedMessage ?: "fcm_token_unavailable"
|
|
248
|
+
promise.resolve(buildResult(granted = true, status = status, token = null, error = msg))
|
|
249
|
+
return@OnCompleteListener
|
|
250
|
+
}
|
|
251
|
+
val token = task.result
|
|
252
|
+
// Emit on top of resolving so JS gets both the resolution AND the
|
|
253
|
+
// refresh-style event (idempotent on the server side).
|
|
254
|
+
UserGistPushEventBus.emitToken(token)
|
|
255
|
+
promise.resolve(buildResult(granted = true, status = status, token = token))
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private fun buildResult(
|
|
261
|
+
granted: Boolean,
|
|
262
|
+
status: String,
|
|
263
|
+
token: String?,
|
|
264
|
+
error: String? = null
|
|
265
|
+
): WritableMap {
|
|
266
|
+
val map = Arguments.createMap()
|
|
267
|
+
map.putBoolean("granted", granted)
|
|
268
|
+
map.putString("status", status)
|
|
269
|
+
map.putString("platform", "android")
|
|
270
|
+
if (token != null) map.putString("token", token)
|
|
271
|
+
if (error != null) map.putString("error", error)
|
|
272
|
+
return map
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
companion object {
|
|
276
|
+
const val NAME = "UserGistPush"
|
|
277
|
+
private const val REQUEST_CODE = 0x52A1 // arbitrary 16-bit; "RA" in hex
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Static bus owned by the UserGistFirebaseMessagingService; lets the service
|
|
283
|
+
* forward events to JS without holding a strong reference to the
|
|
284
|
+
* (possibly-not-yet-initialized) React Native bridge.
|
|
285
|
+
*/
|
|
286
|
+
internal object UserGistPushEventBus {
|
|
287
|
+
@Volatile private var reactContext: ReactApplicationContext? = null
|
|
288
|
+
private val pending: MutableList<Pair<String, WritableMap>> = mutableListOf()
|
|
289
|
+
private var initialNotification: WritableMap? = null
|
|
290
|
+
|
|
291
|
+
@Synchronized
|
|
292
|
+
fun attach(ctx: ReactApplicationContext) {
|
|
293
|
+
this.reactContext = ctx
|
|
294
|
+
val drained = pending.toList()
|
|
295
|
+
pending.clear()
|
|
296
|
+
for ((name, body) in drained) {
|
|
297
|
+
send(name, body)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
fun emitToken(token: String) {
|
|
302
|
+
val map = Arguments.createMap()
|
|
303
|
+
map.putString("token", token)
|
|
304
|
+
map.putString("platform", "android")
|
|
305
|
+
emit("UserGistPush:tokenReceived", map)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
fun emitNotificationReceived(body: WritableMap) {
|
|
309
|
+
emit("UserGistPush:notificationReceived", body)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
@Synchronized
|
|
313
|
+
fun emitNotificationOpened(body: WritableMap) {
|
|
314
|
+
if (initialNotification == null) {
|
|
315
|
+
// Snapshot so getInitialNotification() can return it once.
|
|
316
|
+
initialNotification = Arguments.createMap().apply { merge(body) }
|
|
317
|
+
}
|
|
318
|
+
emit("UserGistPush:notificationOpened", body)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
@Synchronized
|
|
322
|
+
fun takeInitialNotification(): WritableMap? {
|
|
323
|
+
val v = initialNotification
|
|
324
|
+
initialNotification = null
|
|
325
|
+
return v
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
@Synchronized
|
|
329
|
+
private fun emit(name: String, body: WritableMap) {
|
|
330
|
+
val ctx = reactContext
|
|
331
|
+
if (ctx == null || !ctx.hasActiveReactInstance()) {
|
|
332
|
+
if (pending.size >= 100) pending.removeAt(0)
|
|
333
|
+
pending.add(name to body)
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
send(name, body)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private fun send(name: String, body: WritableMap) {
|
|
340
|
+
val ctx = reactContext ?: return
|
|
341
|
+
ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
342
|
+
.emit(name, body)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
package studio.usergist.feedback
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Registers UserGistPushModule with the React Native bridge. Picked up via
|
|
10
|
+
* autolinking — see react-native.config.js at the package root.
|
|
11
|
+
*/
|
|
12
|
+
class UserGistPushPackage : ReactPackage {
|
|
13
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
|
|
14
|
+
listOf(UserGistPushModule(reactContext))
|
|
15
|
+
|
|
16
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
|
|
17
|
+
emptyList()
|
|
18
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { TurboModule } from 'react-native';
|
|
2
|
+
export interface EnablePushOptions {
|
|
3
|
+
readonly skipPermissionRequestIfDenied?: boolean;
|
|
4
|
+
readonly environment?: 'sandbox' | 'production';
|
|
5
|
+
/** Disable delegate interception when the host forwards tokens/messages via Push. */
|
|
6
|
+
readonly installDelegateProxy?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface EnablePushResult {
|
|
9
|
+
readonly granted: boolean;
|
|
10
|
+
readonly status: 'authorized' | 'provisional' | 'denied' | 'not_determined';
|
|
11
|
+
readonly token?: string;
|
|
12
|
+
readonly platform: 'ios' | 'android';
|
|
13
|
+
readonly error?: string;
|
|
14
|
+
}
|
|
15
|
+
export type PushPermissionStatus = 'authorized' | 'provisional' | 'denied' | 'not_determined';
|
|
16
|
+
export interface Spec extends TurboModule {
|
|
17
|
+
enablePush(options: {
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
} | null): Promise<EnablePushResult>;
|
|
20
|
+
disablePush(): Promise<void>;
|
|
21
|
+
getPushPermissionStatus(): Promise<PushPermissionStatus>;
|
|
22
|
+
setBadgeCount(count: number): Promise<void>;
|
|
23
|
+
getInitialNotification(): Promise<{
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
} | null>;
|
|
26
|
+
secureGetItem(key: string): Promise<string | null>;
|
|
27
|
+
secureSetItem(key: string, value: string): Promise<void>;
|
|
28
|
+
secureRemoveItem(key: string): Promise<void>;
|
|
29
|
+
secureMultiRemove(keys: ReadonlyArray<string>): Promise<void>;
|
|
30
|
+
addListener(eventName: string): void;
|
|
31
|
+
removeListeners(count: number): void;
|
|
32
|
+
}
|
|
33
|
+
declare const _default: Spec | null;
|
|
34
|
+
export default _default;
|
|
35
|
+
//# sourceMappingURL=NativeUserGistPush.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NativeUserGistPush.d.ts","sourceRoot":"","sources":["../src/NativeUserGistPush.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAG/C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,6BAA6B,CAAC,EAAE,OAAO,CAAA;IAChD,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,GAAG,YAAY,CAAA;IAC/C,qFAAqF;IACrF,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;CACxC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,aAAa,GAAG,QAAQ,GAAG,gBAAgB,CAAA;IAC3E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,KAAK,GAAG,SAAS,CAAA;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,MAAM,oBAAoB,GAC5B,YAAY,GACZ,aAAa,GACb,QAAQ,GACR,gBAAgB,CAAA;AAEpB,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,UAAU,CAAC,OAAO,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,GAAG,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAA;IACjF,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,uBAAuB,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAAA;IACxD,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3C,sBAAsB,IAAI,OAAO,CAAC;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;IACpE,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAClD,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5C,iBAAiB,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE7D,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrC;;AAED,wBAA4D"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// TurboModule spec for the UserGist push native module.
|
|
2
|
+
//
|
|
3
|
+
// React Native's codegen consumes this file and generates the native
|
|
4
|
+
// scaffolding that lets the new architecture (Bridgeless / Fabric)
|
|
5
|
+
// resolve our module without going through the legacy bridge.
|
|
6
|
+
//
|
|
7
|
+
// At runtime, `src/native/push-bridge.ts` first tries
|
|
8
|
+
// `TurboModuleRegistry.getEnforcing<Spec>('UserGistPush')` and falls back
|
|
9
|
+
// to `NativeModules.UserGistPush` so the package works in both
|
|
10
|
+
// architectures (RN 0.72 → 0.79+).
|
|
11
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
12
|
+
export default TurboModuleRegistry.get('UserGistPush');
|
|
13
|
+
//# sourceMappingURL=NativeUserGistPush.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NativeUserGistPush.js","sourceRoot":"","sources":["../src/NativeUserGistPush.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,EAAE;AACF,qEAAqE;AACrE,mEAAmE;AACnE,8DAA8D;AAC9D,EAAE;AACF,sDAAsD;AACtD,0EAA0E;AAC1E,+DAA+D;AAC/D,mCAAmC;AAGnC,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAsClD,eAAe,mBAAmB,CAAC,GAAG,CAAO,cAAc,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Provider.d.ts","sourceRoot":"","sources":["../src/Provider.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAmD,MAAM,OAAO,CAAA;AA0EvE,UAAU,KAAK;IACb,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACpC;AAUD,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,EAAE,KAAK,GAAG,KAAK,CAAC,YAAY,CAoTxE"}
|