@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 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.18.4'
20
- s.dependency 'CoralogixInternal','2.18.4'
21
- s.dependency 'SessionReplay','2.18.4'
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
@@ -48,6 +48,8 @@ CoralogixRum.setUserContext({
48
48
  user_id: '123',
49
49
  user_name: 'name',
50
50
  user_email: 'user@email.com',
51
+ account_id: 'acc-42', // optional, from 0.12.0
52
+ account_name: 'Acme Corp', // optional, from 0.12.0
51
53
  user_metadata: {
52
54
  role: 'admin',
53
55
  // ...
@@ -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.22.0"
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
- val labels = labelsMap.toStringAnyMap()
114
- CoralogixRum.setLabels(labels)
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, data: ReadableMap, labels: ReadableMap) {
130
- CoralogixRum.log(severity.toCoralogixLogSeverity(), message, data.toStringAnyMap(), labels.toStringAnyMap())
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, data: ReadableMap, labels: ReadableMap)
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.11.2";
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 (typeof value !== 'object' || Array.isArray(value)) {
1749
- logger.warn(`CoralogixRum.log: \`${name}\` must be a plain object; received ${Array.isArray(value) ? 'array' : typeof value}. Ignoring.`);
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, message, safeData, safeLabels);
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.11.2";
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 (typeof value !== 'object' || Array.isArray(value)) {
1747
- logger.warn(`CoralogixRum.log: \`${name}\` must be a plain object; received ${Array.isArray(value) ? 'array' : typeof value}. Ignoring.`);
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, message, safeData, safeLabels);
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
- let labels = labelsMap as? [String: String] ?? [String: String]()
86
- resolve(coralogixRum?.set(labels: labels))
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: String,
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
- let data = data as? [String: Any] ?? [String: Any]()
141
- let labels = labels as? [String: Any] ?? [String: Any]()
142
- coralogixRum?.log(severity:cxLogSeverity, message: message, data: data, labels: labels)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coralogix/react-native-plugin",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
4
4
  "description": "Official Coralogix React Native plugin",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Coralogix",
@@ -2,6 +2,8 @@ export interface UserContextConfig {
2
2
  user_id: string;
3
3
  user_name: string;
4
4
  user_email?: string;
5
+ account_id?: string;
6
+ account_name?: string;
5
7
  user_metadata?: {
6
8
  [key: string]: any;
7
9
  };