@coralogix/react-native-plugin 0.11.2 → 0.12.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 +13 -0
- package/CxSdk.podspec +3 -3
- package/README.md +2 -0
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/cxsdk/CxSdkModule.kt +30 -6
- package/android/src/main/java/com/cxsdk/RUMClient.kt +2 -2
- package/index.cjs.js +128 -6
- package/index.esm.js +128 -6
- package/ios/CxSdk.swift +44 -11
- package/package.json +1 -1
- package/src/model/UserContextConfig.d.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## 0.12.0 (2026-10-06)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- Added optional `account_id` and `account_name` to `UserContext`.
|
|
6
|
+
|
|
7
|
+
## 0.11.3 (2026-09-26)
|
|
8
|
+
|
|
9
|
+
### 🩹 Fixes
|
|
10
|
+
|
|
11
|
+
- Fixed a crash on iOS when calling `setLabels`. Labels with non-string values (such as numbers or booleans) are now preserved instead of being dropped.
|
|
12
|
+
- `log` now handles missing or incorrectly typed arguments gracefully instead of crashing your app.
|
|
13
|
+
|
|
1
14
|
## 0.11.2 (2026-09-19)
|
|
2
15
|
|
|
3
16
|
### ⬆️ Dependency Updates
|
package/CxSdk.podspec
CHANGED
|
@@ -16,9 +16,9 @@ Pod::Spec.new do |s|
|
|
|
16
16
|
|
|
17
17
|
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
18
18
|
|
|
19
|
-
s.dependency 'Coralogix','2.
|
|
20
|
-
s.dependency 'CoralogixInternal','2.
|
|
21
|
-
s.dependency 'SessionReplay','2.
|
|
19
|
+
s.dependency 'Coralogix','2.19.0'
|
|
20
|
+
s.dependency 'CoralogixInternal','2.19.0'
|
|
21
|
+
s.dependency 'SessionReplay','2.19.0'
|
|
22
22
|
|
|
23
23
|
# Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
|
|
24
24
|
# See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
|
package/README.md
CHANGED
package/android/build.gradle
CHANGED
|
@@ -87,7 +87,7 @@ dependencies {
|
|
|
87
87
|
// 2.22.0 is the first release exposing View.isCoralogixMasked() and
|
|
88
88
|
// View.isInsideCoralogixMaskedSubtree(), which the masked-interaction walk in
|
|
89
89
|
// reportUserInteraction reads instead of reimplementing the SDK's mask rule here.
|
|
90
|
-
implementation "com.coralogix:android-sdk:2.
|
|
90
|
+
implementation "com.coralogix:android-sdk:2.23.0"
|
|
91
91
|
|
|
92
92
|
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
|
93
93
|
testImplementation "org.mockito:mockito-core:5.12.0"
|
|
@@ -109,9 +109,14 @@ class CxSdkModule(reactContext: ReactApplicationContext) :
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
@ReactMethod
|
|
112
|
-
override fun setLabels(labelsMap: ReadableMap) {
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
override fun setLabels(labelsMap: ReadableMap?) {
|
|
113
|
+
// Nullable: a JS null would fail Kotlin's null check and crash the app.
|
|
114
|
+
if (labelsMap == null) {
|
|
115
|
+
Log.w("CxSdkModule", "setLabels: labels is null, ignoring")
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
CoralogixRum.setLabels(labelsMap.toStringAnyMap())
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
@ReactMethod
|
|
@@ -126,8 +131,23 @@ class CxSdkModule(reactContext: ReactApplicationContext) :
|
|
|
126
131
|
}
|
|
127
132
|
|
|
128
133
|
@ReactMethod
|
|
129
|
-
override fun log(severity: Int, message: String
|
|
130
|
-
|
|
134
|
+
override fun log(severity: Int, message: String?, data: ReadableMap?, labels: ReadableMap?) {
|
|
135
|
+
// Nullable: a JS null would fail Kotlin's null check and crash the app.
|
|
136
|
+
// Non-string messages fail earlier, in RN's parsing; `toSafeMessage` (JS) covers them.
|
|
137
|
+
|
|
138
|
+
// Drop unknown severities, like iOS, rather than invent one.
|
|
139
|
+
val cxSeverity = runCatching { severity.toCoralogixLogSeverity() }.getOrNull()
|
|
140
|
+
if (cxSeverity == null) {
|
|
141
|
+
Log.w("CxSdkModule", "log: invalid severity $severity, dropping log. Must be one of [1, 2, 3, 4, 5, 6]")
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
CoralogixRum.log(
|
|
146
|
+
cxSeverity,
|
|
147
|
+
message ?: "",
|
|
148
|
+
data?.toStringAnyMap() ?: emptyMap(),
|
|
149
|
+
labels?.toStringAnyMap() ?: emptyMap()
|
|
150
|
+
)
|
|
131
151
|
}
|
|
132
152
|
|
|
133
153
|
@ReactMethod
|
|
@@ -813,7 +833,9 @@ class CxSdkModule(reactContext: ReactApplicationContext) :
|
|
|
813
833
|
userId = getString("user_id") ?: "",
|
|
814
834
|
username = getString("user_name") ?: "",
|
|
815
835
|
email = getString("user_email") ?: "",
|
|
816
|
-
metadata = getMap("user_metadata")?.toStringMap() ?: mapOf()
|
|
836
|
+
metadata = getMap("user_metadata")?.toStringMap() ?: mapOf(),
|
|
837
|
+
accountId = getString("account_id"),
|
|
838
|
+
accountName = getString("account_name")
|
|
817
839
|
)
|
|
818
840
|
|
|
819
841
|
private fun ReadableMap.toInstrumentationMap(): Map<Instrumentation, Boolean> {
|
|
@@ -950,6 +972,8 @@ class CxSdkModule(reactContext: ReactApplicationContext) :
|
|
|
950
972
|
map.putString("user_name", username)
|
|
951
973
|
map.putString("user_email", email)
|
|
952
974
|
map.putMap("user_metadata", metadata.toWritableMap())
|
|
975
|
+
accountId?.let { map.putString("account_id", it) }
|
|
976
|
+
accountName?.let { map.putString("account_name", it) }
|
|
953
977
|
|
|
954
978
|
return map
|
|
955
979
|
}
|
|
@@ -10,10 +10,10 @@ interface RUMClient {
|
|
|
10
10
|
fun getUserContext(promise: Promise)
|
|
11
11
|
fun setApplicationContext(appContextMap: ReadableMap)
|
|
12
12
|
fun getSessionId(promise: Promise)
|
|
13
|
-
fun setLabels(labelsMap: ReadableMap)
|
|
13
|
+
fun setLabels(labelsMap: ReadableMap?)
|
|
14
14
|
fun getLabels(promise: Promise)
|
|
15
15
|
fun setViewContext(viewContext: String)
|
|
16
|
-
fun log(severity: Int, message: String
|
|
16
|
+
fun log(severity: Int, message: String?, data: ReadableMap?, labels: ReadableMap?)
|
|
17
17
|
fun reportNetworkRequest(requestDetails: ReadableMap)
|
|
18
18
|
fun sendCustomMeasurement(measurement: ReadableMap)
|
|
19
19
|
fun startTimeMeasure(name: String, labels: ReadableMap?)
|
package/index.cjs.js
CHANGED
|
@@ -1140,7 +1140,7 @@ function stopJsRefreshRateSampler() {
|
|
|
1140
1140
|
appStateSub = null;
|
|
1141
1141
|
}
|
|
1142
1142
|
|
|
1143
|
-
var version = "0.
|
|
1143
|
+
var version = "0.12.0";
|
|
1144
1144
|
var pkg = {
|
|
1145
1145
|
version: version};
|
|
1146
1146
|
|
|
@@ -1741,17 +1741,121 @@ let initPromise;
|
|
|
1741
1741
|
// `CoralogixRum.isInited` be true while native has no RUM behind it.
|
|
1742
1742
|
let nativeOwnsSamplingRoll = false;
|
|
1743
1743
|
let fetchInstrumentation;
|
|
1744
|
+
|
|
1745
|
+
/**
|
|
1746
|
+
* Whether RN can send `value` as a dictionary; never throws. RN sends an object's
|
|
1747
|
+
* enumerable properties, so plain objects and class instances with fields work,
|
|
1748
|
+
* while `Map`, `Set` and `Date` (none enumerable) would arrive as `{}`.
|
|
1749
|
+
*/
|
|
1750
|
+
function isPlainObjectArgument(value) {
|
|
1751
|
+
try {
|
|
1752
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
1753
|
+
return false;
|
|
1754
|
+
}
|
|
1755
|
+
const proto = Object.getPrototypeOf(value);
|
|
1756
|
+
if (proto === Object.prototype || proto === null) {
|
|
1757
|
+
return true;
|
|
1758
|
+
}
|
|
1759
|
+
for (const _key in value) {
|
|
1760
|
+
return true;
|
|
1761
|
+
}
|
|
1762
|
+
return false;
|
|
1763
|
+
} catch (_unused) {
|
|
1764
|
+
return false;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
/** Whether `severity` is one of the `CoralogixLogSeverity` values (1–6). */
|
|
1769
|
+
function isValidSeverity(severity) {
|
|
1770
|
+
return Number.isInteger(severity) && severity >= 1 && severity <= 6;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
/** Type name for warnings; never throws. */
|
|
1774
|
+
function describeArgument(value) {
|
|
1775
|
+
if (value === null) {
|
|
1776
|
+
return 'null';
|
|
1777
|
+
}
|
|
1778
|
+
try {
|
|
1779
|
+
var _Object$getPrototypeO;
|
|
1780
|
+
if (Array.isArray(value)) {
|
|
1781
|
+
return 'array';
|
|
1782
|
+
}
|
|
1783
|
+
const name = typeof value === 'object' ? (_Object$getPrototypeO = Object.getPrototypeOf(value)) == null || (_Object$getPrototypeO = _Object$getPrototypeO.constructor) == null ? void 0 : _Object$getPrototypeO.name : undefined;
|
|
1784
|
+
// Minified builds rename classes to one letter, which would only confuse.
|
|
1785
|
+
return name && name !== 'Object' && name.length > 1 ? name : typeof value;
|
|
1786
|
+
} catch (_unused2) {
|
|
1787
|
+
return 'an uninspectable value';
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1744
1790
|
function toSafeRecord(name, value) {
|
|
1745
1791
|
if (value == null) {
|
|
1746
1792
|
return {};
|
|
1747
1793
|
}
|
|
1748
|
-
if (
|
|
1749
|
-
logger.warn(`CoralogixRum.log: \`${name}\` must be a plain object; received ${
|
|
1794
|
+
if (!isPlainObjectArgument(value)) {
|
|
1795
|
+
logger.warn(`CoralogixRum.log: \`${name}\` must be a plain object; received ${describeArgument(value)}. Ignoring.`);
|
|
1750
1796
|
return {};
|
|
1751
1797
|
}
|
|
1752
1798
|
return value;
|
|
1753
1799
|
}
|
|
1754
1800
|
|
|
1801
|
+
/** Stringifies any value without throwing (`JSON.stringify` and `String()` both can). */
|
|
1802
|
+
function stringifyUnknown(value) {
|
|
1803
|
+
try {
|
|
1804
|
+
const json = JSON.stringify(value);
|
|
1805
|
+
if (typeof json === 'string') {
|
|
1806
|
+
return json;
|
|
1807
|
+
}
|
|
1808
|
+
} catch (_unused3) {
|
|
1809
|
+
// fall through to String()
|
|
1810
|
+
}
|
|
1811
|
+
try {
|
|
1812
|
+
return String(value);
|
|
1813
|
+
} catch (_unused4) {
|
|
1814
|
+
return '[unserializable]';
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
/**
|
|
1819
|
+
* Readable text for an `Error` or a function, which `JSON.stringify` renders as `"{}"`
|
|
1820
|
+
* or source code. Returns undefined for anything else; never throws.
|
|
1821
|
+
*/
|
|
1822
|
+
function describeSpecialMessage(value) {
|
|
1823
|
+
try {
|
|
1824
|
+
if (value instanceof Error) {
|
|
1825
|
+
return value.stack || String(value);
|
|
1826
|
+
}
|
|
1827
|
+
if (typeof value === 'function') {
|
|
1828
|
+
return `[function ${value.name || 'anonymous'}]`;
|
|
1829
|
+
}
|
|
1830
|
+
} catch (_unused5) {
|
|
1831
|
+
// fall through to stringifyUnknown
|
|
1832
|
+
}
|
|
1833
|
+
return undefined;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
/**
|
|
1837
|
+
* Coerces `message` to a string before the bridge. On Android a non-string throws
|
|
1838
|
+
* inside RN's argument parsing, before native code runs, so only JS can guard it.
|
|
1839
|
+
*/
|
|
1840
|
+
function toSafeMessage(value) {
|
|
1841
|
+
if (typeof value === 'string') {
|
|
1842
|
+
return value;
|
|
1843
|
+
}
|
|
1844
|
+
if (value == null) {
|
|
1845
|
+
logger.warn(`CoralogixRum.log: \`message\` must be a string; received ${value === null ? 'null' : 'undefined'}. Sending an empty message.`);
|
|
1846
|
+
return '';
|
|
1847
|
+
}
|
|
1848
|
+
let coerced;
|
|
1849
|
+
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
|
|
1850
|
+
coerced = String(value);
|
|
1851
|
+
} else {
|
|
1852
|
+
var _describeSpecialMessa;
|
|
1853
|
+
coerced = (_describeSpecialMessa = describeSpecialMessage(value)) != null ? _describeSpecialMessa : stringifyUnknown(value);
|
|
1854
|
+
}
|
|
1855
|
+
logger.warn(`CoralogixRum.log: \`message\` must be a string; received ${describeArgument(value)}. Coerced to a string.`);
|
|
1856
|
+
return coerced;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1755
1859
|
/**
|
|
1756
1860
|
* The real init work. Runs at most once per in-flight/finished init: `CoralogixRum.init`
|
|
1757
1861
|
* memoizes the promise this returns, so concurrent callers share this single run.
|
|
@@ -1949,18 +2053,29 @@ const CoralogixRum = {
|
|
|
1949
2053
|
if (!result || typeof result !== 'object') {
|
|
1950
2054
|
return undefined;
|
|
1951
2055
|
}
|
|
1952
|
-
return {
|
|
2056
|
+
return _extends({
|
|
1953
2057
|
user_id: result.user_id || '',
|
|
1954
2058
|
user_name: result.user_name || '',
|
|
1955
2059
|
user_email: result.user_email || '',
|
|
1956
2060
|
user_metadata: result.user_metadata || {}
|
|
1957
|
-
}
|
|
2061
|
+
}, result.account_id && {
|
|
2062
|
+
account_id: result.account_id
|
|
2063
|
+
}, result.account_name && {
|
|
2064
|
+
account_name: result.account_name
|
|
2065
|
+
});
|
|
1958
2066
|
},
|
|
1959
2067
|
setLabels: labels => {
|
|
1960
2068
|
if (!isInited) {
|
|
1961
2069
|
logger.debug('CoralogixRum must be initiated before setting labels');
|
|
1962
2070
|
return;
|
|
1963
2071
|
}
|
|
2072
|
+
|
|
2073
|
+
// Native reads this as a dictionary unchecked, so a non-object crashes the app.
|
|
2074
|
+
// Return instead of sending `{}`, which would wipe existing labels.
|
|
2075
|
+
if (!isPlainObjectArgument(labels)) {
|
|
2076
|
+
logger.warn(`CoralogixRum.setLabels: \`labels\` must be a plain object; received ${describeArgument(labels)}. Ignoring.`);
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
1964
2079
|
CxSdk.setLabels(labels);
|
|
1965
2080
|
},
|
|
1966
2081
|
getLabels: async () => {
|
|
@@ -1982,9 +2097,16 @@ const CoralogixRum = {
|
|
|
1982
2097
|
logger.debug('CoralogixRum must be initiated before using log');
|
|
1983
2098
|
return;
|
|
1984
2099
|
}
|
|
2100
|
+
|
|
2101
|
+
// A non-integer severity crashes Android while RN parses the arguments.
|
|
2102
|
+
if (!isValidSeverity(severity)) {
|
|
2103
|
+
logger.warn(`CoralogixRum.log: \`severity\` must be a CoralogixLogSeverity (1-6); received ${describeArgument(severity)}. Dropping the log.`);
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2106
|
+
const safeMessage = toSafeMessage(message);
|
|
1985
2107
|
const safeData = toSafeRecord('data', data);
|
|
1986
2108
|
const safeLabels = toSafeRecord('labels', labels);
|
|
1987
|
-
CxSdk.log(severity,
|
|
2109
|
+
CxSdk.log(severity, safeMessage, safeData, safeLabels);
|
|
1988
2110
|
},
|
|
1989
2111
|
debug(message, data) {
|
|
1990
2112
|
this.log(CoralogixLogSeverity.Debug, message, data);
|
package/index.esm.js
CHANGED
|
@@ -1138,7 +1138,7 @@ function stopJsRefreshRateSampler() {
|
|
|
1138
1138
|
appStateSub = null;
|
|
1139
1139
|
}
|
|
1140
1140
|
|
|
1141
|
-
var version = "0.
|
|
1141
|
+
var version = "0.12.0";
|
|
1142
1142
|
var pkg = {
|
|
1143
1143
|
version: version};
|
|
1144
1144
|
|
|
@@ -1739,17 +1739,121 @@ let initPromise;
|
|
|
1739
1739
|
// `CoralogixRum.isInited` be true while native has no RUM behind it.
|
|
1740
1740
|
let nativeOwnsSamplingRoll = false;
|
|
1741
1741
|
let fetchInstrumentation;
|
|
1742
|
+
|
|
1743
|
+
/**
|
|
1744
|
+
* Whether RN can send `value` as a dictionary; never throws. RN sends an object's
|
|
1745
|
+
* enumerable properties, so plain objects and class instances with fields work,
|
|
1746
|
+
* while `Map`, `Set` and `Date` (none enumerable) would arrive as `{}`.
|
|
1747
|
+
*/
|
|
1748
|
+
function isPlainObjectArgument(value) {
|
|
1749
|
+
try {
|
|
1750
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
1751
|
+
return false;
|
|
1752
|
+
}
|
|
1753
|
+
const proto = Object.getPrototypeOf(value);
|
|
1754
|
+
if (proto === Object.prototype || proto === null) {
|
|
1755
|
+
return true;
|
|
1756
|
+
}
|
|
1757
|
+
for (const _key in value) {
|
|
1758
|
+
return true;
|
|
1759
|
+
}
|
|
1760
|
+
return false;
|
|
1761
|
+
} catch (_unused) {
|
|
1762
|
+
return false;
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
/** Whether `severity` is one of the `CoralogixLogSeverity` values (1–6). */
|
|
1767
|
+
function isValidSeverity(severity) {
|
|
1768
|
+
return Number.isInteger(severity) && severity >= 1 && severity <= 6;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
/** Type name for warnings; never throws. */
|
|
1772
|
+
function describeArgument(value) {
|
|
1773
|
+
if (value === null) {
|
|
1774
|
+
return 'null';
|
|
1775
|
+
}
|
|
1776
|
+
try {
|
|
1777
|
+
var _Object$getPrototypeO;
|
|
1778
|
+
if (Array.isArray(value)) {
|
|
1779
|
+
return 'array';
|
|
1780
|
+
}
|
|
1781
|
+
const name = typeof value === 'object' ? (_Object$getPrototypeO = Object.getPrototypeOf(value)) == null || (_Object$getPrototypeO = _Object$getPrototypeO.constructor) == null ? void 0 : _Object$getPrototypeO.name : undefined;
|
|
1782
|
+
// Minified builds rename classes to one letter, which would only confuse.
|
|
1783
|
+
return name && name !== 'Object' && name.length > 1 ? name : typeof value;
|
|
1784
|
+
} catch (_unused2) {
|
|
1785
|
+
return 'an uninspectable value';
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1742
1788
|
function toSafeRecord(name, value) {
|
|
1743
1789
|
if (value == null) {
|
|
1744
1790
|
return {};
|
|
1745
1791
|
}
|
|
1746
|
-
if (
|
|
1747
|
-
logger.warn(`CoralogixRum.log: \`${name}\` must be a plain object; received ${
|
|
1792
|
+
if (!isPlainObjectArgument(value)) {
|
|
1793
|
+
logger.warn(`CoralogixRum.log: \`${name}\` must be a plain object; received ${describeArgument(value)}. Ignoring.`);
|
|
1748
1794
|
return {};
|
|
1749
1795
|
}
|
|
1750
1796
|
return value;
|
|
1751
1797
|
}
|
|
1752
1798
|
|
|
1799
|
+
/** Stringifies any value without throwing (`JSON.stringify` and `String()` both can). */
|
|
1800
|
+
function stringifyUnknown(value) {
|
|
1801
|
+
try {
|
|
1802
|
+
const json = JSON.stringify(value);
|
|
1803
|
+
if (typeof json === 'string') {
|
|
1804
|
+
return json;
|
|
1805
|
+
}
|
|
1806
|
+
} catch (_unused3) {
|
|
1807
|
+
// fall through to String()
|
|
1808
|
+
}
|
|
1809
|
+
try {
|
|
1810
|
+
return String(value);
|
|
1811
|
+
} catch (_unused4) {
|
|
1812
|
+
return '[unserializable]';
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
/**
|
|
1817
|
+
* Readable text for an `Error` or a function, which `JSON.stringify` renders as `"{}"`
|
|
1818
|
+
* or source code. Returns undefined for anything else; never throws.
|
|
1819
|
+
*/
|
|
1820
|
+
function describeSpecialMessage(value) {
|
|
1821
|
+
try {
|
|
1822
|
+
if (value instanceof Error) {
|
|
1823
|
+
return value.stack || String(value);
|
|
1824
|
+
}
|
|
1825
|
+
if (typeof value === 'function') {
|
|
1826
|
+
return `[function ${value.name || 'anonymous'}]`;
|
|
1827
|
+
}
|
|
1828
|
+
} catch (_unused5) {
|
|
1829
|
+
// fall through to stringifyUnknown
|
|
1830
|
+
}
|
|
1831
|
+
return undefined;
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
/**
|
|
1835
|
+
* Coerces `message` to a string before the bridge. On Android a non-string throws
|
|
1836
|
+
* inside RN's argument parsing, before native code runs, so only JS can guard it.
|
|
1837
|
+
*/
|
|
1838
|
+
function toSafeMessage(value) {
|
|
1839
|
+
if (typeof value === 'string') {
|
|
1840
|
+
return value;
|
|
1841
|
+
}
|
|
1842
|
+
if (value == null) {
|
|
1843
|
+
logger.warn(`CoralogixRum.log: \`message\` must be a string; received ${value === null ? 'null' : 'undefined'}. Sending an empty message.`);
|
|
1844
|
+
return '';
|
|
1845
|
+
}
|
|
1846
|
+
let coerced;
|
|
1847
|
+
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
|
|
1848
|
+
coerced = String(value);
|
|
1849
|
+
} else {
|
|
1850
|
+
var _describeSpecialMessa;
|
|
1851
|
+
coerced = (_describeSpecialMessa = describeSpecialMessage(value)) != null ? _describeSpecialMessa : stringifyUnknown(value);
|
|
1852
|
+
}
|
|
1853
|
+
logger.warn(`CoralogixRum.log: \`message\` must be a string; received ${describeArgument(value)}. Coerced to a string.`);
|
|
1854
|
+
return coerced;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1753
1857
|
/**
|
|
1754
1858
|
* The real init work. Runs at most once per in-flight/finished init: `CoralogixRum.init`
|
|
1755
1859
|
* memoizes the promise this returns, so concurrent callers share this single run.
|
|
@@ -1947,18 +2051,29 @@ const CoralogixRum = {
|
|
|
1947
2051
|
if (!result || typeof result !== 'object') {
|
|
1948
2052
|
return undefined;
|
|
1949
2053
|
}
|
|
1950
|
-
return {
|
|
2054
|
+
return _extends({
|
|
1951
2055
|
user_id: result.user_id || '',
|
|
1952
2056
|
user_name: result.user_name || '',
|
|
1953
2057
|
user_email: result.user_email || '',
|
|
1954
2058
|
user_metadata: result.user_metadata || {}
|
|
1955
|
-
}
|
|
2059
|
+
}, result.account_id && {
|
|
2060
|
+
account_id: result.account_id
|
|
2061
|
+
}, result.account_name && {
|
|
2062
|
+
account_name: result.account_name
|
|
2063
|
+
});
|
|
1956
2064
|
},
|
|
1957
2065
|
setLabels: labels => {
|
|
1958
2066
|
if (!isInited) {
|
|
1959
2067
|
logger.debug('CoralogixRum must be initiated before setting labels');
|
|
1960
2068
|
return;
|
|
1961
2069
|
}
|
|
2070
|
+
|
|
2071
|
+
// Native reads this as a dictionary unchecked, so a non-object crashes the app.
|
|
2072
|
+
// Return instead of sending `{}`, which would wipe existing labels.
|
|
2073
|
+
if (!isPlainObjectArgument(labels)) {
|
|
2074
|
+
logger.warn(`CoralogixRum.setLabels: \`labels\` must be a plain object; received ${describeArgument(labels)}. Ignoring.`);
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
1962
2077
|
CxSdk.setLabels(labels);
|
|
1963
2078
|
},
|
|
1964
2079
|
getLabels: async () => {
|
|
@@ -1980,9 +2095,16 @@ const CoralogixRum = {
|
|
|
1980
2095
|
logger.debug('CoralogixRum must be initiated before using log');
|
|
1981
2096
|
return;
|
|
1982
2097
|
}
|
|
2098
|
+
|
|
2099
|
+
// A non-integer severity crashes Android while RN parses the arguments.
|
|
2100
|
+
if (!isValidSeverity(severity)) {
|
|
2101
|
+
logger.warn(`CoralogixRum.log: \`severity\` must be a CoralogixLogSeverity (1-6); received ${describeArgument(severity)}. Dropping the log.`);
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
const safeMessage = toSafeMessage(message);
|
|
1983
2105
|
const safeData = toSafeRecord('data', data);
|
|
1984
2106
|
const safeLabels = toSafeRecord('labels', labels);
|
|
1985
|
-
CxSdk.log(severity,
|
|
2107
|
+
CxSdk.log(severity, safeMessage, safeData, safeLabels);
|
|
1986
2108
|
},
|
|
1987
2109
|
debug(message, data) {
|
|
1988
2110
|
this.log(CoralogixLogSeverity.Debug, message, data);
|
package/ios/CxSdk.swift
CHANGED
|
@@ -79,11 +79,15 @@ class CxSdk: RCTEventEmitter {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
@objc(setLabels:withResolver:withRejecter:)
|
|
82
|
-
func setLabels(labelsMap: NSDictionary
|
|
82
|
+
func setLabels(labelsMap: NSDictionary?,
|
|
83
83
|
resolve:RCTPromiseResolveBlock,
|
|
84
84
|
reject:RCTPromiseRejectBlock) -> Void {
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
// Ignore nil or a non-dictionary, as Android does, rather than wipe the labels.
|
|
86
|
+
guard let labelsMap = labelsMap, labelsMap.isKind(of: NSDictionary.self) else {
|
|
87
|
+
resolve(nil)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
resolve(coralogixRum?.set(labels: bridgedDictionary(labelsMap)))
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
@objc(getLabels:withRejecter:)
|
|
@@ -128,18 +132,19 @@ class CxSdk: RCTEventEmitter {
|
|
|
128
132
|
|
|
129
133
|
@objc(log:message:data:labels:withResolver:withRejecter:)
|
|
130
134
|
func log(severity: Int,
|
|
131
|
-
message:
|
|
132
|
-
data: NSDictionary
|
|
133
|
-
labels: NSDictionary
|
|
135
|
+
message: NSString?,
|
|
136
|
+
data: NSDictionary?,
|
|
137
|
+
labels: NSDictionary?,
|
|
134
138
|
resolve:RCTPromiseResolveBlock,
|
|
135
139
|
reject:RCTPromiseRejectBlock) -> Void {
|
|
136
140
|
|
|
137
141
|
guard let cxLogSeverity = CoralogixLogSeverity(rawValue: Int(severity)) else {
|
|
138
142
|
return
|
|
139
143
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
144
|
+
coralogixRum?.log(severity: cxLogSeverity,
|
|
145
|
+
message: bridgedString(message),
|
|
146
|
+
data: bridgedDictionary(data),
|
|
147
|
+
labels: bridgedDictionary(labels))
|
|
143
148
|
resolve("log success")
|
|
144
149
|
}
|
|
145
150
|
|
|
@@ -601,7 +606,9 @@ class CxSdk: RCTEventEmitter {
|
|
|
601
606
|
let coralogixUser = UserContext(userId: userContext?["user_id"] as? String ?? "",
|
|
602
607
|
userName: userContext?["user_name"] as? String ?? "",
|
|
603
608
|
userEmail:userContext?["user_email"] as? String ?? "",
|
|
604
|
-
userMetadata: userMetadata
|
|
609
|
+
userMetadata: userMetadata,
|
|
610
|
+
accountId: userContext?["account_id"] as? String,
|
|
611
|
+
accountName: userContext?["account_name"] as? String)
|
|
605
612
|
let ignoreUrls = (parameter["ignoreUrls"] as? [Any])?.compactMap { $0 as? String } ?? []
|
|
606
613
|
let ignoreError = (parameter["ignoreErrors"] as? [Any])?.compactMap { $0 as? String } ?? []
|
|
607
614
|
let rawNetworkExtraConfig = parameter["networkExtraConfig"] as? [[String: Any]]
|
|
@@ -711,7 +718,9 @@ class CxSdk: RCTEventEmitter {
|
|
|
711
718
|
return UserContext(userId: parameter["user_id"] as? String ?? "",
|
|
712
719
|
userName: parameter["user_name"] as? String ?? "",
|
|
713
720
|
userEmail:parameter["user_email"] as? String ?? "",
|
|
714
|
-
userMetadata: userMetadata
|
|
721
|
+
userMetadata: userMetadata,
|
|
722
|
+
accountId: parameter["account_id"] as? String,
|
|
723
|
+
accountName: parameter["account_name"] as? String)
|
|
715
724
|
}
|
|
716
725
|
|
|
717
726
|
private func mobileVitalsType(from string: String) -> CoralogixExporterOptions.MobileVitalsType? {
|
|
@@ -832,6 +841,30 @@ class CxSdk: RCTEventEmitter {
|
|
|
832
841
|
return set
|
|
833
842
|
}
|
|
834
843
|
|
|
844
|
+
/// Reads a bridged dictionary without trusting its type: release builds skip RN's
|
|
845
|
+
/// type check, so a JS array/string/number can arrive here. Must be `isKind(of:)` —
|
|
846
|
+
/// `as? NSDictionary` gets optimised away. Values pass through as `Any`, like Android.
|
|
847
|
+
private func bridgedDictionary(_ value: NSDictionary?) -> [String: Any] {
|
|
848
|
+
guard let object = value, object.isKind(of: NSDictionary.self) else {
|
|
849
|
+
return [String: Any]()
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
var result = [String: Any]()
|
|
853
|
+
object.enumerateKeysAndObjects { key, value, _ in
|
|
854
|
+
guard let name = key as? String else { return }
|
|
855
|
+
result[name] = value
|
|
856
|
+
}
|
|
857
|
+
return result
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/// Same for strings: a Swift `String` parameter would force-cast an `NSNumber` and crash.
|
|
861
|
+
private func bridgedString(_ value: NSString?) -> String {
|
|
862
|
+
guard let object = value, object.isKind(of: NSString.self) else {
|
|
863
|
+
return ""
|
|
864
|
+
}
|
|
865
|
+
return object as String
|
|
866
|
+
}
|
|
867
|
+
|
|
835
868
|
private func toAnyDict(_ dict: NSDictionary?) -> [String: Any]? {
|
|
836
869
|
guard let dict = dict, dict.count > 0 else { return nil }
|
|
837
870
|
var result = [String: Any]()
|
package/package.json
CHANGED