diff --git a/App.tsx b/App.tsx
index 45286e2..6a99b46 100644
--- a/App.tsx
+++ b/App.tsx
@@ -85,6 +85,11 @@ function App(): JSX.Element {
);
useEffect(() => {
+ // check if app was launched by tapping a native notification (cold start)
+ getPendingNotificationClick().then(data => {
+ if (data) handleNotificationClick({data});
+ });
+
notifee.getInitialNotification().then(initN => {
if (!initN?.notification) {
return;
@@ -101,6 +106,10 @@ function App(): JSX.Element {
);
const event = AppState.addEventListener('focus', () => {
+ // check if app was resumed by tapping a native notification (background state)
+ getPendingNotificationClick().then(data => {
+ if (data) handleNotificationClick({data});
+ });
if (backgroundClickedNotification) {
handleNotificationClick(backgroundClickedNotification);
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index ea87431..5582641 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -30,5 +30,8 @@
+
diff --git a/android/app/src/main/java/com/nerimityreactnative/MainActivity.kt b/android/app/src/main/java/com/nerimityreactnative/MainActivity.kt
index 505e3bb..dc14965 100644
--- a/android/app/src/main/java/com/nerimityreactnative/MainActivity.kt
+++ b/android/app/src/main/java/com/nerimityreactnative/MainActivity.kt
@@ -1,22 +1,29 @@
package com.nerimityreactnative
+import android.content.Intent
+import android.os.Bundle
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
+import com.nerimityreactnative.custom.EmojiNotificationModule
class MainActivity : ReactActivity() {
- /**
- * Returns the name of the main component registered from JavaScript. This is used to schedule
- * rendering of the component.
- */
override fun getMainComponentName(): String = "NerimityReactNative"
- /**
- * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
- * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
- */
override fun createReactActivityDelegate(): ReactActivityDelegate =
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
+
+ // called when app is launched from dead state via notification tap
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ intent?.let { EmojiNotificationModule.storeNotificationData(it) }
+ }
+
+ // called when app is already running (background) and notification is tapped
+ override fun onNewIntent(intent: Intent?) {
+ super.onNewIntent(intent)
+ intent?.let { EmojiNotificationModule.storeNotificationData(it) }
+ }
}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/nerimityreactnative/MainApplication.kt b/android/app/src/main/java/com/nerimityreactnative/MainApplication.kt
index fb0b848..5dba811 100644
--- a/android/app/src/main/java/com/nerimityreactnative/MainApplication.kt
+++ b/android/app/src/main/java/com/nerimityreactnative/MainApplication.kt
@@ -10,6 +10,7 @@ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.soloader.SoLoader
+import com.nerimityreactnative.custom.EmojiNotificationPackage
class MainApplication : Application(), ReactApplication {
@@ -19,6 +20,7 @@ class MainApplication : Application(), ReactApplication {
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(MyReactNativePackage())
+ add(EmojiNotificationPackage())
}
override fun getJSMainModuleName(): String = "index"
diff --git a/android/app/src/main/java/com/nerimityreactnative/custom/EmojiNotificationModule.kt b/android/app/src/main/java/com/nerimityreactnative/custom/EmojiNotificationModule.kt
new file mode 100644
index 0000000..e54d714
--- /dev/null
+++ b/android/app/src/main/java/com/nerimityreactnative/custom/EmojiNotificationModule.kt
@@ -0,0 +1,391 @@
+package com.nerimityreactnative.custom
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.RectF
+import android.text.Spanned
+import android.widget.RemoteViews
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import com.facebook.react.bridge.Arguments
+import com.facebook.react.bridge.Promise
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.bridge.ReactContextBaseJavaModule
+import com.facebook.react.bridge.ReactMethod
+import com.facebook.react.bridge.ReadableMap
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import com.nerimityreactnative.R
+import java.net.URL
+
+class EmojiNotificationModule(reactContext: ReactApplicationContext) :
+ ReactContextBaseJavaModule(reactContext) {
+
+ companion object {
+ private const val MAX_MESSAGES = 6
+ // channel id => list of (body, emojiBitmaps)
+ private val messageHistory = mutableMapOf>()
+
+ var pendingChannelId: String? = null
+ var pendingServerId: String? = null
+ var pendingUserId: String? = null
+
+ fun clearHistory(channelId: String) {
+ messageHistory.remove(channelId)
+ }
+
+ fun storeNotificationData(intent: android.content.Intent) {
+ val channelId = intent.getStringExtra("notif_channelId") ?: return
+ pendingChannelId = channelId
+ pendingServerId = intent.getStringExtra("notif_serverId")
+ pendingUserId = intent.getStringExtra("notif_userId")
+ }
+ }
+
+ private data class MessageEntry(
+ val body: String,
+ val unicodeBody: String,
+ val emojiBitmaps: Map
+ )
+
+ override fun getName(): String = "EmojiNotificationModule"
+
+ @ReactMethod
+ fun displayNotification(params: ReadableMap) {
+ val context = reactApplicationContext
+ val id = params.getString("id") ?: return
+ val title = params.getString("title") ?: ""
+ val body = params.getString("body") ?: ""
+ val channelId = params.getString("channelId") ?: return
+ val largeIconUrl = if (params.hasKey("largeIcon") && !params.isNull("largeIcon")) params.getString("largeIcon") else null
+ val circularLargeIcon = if (params.hasKey("circularLargeIcon")) params.getBoolean("circularLargeIcon") else false
+ val subText = if (params.hasKey("subText") && !params.isNull("subText")) params.getString("subText") else null
+ val fallbackAvatarLetter = if (params.hasKey("fallbackAvatarLetter") && !params.isNull("fallbackAvatarLetter")) params.getString("fallbackAvatarLetter") else null
+ val fallbackAvatarColor = if (params.hasKey("fallbackAvatarColor") && !params.isNull("fallbackAvatarColor")) params.getString("fallbackAvatarColor") else null
+ val serverId = if (params.hasKey("serverId") && !params.isNull("serverId")) params.getString("serverId") else null
+ val userId = if (params.hasKey("userId") && !params.isNull("userId")) params.getString("userId") else null
+
+ val emojis = mutableListOf()
+ if (params.hasKey("emojis") && !params.isNull("emojis")) {
+ val emojiArray = params.getArray("emojis")
+ if (emojiArray != null) {
+ for (i in 0 until emojiArray.size()) {
+ val emojiMap = emojiArray.getMap(i)
+ emojis.add(
+ EmojiInfo(
+ placeholder = emojiMap.getString("placeholder") ?: "",
+ url = emojiMap.getString("url") ?: ""
+ )
+ )
+ }
+ }
+ }
+
+ CoroutineScope(Dispatchers.IO).launch {
+ val emojiBitmaps = mutableMapOf()
+ for (emoji in emojis) {
+ try {
+ val bitmap = downloadBitmap(emoji.url)
+ if (bitmap != null) {
+ emojiBitmaps[emoji.placeholder] = bitmap
+ }
+ } catch (_: Exception) {}
+ }
+
+ var largeIconBitmap: Bitmap? = null
+ if (largeIconUrl != null) {
+ try {
+ val raw = downloadBitmap(largeIconUrl)
+ if (raw != null) {
+ largeIconBitmap = if (circularLargeIcon) makeCircular(raw) else raw
+ }
+ } catch (_: Exception) {}
+ }
+ if (largeIconBitmap == null && fallbackAvatarLetter != null && fallbackAvatarColor != null) {
+ largeIconBitmap = generateLetterAvatar(fallbackAvatarLetter, fallbackAvatarColor)
+ }
+
+ // body
+ // unicode emoji fallback (text rendering)
+ val unicodeBody = replaceEmojiPlaceholdersWithUnicode(body, emojiBitmaps.keys)
+
+ // add to message history
+ // store unicode body (text fallback) and keep bitmaps (image rendering)
+ val history = messageHistory.getOrPut(id) { mutableListOf() }
+ history.add(MessageEntry(body, unicodeBody, emojiBitmaps))
+ while (history.size > MAX_MESSAGES) {
+ history.removeAt(0)
+ }
+
+ // build collapsed view => only latest message, single line (+ ellipsis)
+ val latestSegments = splitBodyIntoSegments(body, emojiBitmaps.keys)
+ val isSingleEmoji = latestSegments.size == 1 && latestSegments[0].isEmoji
+ val appPackageName = context.packageName
+
+ val collapsedView = RemoteViews(appPackageName, R.layout.notification_emoji_collapsed)
+ collapsedView.setTextViewText(R.id.notification_title, fromHtml(title))
+ collapsedView.removeAllViews(R.id.notification_body_row)
+ if (isSingleEmoji && emojiBitmaps.containsKey(latestSegments[0].content)) {
+ val emojiView = RemoteViews(appPackageName, R.layout.notification_emoji_segment)
+ emojiView.setImageViewBitmap(R.id.segment_emoji, emojiBitmaps[latestSegments[0].content])
+ collapsedView.addView(R.id.notification_body_row, emojiView)
+ } else {
+ val textView = RemoteViews(appPackageName, R.layout.notification_text_segment_single)
+ textView.setTextViewText(R.id.segment_text_single, fromHtml(unicodeBody))
+ collapsedView.addView(R.id.notification_body_row, textView)
+ }
+
+ val headsUpView = RemoteViews(appPackageName, R.layout.notification_emoji_collapsed)
+ headsUpView.setTextViewText(R.id.notification_title, fromHtml(title))
+ headsUpView.removeAllViews(R.id.notification_body_row)
+ if (isSingleEmoji && emojiBitmaps.containsKey(latestSegments[0].content)) {
+ val emojiView = RemoteViews(appPackageName, R.layout.notification_emoji_segment)
+ emojiView.setImageViewBitmap(R.id.segment_emoji, emojiBitmaps[latestSegments[0].content])
+ headsUpView.addView(R.id.notification_body_row, emojiView)
+ } else {
+ val textView = RemoteViews(appPackageName, R.layout.notification_text_segment_single)
+ textView.setTextViewText(R.id.segment_text_single, fromHtml(unicodeBody))
+ headsUpView.addView(R.id.notification_body_row, textView)
+ }
+
+ // build expanded view => complete message history
+ val expandedView = buildExpandedRemoteViews(id, title, isSingleEmoji && history.size == 1)
+
+ withContext(Dispatchers.Main) {
+ val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
+ ?: Intent()
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ launchIntent.putExtra("notif_channelId", id)
+ serverId?.let { launchIntent.putExtra("notif_serverId", it) }
+ userId?.let { launchIntent.putExtra("notif_userId", it) }
+ val pendingIntent = PendingIntent.getActivity(
+ context, id.hashCode(), launchIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ val dismissIntent = Intent(context, NotificationDismissReceiver::class.java).apply {
+ putExtra(NotificationDismissReceiver.EXTRA_CHANNEL_ID, id)
+ }
+ val deletePendingIntent = PendingIntent.getBroadcast(
+ context, id.hashCode(), dismissIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ val builder = NotificationCompat.Builder(context, channelId)
+ .setSmallIcon(context.resources.getIdentifier("ic_stat_notify", "drawable", context.packageName))
+ .setContentTitle(fromHtml(title))
+ .setContentText(fromHtml(unicodeBody))
+ .setContentIntent(pendingIntent)
+ .setDeleteIntent(deletePendingIntent)
+ .setAutoCancel(true)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .setCustomContentView(collapsedView)
+ .setCustomHeadsUpContentView(headsUpView)
+ .setCustomBigContentView(expandedView)
+ .setStyle(NotificationCompat.DecoratedCustomViewStyle())
+
+ if (subText != null) {
+ builder.setSubText(subText)
+ }
+
+ if (largeIconBitmap != null) {
+ builder.setLargeIcon(largeIconBitmap)
+ }
+
+ try {
+ NotificationManagerCompat.from(context).notify(id.hashCode(), builder.build())
+ } catch (_: SecurityException) {}
+ }
+ }
+ }
+
+ @ReactMethod
+ fun getPendingNotificationClick(promise: Promise) {
+ val channelId = pendingChannelId
+ if (channelId != null) {
+ val map = Arguments.createMap()
+ map.putString("channelId", channelId)
+ pendingServerId?.let { map.putString("serverId", it) }
+ pendingUserId?.let { map.putString("userId", it) }
+ pendingChannelId = null
+ pendingServerId = null
+ pendingUserId = null
+ promise.resolve(map)
+ } else {
+ promise.resolve(null)
+ }
+ }
+
+ private fun buildExpandedRemoteViews(
+ channelId: String,
+ title: String,
+ isSingleEmoji: Boolean
+ ): RemoteViews {
+ val packageName = reactApplicationContext.packageName
+ val expandedView = RemoteViews(packageName, R.layout.notification_emoji_content)
+ expandedView.setTextViewText(R.id.notification_title, fromHtml(title))
+ expandedView.removeAllViews(R.id.notification_body_row)
+
+ val history = messageHistory[channelId] ?: return expandedView
+
+ for (entry in history) {
+ val segments = splitBodyIntoSegments(entry.body, entry.emojiBitmaps.keys)
+ val unicodeSegments = splitBodyIntoSegments(entry.unicodeBody, entry.emojiBitmaps.keys)
+ val singleEmoji = isSingleEmoji && history.size == 1
+
+ // each message gets own horizontal row
+ val rowView = RemoteViews(packageName, R.layout.notification_message_row)
+ rowView.removeAllViews(R.id.message_row_content)
+
+ for (i in segments.indices) {
+ val segment = segments[i]
+ if (segment.isEmoji && entry.emojiBitmaps.containsKey(segment.content)) {
+ val segmentLayoutId = if (singleEmoji) R.layout.notification_emoji_segment_large else R.layout.notification_emoji_segment
+ val emojiView = RemoteViews(packageName, segmentLayoutId)
+ emojiView.setImageViewBitmap(R.id.segment_emoji, entry.emojiBitmaps[segment.content])
+ rowView.addView(R.id.message_row_content, emojiView)
+ } else if (segment.content.isNotEmpty()) {
+ val textContent = if (i < unicodeSegments.size && !unicodeSegments[i].isEmoji) unicodeSegments[i].content else segment.content
+ val textView = RemoteViews(packageName, R.layout.notification_text_segment)
+ textView.setTextViewText(R.id.segment_text, fromHtml(textContent))
+ rowView.addView(R.id.message_row_content, textView)
+ }
+ }
+
+ expandedView.addView(R.id.notification_body_row, rowView)
+ }
+
+ return expandedView
+ }
+
+ private fun buildBodyRemoteViews(
+ layoutId: Int,
+ segments: List,
+ emojiBitmaps: Map,
+ isSingleEmoji: Boolean
+ ): RemoteViews {
+ val packageName = reactApplicationContext.packageName
+ val contentView = RemoteViews(packageName, layoutId)
+
+ contentView.removeAllViews(R.id.notification_body_row)
+
+ for (segment in segments) {
+ if (segment.isEmoji && emojiBitmaps.containsKey(segment.content)) {
+ val segmentLayoutId = if (isSingleEmoji) R.layout.notification_emoji_segment_large else R.layout.notification_emoji_segment
+ val emojiView = RemoteViews(packageName, segmentLayoutId)
+ emojiView.setImageViewBitmap(R.id.segment_emoji, emojiBitmaps[segment.content])
+ contentView.addView(R.id.notification_body_row, emojiView)
+ } else if (segment.content.isNotEmpty()) {
+ val textView = RemoteViews(packageName, R.layout.notification_text_segment)
+ textView.setTextViewText(R.id.segment_text, fromHtml(segment.content))
+ contentView.addView(R.id.notification_body_row, textView)
+ }
+ }
+
+ return contentView
+ }
+
+ private fun splitBodyIntoSegments(
+ body: String,
+ placeholders: Set
+ ): List {
+ if (placeholders.isEmpty()) {
+ return listOf(Segment(body, false))
+ }
+
+ val segments = mutableListOf()
+ var remaining = body
+
+ while (remaining.isNotEmpty()) {
+ var earliestIndex = Int.MAX_VALUE
+ var earliestPlaceholder = ""
+
+ for (placeholder in placeholders) {
+ val index = remaining.indexOf(placeholder)
+ if (index in 0 until earliestIndex) {
+ earliestIndex = index
+ earliestPlaceholder = placeholder
+ }
+ }
+
+ if (earliestIndex == Int.MAX_VALUE) {
+ segments.add(Segment(remaining, false))
+ break
+ }
+
+ if (earliestIndex > 0) {
+ segments.add(Segment(remaining.substring(0, earliestIndex), false))
+ }
+
+ segments.add(Segment(earliestPlaceholder, true))
+ remaining = remaining.substring(earliestIndex + earliestPlaceholder.length)
+ }
+
+ return segments
+ }
+
+ private fun generateLetterAvatar(letter: String, hexColor: String): Bitmap {
+ val size = 128
+ val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bitmap)
+
+ val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ bgPaint.color = try {
+ android.graphics.Color.parseColor(hexColor)
+ } catch (_: Exception) {
+ android.graphics.Color.parseColor("#7c7c7c")
+ }
+ canvas.drawOval(RectF(0f, 0f, size.toFloat(), size.toFloat()), bgPaint)
+
+ val textPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ textPaint.color = android.graphics.Color.WHITE
+ textPaint.textSize = size * 0.45f
+ textPaint.textAlign = Paint.Align.CENTER
+ textPaint.typeface = android.graphics.Typeface.DEFAULT_BOLD
+
+ val textBounds = android.graphics.Rect()
+ textPaint.getTextBounds(letter, 0, letter.length, textBounds)
+ val y = size / 2f + textBounds.height() / 2f
+
+ canvas.drawText(letter, size / 2f, y, textPaint)
+ return bitmap
+ }
+
+ private fun downloadBitmap(url: String): Bitmap? {
+ val connection = URL(url).openConnection()
+ connection.connectTimeout = 5000
+ connection.readTimeout = 5000
+ val bytes = connection.getInputStream().use { it.readBytes() }
+ return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
+ }
+
+ private fun makeCircular(bitmap: Bitmap): Bitmap {
+ val size = minOf(bitmap.width, bitmap.height)
+ val output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(output)
+ val paint = Paint(Paint.ANTI_ALIAS_FLAG)
+ val rect = RectF(0f, 0f, size.toFloat(), size.toFloat())
+ canvas.drawOval(rect, paint)
+ paint.xfermode = android.graphics.PorterDuffXfermode(android.graphics.PorterDuff.Mode.SRC_IN)
+ canvas.drawBitmap(bitmap, null, rect, paint)
+ return output
+ }
+
+ @Suppress("DEPRECATION")
+ private fun fromHtml(html: String): Spanned {
+ return android.text.Html.fromHtml(html, android.text.Html.FROM_HTML_MODE_COMPACT)
+ }
+
+ private fun replaceEmojiPlaceholdersWithUnicode(text: String, placeholders: Set): String {
+ return text
+ }
+
+ private data class EmojiInfo(val placeholder: String, val url: String)
+ private data class Segment(val content: String, val isEmoji: Boolean)
+}
diff --git a/android/app/src/main/java/com/nerimityreactnative/custom/EmojiNotificationPackage.kt b/android/app/src/main/java/com/nerimityreactnative/custom/EmojiNotificationPackage.kt
new file mode 100644
index 0000000..fb768c7
--- /dev/null
+++ b/android/app/src/main/java/com/nerimityreactnative/custom/EmojiNotificationPackage.kt
@@ -0,0 +1,16 @@
+package com.nerimityreactnative.custom
+
+import com.facebook.react.ReactPackage
+import com.facebook.react.bridge.NativeModule
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.uimanager.ViewManager
+
+class EmojiNotificationPackage : ReactPackage {
+ override fun createNativeModules(reactContext: ReactApplicationContext): List {
+ return listOf(EmojiNotificationModule(reactContext))
+ }
+
+ override fun createViewManagers(reactContext: ReactApplicationContext): List> {
+ return emptyList()
+ }
+}
diff --git a/android/app/src/main/java/com/nerimityreactnative/custom/NotificationDismissReceiver.kt b/android/app/src/main/java/com/nerimityreactnative/custom/NotificationDismissReceiver.kt
new file mode 100644
index 0000000..3b7816b
--- /dev/null
+++ b/android/app/src/main/java/com/nerimityreactnative/custom/NotificationDismissReceiver.kt
@@ -0,0 +1,16 @@
+package com.nerimityreactnative.custom
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+
+class NotificationDismissReceiver : BroadcastReceiver() {
+ companion object {
+ const val EXTRA_CHANNEL_ID = "notification_channel_id"
+ }
+
+ override fun onReceive(context: Context, intent: Intent) {
+ val channelId = intent.getStringExtra(EXTRA_CHANNEL_ID) ?: return
+ EmojiNotificationModule.clearHistory(channelId)
+ }
+}
diff --git a/android/app/src/main/res/layout/notification_emoji_collapsed.xml b/android/app/src/main/res/layout/notification_emoji_collapsed.xml
new file mode 100644
index 0000000..899ff6f
--- /dev/null
+++ b/android/app/src/main/res/layout/notification_emoji_collapsed.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/notification_emoji_content.xml b/android/app/src/main/res/layout/notification_emoji_content.xml
new file mode 100644
index 0000000..5ac3732
--- /dev/null
+++ b/android/app/src/main/res/layout/notification_emoji_content.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/notification_emoji_segment.xml b/android/app/src/main/res/layout/notification_emoji_segment.xml
new file mode 100644
index 0000000..ccde100
--- /dev/null
+++ b/android/app/src/main/res/layout/notification_emoji_segment.xml
@@ -0,0 +1,8 @@
+
+
diff --git a/android/app/src/main/res/layout/notification_emoji_segment_large.xml b/android/app/src/main/res/layout/notification_emoji_segment_large.xml
new file mode 100644
index 0000000..d3fadf8
--- /dev/null
+++ b/android/app/src/main/res/layout/notification_emoji_segment_large.xml
@@ -0,0 +1,6 @@
+
+
diff --git a/android/app/src/main/res/layout/notification_message_row.xml b/android/app/src/main/res/layout/notification_message_row.xml
new file mode 100644
index 0000000..12a982e
--- /dev/null
+++ b/android/app/src/main/res/layout/notification_message_row.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/android/app/src/main/res/layout/notification_text_segment.xml b/android/app/src/main/res/layout/notification_text_segment.xml
new file mode 100644
index 0000000..9dc625c
--- /dev/null
+++ b/android/app/src/main/res/layout/notification_text_segment.xml
@@ -0,0 +1,6 @@
+
+
diff --git a/android/app/src/main/res/layout/notification_text_segment_single.xml b/android/app/src/main/res/layout/notification_text_segment_single.xml
new file mode 100644
index 0000000..120bff9
--- /dev/null
+++ b/android/app/src/main/res/layout/notification_text_segment_single.xml
@@ -0,0 +1,9 @@
+
+
diff --git a/package-lock.json b/package-lock.json
index aad83b6..bb35550 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -195,13 +195,14 @@
}
},
"node_modules/@babel/code-frame": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
- "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
"license": "MIT",
"dependencies": {
- "@babel/highlight": "^7.24.7",
- "picocolors": "^1.0.0"
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
@@ -553,18 +554,18 @@
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz",
- "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
- "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -595,38 +596,26 @@
}
},
"node_modules/@babel/helpers": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz",
- "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
"license": "MIT",
"dependencies": {
- "@babel/template": "^7.24.7",
- "@babel/types": "^7.24.7"
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
- "node_modules/@babel/highlight": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
- "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
+ "node_modules/@babel/parser": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.24.7",
- "chalk": "^2.4.2",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.0.0"
+ "@babel/types": "^7.29.0"
},
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz",
- "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==",
- "license": "MIT",
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -2277,26 +2266,23 @@
"license": "MIT"
},
"node_modules/@babel/runtime": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz",
- "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
+ "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
"license": "MIT",
- "dependencies": {
- "regenerator-runtime": "^0.14.0"
- },
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz",
- "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.24.7",
- "@babel/parser": "^7.24.7",
- "@babel/types": "^7.24.7"
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -2324,14 +2310,13 @@
}
},
"node_modules/@babel/types": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz",
- "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.24.7",
- "@babel/helper-validator-identifier": "^7.24.7",
- "to-fast-properties": "^2.0.0"
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
@@ -2424,9 +2409,9 @@
"license": "Python-2.0"
},
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2451,9 +2436,9 @@
}
},
"node_modules/@eslint/eslintrc/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2531,9 +2516,9 @@
}
},
"node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5389,9 +5374,9 @@
}
},
"node_modules/@react-native/community-cli-plugin/node_modules/image-size": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz",
- "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz",
+ "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==",
"license": "MIT",
"dependencies": {
"queue": "6.0.2"
@@ -6123,9 +6108,9 @@
}
},
"node_modules/@react-native/metro-config/node_modules/image-size": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz",
- "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz",
+ "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7794,9 +7779,9 @@
}
},
"node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7887,9 +7872,9 @@
"license": "MIT"
},
"node_modules/bytes": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
- "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
@@ -7915,6 +7900,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/caller-callsite": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
@@ -7987,29 +7985,6 @@
],
"license": "CC-BY-4.0"
},
- "node_modules/chalk": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
- "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^3.2.1",
- "escape-string-regexp": "^1.0.5",
- "supports-color": "^5.3.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/chalk/node_modules/escape-string-regexp": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
- "license": "MIT",
- "engines": {
- "node": ">=0.8.0"
- }
- },
"node_modules/char-regex": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
@@ -8259,17 +8234,17 @@
}
},
"node_modules/compression": {
- "version": "1.7.4",
- "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
- "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
+ "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"license": "MIT",
"dependencies": {
- "accepts": "~1.3.5",
- "bytes": "3.0.0",
- "compressible": "~2.0.16",
+ "bytes": "3.1.2",
+ "compressible": "~2.0.18",
"debug": "2.6.9",
- "on-headers": "~1.0.2",
- "safe-buffer": "5.1.2",
+ "negotiator": "~0.6.4",
+ "on-headers": "~1.1.0",
+ "safe-buffer": "5.2.1",
"vary": "~1.1.2"
},
"engines": {
@@ -8291,6 +8266,35 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
+ "node_modules/compression/node_modules/negotiator": {
+ "version": "0.6.4",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
+ "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/compression/node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -8512,9 +8516,9 @@
}
},
"node_modules/cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@@ -8844,6 +8848,20 @@
"url": "https://dotenvx.com"
}
},
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -9009,14 +9027,10 @@
}
},
"node_modules/es-define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
- "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
- "dev": true,
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
- "dependencies": {
- "get-intrinsic": "^1.2.4"
- },
"engines": {
"node": ">= 0.4"
}
@@ -9025,7 +9039,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -9058,10 +9071,9 @@
}
},
"node_modules/es-object-atoms": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
- "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
- "dev": true,
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -9071,15 +9083,15 @@
}
},
"node_modules/es-set-tostringtag": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
- "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
- "dev": true,
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
- "get-intrinsic": "^1.2.4",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
- "hasown": "^2.0.1"
+ "hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
@@ -9369,9 +9381,9 @@
"license": "MIT"
},
"node_modules/eslint-plugin-react/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9468,9 +9480,9 @@
"license": "Python-2.0"
},
"node_modules/eslint/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9602,9 +9614,9 @@
}
},
"node_modules/eslint/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9898,22 +9910,18 @@
"license": "MIT"
},
"node_modules/fast-xml-parser": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz",
- "integrity": "sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==",
+ "version": "4.5.3",
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz",
+ "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
- },
- {
- "type": "paypal",
- "url": "https://paypal.me/naturalintelligence"
}
],
"license": "MIT",
"dependencies": {
- "strnum": "^1.0.5"
+ "strnum": "^1.1.1"
},
"bin": {
"fxparser": "src/cli/cli.js"
@@ -10195,14 +10203,16 @@
}
},
"node_modules/form-data": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"peer": true,
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -10309,17 +10319,21 @@
}
},
"node_modules/get-intrinsic": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
- "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
- "dev": true,
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
- "has-proto": "^1.0.1",
- "has-symbols": "^1.0.3",
- "hasown": "^2.0.0"
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
@@ -10423,6 +10437,19 @@
"node": ">=8.0.0"
}
},
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
@@ -10488,9 +10515,9 @@
}
},
"node_modules/glob/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
@@ -10556,13 +10583,12 @@
}
},
"node_modules/gopd": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
- "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
- "dev": true,
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
- "dependencies": {
- "get-intrinsic": "^1.1.3"
+ "engines": {
+ "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -10591,15 +10617,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/has-flag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/has-property-descriptors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
@@ -10627,10 +10644,9 @@
}
},
"node_modules/has-symbols": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
- "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
- "dev": true,
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -10643,7 +10659,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
@@ -10743,25 +10758,29 @@
"license": "MIT"
},
"node_modules/http-errors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
- "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
- "depd": "2.0.0",
- "inherits": "2.0.4",
- "setprototypeof": "1.2.0",
- "statuses": "2.0.1",
- "toidentifier": "1.0.1"
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/http-errors/node_modules/statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
@@ -13272,9 +13291,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"license": "MIT",
"dependencies": {
"argparse": "^1.0.7",
@@ -13642,9 +13661,9 @@
}
},
"node_modules/lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT"
},
"node_modules/lodash.debounce": {
@@ -13943,6 +13962,15 @@
"integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==",
"license": "Apache-2.0"
},
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/mem": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/mem/-/mem-5.1.1.tgz",
@@ -14949,9 +14977,9 @@
}
},
"node_modules/micromatch": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
- "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
@@ -15142,9 +15170,9 @@
}
},
"node_modules/node-dir/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
@@ -15184,9 +15212,9 @@
}
},
"node_modules/node-forge": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
- "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz",
+ "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==",
"license": "(BSD-3-Clause OR GPL-2.0)",
"engines": {
"node": ">= 6.13.0"
@@ -15385,9 +15413,9 @@
}
},
"node_modules/on-headers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
- "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
+ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
@@ -15801,9 +15829,9 @@
}
},
"node_modules/picocolors": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
- "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
},
"node_modules/picomatch": {
@@ -16802,9 +16830,9 @@
}
},
"node_modules/react-native-macos/node_modules/image-size": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz",
- "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz",
+ "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==",
"license": "MIT",
"peer": true,
"dependencies": {
@@ -18031,9 +18059,9 @@
"peer": true
},
"node_modules/react-native-windows/node_modules/image-size": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz",
- "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz",
+ "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==",
"license": "MIT",
"peer": true,
"dependencies": {
@@ -18869,12 +18897,6 @@
"node": ">=4"
}
},
- "node_modules/regenerator-runtime": {
- "version": "0.14.1",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
- "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
- "license": "MIT"
- },
"node_modules/regenerator-transform": {
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz",
@@ -19157,24 +19179,24 @@
}
},
"node_modules/send": {
- "version": "0.18.0",
- "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
- "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
- "encodeurl": "~1.0.2",
+ "encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
- "fresh": "0.5.2",
- "http-errors": "2.0.0",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
"mime": "1.6.0",
"ms": "2.1.3",
- "on-finished": "2.4.1",
+ "on-finished": "~2.4.1",
"range-parser": "~1.2.1",
- "statuses": "2.0.1"
+ "statuses": "~2.0.2"
},
"engines": {
"node": ">= 0.8.0"
@@ -19195,6 +19217,15 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
+ "node_modules/send/node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/send/node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@@ -19226,9 +19257,9 @@
}
},
"node_modules/send/node_modules/statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
@@ -19244,20 +19275,29 @@
}
},
"node_modules/serve-static": {
- "version": "1.15.0",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
- "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
"license": "MIT",
"dependencies": {
- "encodeurl": "~1.0.2",
+ "encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
- "send": "0.18.0"
+ "send": "~0.19.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
+ "node_modules/serve-static/node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -19722,9 +19762,15 @@
}
},
"node_modules/strnum": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
- "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz",
+ "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
"license": "MIT"
},
"node_modules/sudo-prompt": {
@@ -19781,18 +19827,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/supports-color": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
- "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "license": "MIT",
- "dependencies": {
- "has-flag": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
@@ -19889,9 +19923,9 @@
}
},
"node_modules/test-exclude/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -19971,15 +20005,6 @@
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
"license": "BSD-3-Clause"
},
- "node_modules/to-fast-properties": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
- "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -20335,9 +20360,9 @@
}
},
"node_modules/username/node_modules/cross-spawn": {
- "version": "6.0.5",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
- "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "version": "6.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz",
+ "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==",
"license": "MIT",
"peer": true,
"dependencies": {
diff --git a/src/pushNotifications.ts b/src/pushNotifications.ts
index 591823f..1838467 100644
--- a/src/pushNotifications.ts
+++ b/src/pushNotifications.ts
@@ -1,13 +1,12 @@
-import notifee, {
- AndroidInboxStyle,
- AndroidStyle,
- AndroidVisibility,
-} from '@notifee/react-native';
+import notifee from '@notifee/react-native';
+import {NativeModules} from 'react-native';
import env from './env';
import {getUserId} from './EncryptedStore';
import {dmChannelMatch, serverChannelMatch} from './UrlPatternMatchers';
import {currentUrl} from './components/CustomWebView';
+const {EmojiNotificationModule} = NativeModules;
+
export enum MessageType {
CONTENT = 0,
JOIN_SERVER = 1,
@@ -80,24 +79,13 @@ export async function handlePushNotification(
}
export async function showServerPushNotification(data: ServerNotificationData) {
- const existingNotification = await notifee
- .getDisplayedNotifications()
- .then(res => res.find(n => n.notification.id === data.channelId));
-
- const selfUserId =
- existingNotification?.notification?.data?.selfUserId || (await getUserId());
+ const selfUserId = await getUserId();
if (selfUserId === data.cUserId) {
return;
}
- const existingLines =
- (existingNotification?.notification?.android?.style as AndroidInboxStyle)
- ?.lines || [];
-
const creatorName = sanitize(data.cName);
-
- const username = `${creatorName}:`;
let content = data.content;
// lets assume its an image message
@@ -123,58 +111,39 @@ export async function showServerPushNotification(data: ServerNotificationData) {
content = 'has started a call.';
}
- let newLines = [username, content];
+ let emojis: {placeholder: string; url: string}[] = [];
+ if (type === MessageType.CONTENT && data.content) {
+ const result = replaceCustomEmojisWithPlaceholders(data.content);
+ content = formatMarkup(sanitize(result.body));
+ emojis = result.emojis;
+ }
+
+ const bodyText = `${creatorName}: ${content}`;
- // Display a notification
- await notifee.displayNotification({
+ EmojiNotificationModule.displayNotification({
id: data.channelId,
- title: `${sanitize(data.serverName)} #${sanitize(data.channelName)}`,
- body: newLines.join(' '),
- data: {
- selfUserId: selfUserId!,
- channelId: data.channelId,
- serverId: data.serverId,
- userId: data.cUserId,
- },
- android: {
- smallIcon: 'ic_stat_notify',
- pressAction: {
- id: 'default',
- },
- groupId: Math.random().toString(),
- visibility: AndroidVisibility.PUBLIC,
- circularLargeIcon: true,
- channelId: ANDROID_CHANNELS.serverMessages,
- ...(data.sAvatar
- ? {largeIcon: `${env.NERIMITY_CDN}${data.sAvatar}`}
- : undefined),
- style: {
- type: AndroidStyle.INBOX,
- title: `${sanitize(data.serverName)} #${sanitize(
- data.channelName,
- )}`,
- lines: [...existingLines, ...newLines].slice(-5),
- },
- },
+ title: `${sanitize(data.serverName)} | #${sanitize(data.channelName)}`,
+ body: bodyText,
+ emojis,
+ channelId: ANDROID_CHANNELS.serverMessages,
+ subText: sanitize(data.serverName),
+ largeIcon: data.sAvatar
+ ? `${env.NERIMITY_CDN}${data.sAvatar}`
+ : null,
+ circularLargeIcon: true,
+ fallbackAvatarLetter: data.serverName?.charAt(0)?.toUpperCase() || '?',
+ fallbackAvatarColor: data.sHexColor || '#7c7c7c',
+ serverId: data.serverId,
});
}
export async function showDMNotificationData(data: DMNotificationData) {
- const existingNotification = await notifee
- .getDisplayedNotifications()
- .then(res => res.find(n => n.notification.id === data.channelId));
-
- const selfUserId =
- existingNotification?.notification?.data?.selfUserId || (await getUserId());
+ const selfUserId = await getUserId();
if (selfUserId === data.cUserId) {
return;
}
- const existingLines =
- (existingNotification?.notification?.android?.style as AndroidInboxStyle)
- ?.lines || [];
-
let newLine = sanitize(data.content);
// lets assume its an image message
@@ -187,34 +156,27 @@ export async function showDMNotificationData(data: DMNotificationData) {
newLine = 'has started a call.';
}
- // Display a notification
- await notifee.displayNotification({
+ let emojis: {placeholder: string; url: string}[] = [];
+ if (type === MessageType.CONTENT && data.content) {
+ const result = replaceCustomEmojisWithPlaceholders(data.content);
+ newLine = formatMarkup(sanitize(result.body));
+ emojis = result.emojis;
+ }
+
+ EmojiNotificationModule.displayNotification({
id: data.channelId,
- title: `@${sanitize(data.cName)}`,
- body: newLine,
- data: {
- selfUserId: selfUserId!,
- channelId: data.channelId,
- userId: data.cUserId,
- },
- android: {
- smallIcon: 'ic_stat_notify',
- pressAction: {
- id: 'default',
- },
- groupId: Math.random().toString(),
- visibility: AndroidVisibility.PUBLIC,
- circularLargeIcon: true,
- channelId: ANDROID_CHANNELS.dmMessages,
- ...(data.uAvatar
- ? {largeIcon: `${env.NERIMITY_CDN}${data.uAvatar}`}
- : undefined),
- style: {
- type: AndroidStyle.INBOX,
- title: `@${sanitize(data.cName)}`,
- lines: [...existingLines, newLine].slice(-5),
- },
- },
+ title: `${sanitize(data.cName)}`,
+ body: `${sanitize(data.cName)}: ${newLine}`,
+ emojis,
+ channelId: ANDROID_CHANNELS.dmMessages,
+ subText: 'Direct',
+ largeIcon: data.uAvatar
+ ? `${env.NERIMITY_CDN}${data.uAvatar}`
+ : null,
+ circularLargeIcon: true,
+ fallbackAvatarLetter: data.cName?.charAt(0)?.toUpperCase() || '?',
+ fallbackAvatarColor: data.uHexColor || '#7c7c7c',
+ userId: data.cUserId,
});
}
@@ -232,10 +194,84 @@ function sanitize(string?: string) {
'&': '&',
'<': '<',
'>': '>',
- '"': '"',
- "'": ''',
- '/': '/',
} as const;
- const reg = /[&<>"'/]/gi;
+ const reg = /[&<>]/g;
return string.replace(reg, match => map[match as keyof typeof map]);
}
+
+function formatMarkup(text: string): string {
+ // headers: # to ###### => bold
+ text = text.replace(/^#{1,6}\s+(.+)$/gm, '$1');
+ // spoiler: ||text|| => [spoiler]
+ text = text.replace(/\|\|(.+?)\|\|/g, '[spoiler]');
+ // bold+italic: ***text*** or ___text___
+ text = text.replace(/\*\*\*(.*?)\*\*\*/g, '$1');
+ text = text.replace(/___(.*?)___/g, '$1');
+ // bold: **text**
+ text = text.replace(/\*\*(.*?)\*\*/g, '$1');
+ // ttalic: *text* or _text_
+ text = text.replace(/\*([^*]+)\*/g, '$1');
+ text = text.replace(/(?$1');
+ // strikethrough: ~~text~~
+ text = text.replace(/~~(.*?)~~/g, '$1');
+ // link: [text](url) => text
+ text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
+ // checkbox: -[ ] or -[x]
+ text = text.replace(/-\[ \]/g, '☐');
+ text = text.replace(/-\[x\]/g, '☑');
+ // timestamp: [tr:1234567] => [timestamp]
+ text = text.replace(/\[tr:\d+\]/g, '[timestamp]');
+ // color: [#hex]text => text
+ text = text.replace(/\[#[0-9a-fA-F]{3,8}\]/g, '');
+ return text;
+}
+
+export function getPendingNotificationClick(): Promise<{
+ channelId: string;
+ serverId?: string;
+ userId?: string;
+} | null> {
+ if (!EmojiNotificationModule?.getPendingNotificationClick) {
+ return Promise.resolve(null);
+ }
+ return EmojiNotificationModule.getPendingNotificationClick();
+}
+
+const CUSTOM_EMOJI_REGEX = /\[(?:w?a)?ce:(\d+):([^\]]+)\]/g;
+
+function getCustomEmojiUrl(emojiId: string, type: string): string {
+ const ext = type === 'ace' ? 'gif' : 'webp';
+ return `${env.NERIMITY_CDN}emojis/${emojiId}.${ext}`;
+}
+
+function extractAllCustomEmojis(
+ content: string,
+): {id: string; name: string; type: string}[] {
+ const results: {id: string; name: string; type: string}[] = [];
+ const regex = /\[((?:w?a)?ce):(\d+):([^\]]+)\]/g;
+ let match;
+ while ((match = regex.exec(content)) !== null) {
+ results.push({id: match[2], name: match[3], type: match[1]});
+ }
+ return results;
+}
+
+function replaceCustomEmojisWithPlaceholders(content: string): {
+ body: string;
+ emojis: {placeholder: string; url: string}[];
+} {
+ const emojis: {placeholder: string; url: string}[] = [];
+ const seen = new Set();
+ const allEmojis = extractAllCustomEmojis(content);
+
+ for (const emoji of allEmojis) {
+ const placeholder = `:${emoji.name}:`;
+ if (!seen.has(emoji.id)) {
+ seen.add(emoji.id);
+ emojis.push({placeholder, url: getCustomEmojiUrl(emoji.id, emoji.type)});
+ }
+ }
+
+ const body = content.replace(CUSTOM_EMOJI_REGEX, ':$2:');
+ return {body, emojis};
+}