Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@ import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.Metadata
import androidx.media3.common.util.UnstableApi
import androidx.media3.extractor.metadata.id3.BinaryFrame
import androidx.media3.extractor.metadata.id3.TextInformationFrame
import androidx.media3.extractor.metadata.vorbis.VorbisComment
import androidx.media3.inspector.MetadataRetriever
import com.cyanchill.missingcore.metadataretriever.NativeMetadataRetrieverSpec
import com.cyanchill.missingcore.metadataretriever.models.ArtworkOptions
import com.cyanchill.missingcore.metadataretriever.models.BridgeReturnables.*
import com.cyanchill.missingcore.metadataretriever.utils.LyricsParser
import com.cyanchill.missingcore.metadataretriever.utils.MapUtils
import com.cyanchill.missingcore.metadataretriever.utils.Normalization
import com.cyanchill.missingcore.metadataretriever.utils.ReplayGainParser
import com.cyanchill.missingcore.metadataretriever.utils.safeExecuteOnURI
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
Expand Down Expand Up @@ -169,7 +168,7 @@ class MetadataRetrieverModule(reactContext: ReactApplicationContext) :
val artworkOptions = ArtworkOptions(options)
val asBase64 = artworkOptions.asBase64

try {
safeExecuteOnURI(uri, "ERR_ARTWORK", promise) {
val metadataList = getMetadataList(getFormatList(uri))

// We'll want to return the image designated as "Cover (front)", otherwise return first image found.
Expand Down Expand Up @@ -216,95 +215,35 @@ class MetadataRetrieverModule(reactContext: ReactApplicationContext) :
val imgUri = (coverImage ?: backupImage)?.let { reader.saveImage(it as ByteArray, artworkOptions) }
promise.resolve(imgUri)
}
} catch (e: ExecutionException) {
val isWantedException =
e.message?.contains("androidx.media3.datasource.FileDataSource\$FileDataSourceException")
?: false
when (isWantedException) {
true -> promise.reject("ENOENT", "ENOENT: No such file or directory (${uri})", e)
false -> promise.reject("ERR_ARTWORK", e.message, e)
}
} catch (e: Exception) {
promise.reject("ERR_ARTWORK", e.message, e)
}
}

/** Returns embedded lyrics in supported "lyrics" tags. Prefers returning synchronized lyrics. */
override fun getLyric(uri: String, promise: Promise) {
try {
safeExecuteOnURI(uri, "ERR_LYRIC", promise) {
val metadataList = getMetadataList(getFormatList(uri))

var lyricsStr: String? = null
var isLyricsSync = false
var parsedLyrics: LyricsParser? = null

for (metadata in metadataList) {
val numEntries = metadata.length()
// Manually iterate over metadata entries to find a supported key.
for (i in 0 until numEntries) {
val metadataEntry = metadata[i]

if (metadataEntry is VorbisComment && metadataEntry.key.uppercase() == "LYRICS") {
lyricsStr = metadataEntry.value
isLyricsSync = true
} else if (
(metadataEntry is TextInformationFrame || metadataEntry is BinaryFrame) &&
metadataEntry.id.uppercase() in ID3v2_LYRIC_TAGS
) {
lyricsStr = when (metadataEntry) {
is TextInformationFrame -> metadataEntry.values.firstOrNull()
is BinaryFrame -> {
val byteArr = metadataEntry.data
// The 1st byte in the array determines the encoding in ID3.
// - Mp3Tag doesn't specify a Byte Order Mark if it's `1` (UTF-16), so we'll do a heuristic guess for what charset to use.
// - Ref: https://mutagen-specs.readthedocs.io/en/latest/id3/id3v2.4.0-structure.html#id3v2-frame-overview
val encodingCharSet = when (byteArr[0].toString()) {
"0" -> Charsets.ISO_8859_1
"1" -> {
if (byteArr[1] == BYTE_0xFE && byteArr[2] == BYTE_0xFF) {
Charsets.UTF_16BE
} else if (byteArr[1] == BYTE_0xFF && byteArr[2] == BYTE_0xFE) {
Charsets.UTF_16LE
} else {
// Heuristic guess of byte order using new line character.
// - If the conversion contains a newline character, that charset should be used.
if (String(byteArr, Charsets.UTF_16LE).contains("\n")) Charsets.UTF_16LE
else Charsets.UTF_16BE
}
}
"2" -> Charsets.UTF_16BE
else -> Charsets.UTF_8
}
String(byteArr, encodingCharSet)
}
else -> null
}
isLyricsSync = metadataEntry.id.uppercase() === "SYLT"
val lyricsCandidate = LyricsParser(metadata[i])
if (lyricsCandidate.lyrics != null) {
parsedLyrics = lyricsCandidate
if (parsedLyrics.isSync) break
}

if (isLyricsSync) break
}

if (isLyricsSync) break
if (parsedLyrics?.isSync == true) break
}

if (lyricsStr !== null) lyricsStr = lyricsStr.replace("\u0000", "")

promise.resolve(lyricsStr)
} catch (e: ExecutionException) {
val isWantedException =
e.message?.contains("androidx.media3.datasource.FileDataSource\$FileDataSourceException")
?: false
when (isWantedException) {
true -> promise.reject("ENOENT", "ENOENT: No such file or directory (${uri})", e)
false -> promise.reject("ERR_LYRIC", e.message, e)
}
} catch (e: Exception) {
promise.reject("ERR_LYRIC", e.message, e)
promise.resolve(parsedLyrics?.lyrics)
}
}

override fun getR128Gain(uri: String, promise: Promise) {
try {
safeExecuteOnURI(uri, "ERR_REPLAY_GAIN", promise) {
val metadataList = getMetadataList(getFormatList(uri))
var gain: Float? = null

Expand All @@ -320,16 +259,6 @@ class MetadataRetrieverModule(reactContext: ReactApplicationContext) :
}

promise.resolve(gain)
} catch (e: ExecutionException) {
val isWantedException =
e.message?.contains("androidx.media3.datasource.FileDataSource\$FileDataSourceException")
?: false
when (isWantedException) {
true -> promise.reject("ENOENT", "ENOENT: No such file or directory (${uri})", e)
false -> promise.reject("ERR_REPLAY_GAIN", e.message, e)
}
} catch (e: Exception) {
promise.reject("ERR_REPLAY_GAIN", e.message, e)
}
}

Expand All @@ -344,7 +273,7 @@ class MetadataRetrieverModule(reactContext: ReactApplicationContext) :
val formatStrArr = Arguments.createArray()
val metadataStrArr = Arguments.createArray()

try {
safeExecuteOnURI(uri, "ERR_DEBUG", promise) {
val formatList = getFormatList(uri)
formatList.forEach { item -> formatStrArr.pushString(item.toString()) }

Expand All @@ -354,16 +283,6 @@ class MetadataRetrieverModule(reactContext: ReactApplicationContext) :
returnObj.putArray("format", formatStrArr)
returnObj.putArray("metadata", metadataStrArr)
promise.resolve(returnObj)
} catch (e: ExecutionException) {
val isWantedException =
e.message?.contains("androidx.media3.datasource.FileDataSource\$FileDataSourceException")
?: false
when (isWantedException) {
true -> promise.reject("ENOENT", "ENOENT: No such file or directory (${uri})", e)
false -> promise.reject("ERR_DEBUG", e.message, e)
}
} catch (e: Exception) {
promise.reject("ERR_DEBUG", e.message, e)
}
}

Expand Down Expand Up @@ -406,10 +325,5 @@ class MetadataRetrieverModule(reactContext: ReactApplicationContext) :

companion object {
const val NAME = NativeMetadataRetrieverSpec.NAME

// We'll only support embedded lyrics in ID3v2.3+ tags.
private val ID3v2_LYRIC_TAGS = listOf("SYLT", "USLT")
private const val BYTE_0xFE = 0xFE.toByte()
private const val BYTE_0xFF = 0xFF.toByte()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.cyanchill.missingcore.metadataretriever.utils

import androidx.annotation.OptIn
import androidx.media3.common.Metadata
import androidx.media3.common.util.UnstableApi
import androidx.media3.extractor.metadata.id3.BinaryFrame
import androidx.media3.extractor.metadata.id3.TextInformationFrame
import androidx.media3.extractor.metadata.vorbis.VorbisComment

@OptIn(UnstableApi::class)
class LyricsParser(entry: Metadata.Entry) {
var lyrics: String? = null
var isSync = false

init {
// Extra lyrics based on the class.
when (entry) {
is TextInformationFrame -> handleTextInformationFrame(entry)
is BinaryFrame -> handleBinaryFrame(entry)
is VorbisComment -> handleVorbisComment(entry)
}

// Sanitize input
lyrics = lyrics?.replace("\u0000", "")
}

private fun handleTextInformationFrame(frame: TextInformationFrame) {
val tagName = frame.id.uppercase()
if (tagName !in ID3v2_LYRIC_TAGS) return
lyrics = frame.values.firstOrNull()
isSync = tagName == "SYLT"
}

private fun handleBinaryFrame(frame: BinaryFrame) {
val tagName = frame.id.uppercase()
if (tagName !in ID3v2_LYRIC_TAGS) return

val byteArr = frame.data
// The 1st byte in the array determines the encoding in ID3.
// - Mp3Tag doesn't specify a Byte Order Mark if it's `1` (UTF-16), so we'll do a heuristic guess for what charset to use.
// - Ref: https://mutagen-specs.readthedocs.io/en/latest/id3/id3v2.4.0-structure.html#id3v2-frame-overview
val encodingCharSet = when (byteArr[0].toString()) {
"0" -> Charsets.ISO_8859_1
"1" -> {
if (byteArr[1] == BYTE_0xFE && byteArr[2] == BYTE_0xFF) {
Charsets.UTF_16BE
} else if (byteArr[1] == BYTE_0xFF && byteArr[2] == BYTE_0xFE) {
Charsets.UTF_16LE
} else {
// Heuristic guess of byte order using new line character.
// - If the conversion contains a newline character, that charset should be used.
if (String(byteArr, Charsets.UTF_16LE).contains("\n")) Charsets.UTF_16LE
Comment on lines +42 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard short binary frames before reading bytes 0/1/2.

A malformed or truncated lyric frame will throw ArrayIndexOutOfBoundsException here. Since getLyric now runs inside safeExecuteOnURI, one bad embedded tag turns the whole call into a rejected Promise instead of falling back to other entries or returning null.

Suggested fix
     val byteArr = frame.data
+    if (byteArr.isEmpty()) return
+
     // The 1st byte in the array determines the encoding in ID3.
@@
-      "1" -> {
-        if (byteArr[1] == BYTE_0xFE && byteArr[2] == BYTE_0xFF) {
+      "1" -> {
+        if (byteArr.size >= 3 && byteArr[1] == BYTE_0xFE && byteArr[2] == BYTE_0xFF) {
           Charsets.UTF_16BE
-        } else if (byteArr[1] == BYTE_0xFF && byteArr[2] == BYTE_0xFE) {
+        } else if (byteArr.size >= 3 && byteArr[1] == BYTE_0xFF && byteArr[2] == BYTE_0xFE) {
           Charsets.UTF_16LE
         } else {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@android/src/main/java/com/cyanchill/missingcore/metadataretriever/utils/LyricsParser.kt`
around lines 42 - 52, The code in LyricsParser (inside getLyric) reads
byteArr[0..2] without checking length, which can throw
ArrayIndexOutOfBoundsException for truncated lyric frames; add a guard that
checks byteArr.size (or length) before accessing indices 0,1,2 and handle short
frames by returning null (or a safe default) so parsing continues without
throwing inside safeExecuteOnURI; update the encodingCharSet selection (the when
branch that references byteArr[0], byteArr[1], byteArr[2]) to first verify
byteArr.size >= 3 and fall back to ISO_8859_1 or null when the guard fails.

else Charsets.UTF_16BE
}
}
"2" -> Charsets.UTF_16BE
else -> Charsets.UTF_8
}

lyrics = String(byteArr, encodingCharSet)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
isSync = tagName == "SYLT"
}

private fun handleVorbisComment(comment: VorbisComment) {
if (comment.key.uppercase() != "LYRICS") return
// We immediately bail out if we find lyrics in Vorbis Comments due to
// there being no specific tag for synchronized lyrics.
lyrics = comment.value
isSync = true
}

companion object {
private val ID3v2_LYRIC_TAGS = listOf("SYLT", "USLT")
private const val BYTE_0xFE = 0xFE.toByte()
private const val BYTE_0xFF = 0xFF.toByte()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.cyanchill.missingcore.metadataretriever.utils

import com.facebook.react.bridge.Promise
import java.util.concurrent.ExecutionException

/**
* Reusable try/catch wrapper for when we do something pertaining to a file URI. Code that executes
* in the "try" portion goes in the provided `block`.
*/
fun <T> safeExecuteOnURI(uri: String, errorKey: String, promise: Promise, block: () -> T) {
try {
block()
} catch (e: ExecutionException) {
val isWantedException =
e.message?.contains("androidx.media3.datasource.FileDataSource\$FileDataSourceException")
?: false
when (isWantedException) {
true -> promise.reject("ENOENT", "ENOENT: No such file or directory (${uri})", e)
false -> promise.reject(errorKey, e.message, e)
Comment on lines +13 to +19

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the Kotlin file around the reported lines
FILE="android/src/main/java/com/cyanchill/missingcore/metadataretriever/utils/SafeUtils.kt"
echo "=== File exists? ==="
ls -l "$FILE" || true
echo
echo "=== Kotlin snippet (lines 1-120) ==="
nl -ba "$FILE" | sed -n '1,120p'

# 2) Locate safeExecuteOnURI and related usage
echo
echo "=== Search for safeExecuteOnURI ==="
rg -n "safeExecuteOnURI" -S . || true

# 3) Find JS mapping where error.name is set/used
echo
echo "=== Search for error.name mapping ==="
rg -n "error\.name" -S src . || true

# 4) If src/index.ts exists, show relevant area around the cited lines
IDX="src/index.ts"
if [ -f "$IDX" ]; then
  echo
  echo "=== src/index.ts snippet (lines 1-120) ==="
  nl -ba "$IDX" | sed -n '1,160p'
  echo
  echo "=== src/index.ts around 27-60 ==="
  nl -ba "$IDX" | sed -n '20,80p'
else
  echo
  echo "=== No src/index.ts found at $IDX ==="
  ls -la src || true
fi

Repository: MissingCore/react-native-metadata-retriever

Length of output: 341


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="android/src/main/java/com/cyanchill/missingcore/metadataretriever/utils/SafeUtils.kt"
echo "=== Tool availability ==="
for t in rg grep awk sed cat ls head tail find xargs; do
  command -v "$t" >/dev/null 2>&1 && echo "$t: yes" || echo "$t: no"
done
echo

echo "=== File exists? ==="
ls -l "$FILE" || true
echo

echo "=== Kotlin snippet (lines 1-180) with line numbers (awk) ==="
awk 'NR>=1 && NR<=180 {printf "%5d\t%s\n", NR, $0}' "$FILE" | sed -n '1,180p'
echo

echo "=== Search for safeExecuteOnURI ==="
if command -v rg >/dev/null 2>&1; then
  rg -n "safeExecuteOnURI" -S . || true
else
  grep -RIn "safeExecuteOnURI" . || true
fi
echo

echo "=== Search for error.name mapping ==="
if command -v rg >/dev/null 2>&1; then
  rg -n "error\.name" -S src . || true
else
  grep -RIn "error\.name" src . || true
fi
echo

IDX="src/index.ts"
if [ -f "$IDX" ]; then
  echo "=== src/index.ts snippet (lines 1-220) with line numbers (awk) ==="
  awk 'NR>=1 && NR<=220 {printf "%5d\t%s\n", NR, $0}' "$IDX" | sed -n '1,220p'
  echo
  echo "=== src/index.ts around 20-90 ==="
  awk 'NR>=20 && NR<=90 {printf "%5d\t%s\n", NR, $0}' "$IDX"
else
  echo "=== No src/index.ts found at $IDX ==="
  ls -la src || true
fi

Repository: MissingCore/react-native-metadata-retriever

Length of output: 8914


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect MetadataRetrieverModule where safeExecuteOnURI is called and where promise.reject errors are handled
FILE1="android/src/main/java/com/cyanchill/missingcore/metadataretriever/modules/MetadataRetrieverModule.kt"
echo "=== Kotlin snippet: $FILE1 (lines 1-360) ==="
awk 'NR>=1 && NR<=360 {printf "%5d\t%s\n", NR, $0}' "$FILE1" | sed -n '1,360p'

# Search for promise.reject usage
echo
echo "=== Search for promise.reject in MetadataRetrieverModule.kt ==="
rg -n "promise\.reject" "$FILE1" || true

# Search for how errors are serialized back to JS (look for map/Bundle with keys like 'code'/'message'/'name'/'data'/'errors')
echo
echo "=== Search for errors array construction in TS ==="
# try find MetadataRetriever implementation
for f in src/MetadataRetriever.ts src/MetadataRetriever.* src/**/MetadataRetriever*.ts; do :; done
# find files containing "getBulkMetadata" and "result.errors"
rg -n "getBulkMetadata|result\.errors|errors\]|\berrors\b" -S src || true

echo
echo "=== Search for NativeError / error data shape in TS ==="
rg -n "Native|reject|code|message|data|error\.name|error\.code" -S src || true

Repository: MissingCore/react-native-metadata-retriever

Length of output: 21922


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Show src/types/GetMetadata.ts ==="
FILE="src/types/GetMetadata.ts"
ls -l "$FILE" || true
awk 'NR>=1 && NR<=120 {printf "%5d\t%s\n", NR, $0}' "$FILE" | sed -n '1,120p'
echo

echo "=== Show src/NativeMetadataRetriever.ts (top) ==="
FILE="src/NativeMetadataRetriever.ts"
awk 'NR>=1 && NR<=140 {printf "%5d\t%s\n", NR, $0}' "$FILE" | sed -n '1,140p'
echo

echo "=== Show src/MetadataRetriever.ts (top) ==="
FILE="src/MetadataRetriever.ts"
awk 'NR>=1 && NR<=160 {printf "%5d\t%s\n", NR, $0}' "$FILE" | sed -n '1,160p'
echo

echo "=== Search for Android promise.reject serialization ==="
# If there is a shared ErrorObject or mapping on native side.
rg -n "promise\.reject|ErrorObject|ResultObject\(" android/src/main/java -S || true
echo

echo "=== Show NativeMetadataRetrieverSpec.kt (top if exists) ==="
SPEC="android/src/main/java/com/cyanchill/missingcore/metadataretriever/NativeMetadataRetrieverSpec.java"
SPEC2="android/src/main/java/com/cyanchill/missingcore/metadataretriever/NativeMetadataRetrieverSpec.kt"
if [ -f "$SPEC2" ]; then
  awk 'NR>=1 && NR<=220 {printf "%5d\t%s\n", NR, $0}' "$SPEC2" | sed -n '1,220p'
elif [ -f "$SPEC" ]; then
  awk 'NR>=1 && NR<=220 {printf "%5d\t%s\n", NR, $0}' "$SPEC" | sed -n '1,220p'
else
  echo "Spec files not found at expected paths."
  # try find by name
  fd -t f "NativeMetadataRetrieverSpec.*" android/src/main/java -a || true
fi

Repository: MissingCore/react-native-metadata-retriever

Length of output: 6012


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="android/src/main/java/com/cyanchill/missingcore/metadataretriever/models/BridgeReturnables.kt"
echo "=== Show $FILE ==="
awk 'NR>=1 && NR<=200 {printf "%5d\t%s\n", NR, $0}' "$FILE" | sed -n '1,200p'
echo

# Ensure MetadataRetrieverModule already has brittle check; show that region
FILE2="android/src/main/java/com/cyanchill/missingcore/metadataretriever/modules/MetadataRetrieverModule.kt"
echo "=== Show getBulkMetadata catch region (lines ~120-170) ==="
awk 'NR>=120 && NR<=180 {printf "%5d\t%s\n", NR, $0}' "$FILE2" | sed -n '120,180p'
echo

# Search for promise rejection handling on JS side (ErrorObject / error mapping for rejected promises)
echo "=== Search TS for 'ENOENT' and for handling rejected native errors ==="
rg -n "ENOENT|ERR_" src . || true

Repository: MissingCore/react-native-metadata-retriever

Length of output: 2353


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== List src directory files (top 200) ==="
find src -maxdepth 2 -type f | sed -n '1,200p'

echo
echo "=== Search for getArtwork/getLyric/getR128Gain error handling ==="
rg -n "getArtwork|getLyric|getR128Gain|ENOENT|ERR_" src --hidden || true

echo
echo "=== Show src/MetadataRetriever.ts if it exists ==="
FILE="src/MetadataRetriever.ts"
if [ -f "$FILE" ]; then
  awk 'NR>=1 && NR<=200 {printf "%5d\t%s\n", NR, $0}' "$FILE"
else
  echo "No $FILE"
  ls -ლა src
fi

Repository: MissingCore/react-native-metadata-retriever

Length of output: 2342


Map ENOENT via the exception cause chain instead of ExecutionException.message substring matching.

  • android/src/main/java/com/cyanchill/missingcore/metadataretriever/utils/SafeUtils.kt decides ENOENT by checking e.message?.contains(...), which is brittle (and can be null), so missing files can be misclassified.
  • The same brittle substring check is also duplicated in android/src/main/java/com/cyanchill/missingcore/metadataretriever/modules/MetadataRetrieverModule.kt (getBulkMetadata’s catch (e: ExecutionException)), which directly impacts getMetadata() where src/index.ts sets error.name from result.errors[0].data.name (so ENOENT can become "ERR_METADATA").
Suggested direction
 } catch (e: ExecutionException) {
-  val isWantedException =
-    e.message?.contains("androidx.media3.datasource.FileDataSource\$FileDataSourceException")
-      ?: false
+  val isWantedException =
+    generateSequence(e as Throwable?) { it.cause }
+      .any { it?.javaClass?.name == "androidx.media3.datasource.FileDataSource\$FileDataSourceException" }
   when (isWantedException) {
     true -> promise.reject("ENOENT", "ENOENT: No such file or directory (${uri})", e)
     false -> promise.reject(errorKey, e.message, e)
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@android/src/main/java/com/cyanchill/missingcore/metadataretriever/utils/SafeUtils.kt`
around lines 13 - 19, The ENOENT detection is brittle because it inspects
ExecutionException.message; instead, traverse the exception cause chain
(e.cause, cause.cause, ...) and check each cause's runtime class name or
qualifiedName for
"androidx.media3.datasource.FileDataSource$FileDataSourceException" (or use
instanceof if you can import the class) to decide ENOENT; update the catch
blocks in SafeUtils.kt (the ExecutionException handler that currently calls
promise.reject("ENOENT", ...)) and the catch in MetadataRetrieverModule.kt's
getBulkMetadata to use this cause-chain check and only map to
promise.reject("ENOENT", ...) or the ENOENT error mapping when a matching cause
is found, otherwise fall back to promise.reject(errorKey, e.message, e).

}
} catch (e: Exception) {
promise.reject(errorKey, e.message, e)
}
}
Loading