Skip to content

Commit

Permalink
fix(react-native): reinstate the make-safe module to allow recursiv…
Browse files Browse the repository at this point in the history
…e meta-data in React Native applications

This reverts commit 99a0cb4
  • Loading branch information
lemnik committed Jan 24, 2022
1 parent c84c923 commit bccfe5b
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 4 deletions.
6 changes: 4 additions & 2 deletions packages/delivery-react-native/delivery.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const makeSafe = require('./make-safe')

module.exports = (client, NativeClient) => ({
sendEvent: (payload, cb = () => {}) => {
const event = payload.events[0]
Expand All @@ -17,10 +19,10 @@ module.exports = (client, NativeClient) => ({
app: event.app,
device: event.device,
threads: event.threads,
breadcrumbs: event.breadcrumbs,
breadcrumbs: makeSafe(event.breadcrumbs),
context: event.context,
user: event._user,
metadata: event._metadata,
metadata: makeSafe(event._metadata),
groupingHash: event.groupingHash,
apiKey: event.apiKey,
nativeStack: nativeStack
Expand Down
83 changes: 83 additions & 0 deletions packages/delivery-react-native/make-safe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
const isArray = require('@bugsnag/core/lib/es-utils/is-array')

const isSafeLiteral = (obj) => (
typeof obj === 'string' || obj instanceof String ||
typeof obj === 'number' || obj instanceof Number ||
typeof obj === 'boolean' || obj instanceof Boolean
)

const isError = o => (
o instanceof Error || /^\[object (Error|(Dom)?Exception)]$/.test(Object.prototype.toString.call(o))
)

const throwsMessage = err => '[Throws: ' + (err ? err.message : '?') + ']'

const safelyGetProp = (obj, propName) => {
try {
return obj[propName]
} catch (err) {
return throwsMessage(err)
}
}

/**
* Similar to `safe-json-stringify` this function rebuilds an object graph to be safe for the ReactNative JS Bridge.
* This requirement is different to `JSON.parse(safeJsonStringify(data))` in three key ways:
* - `toJSON` methods are not called
* - we leave more types intact for the JS Bridge to handle
* - there is no redaction or fixed depth limit
*
* @param data the value to be made safe for the ReactNative bridge
* @returns a safe version of the given `data`
*/
module.exports = function (data) {
const seen = []

const visit = (obj) => {
if (obj === null || obj === undefined) return obj

if (isSafeLiteral(obj)) {
return obj
}

if (isError(obj)) {
return visit({ name: obj.name, message: obj.message })
}

if (obj instanceof Date) {
return obj.toISOString()
}

if (seen.includes(obj)) {
// circular references are replaced and marked
return '[Circular]'
}

// handle arrays, and all iterable non-array types (such as Set)
if (isArray(obj) || obj[Symbol.iterator]) {
seen.push(obj)
const safeArray = []
try {
for (const value of obj) {
safeArray.push(visit(value))
}
} catch (err) {
// if retrieving the Iterator fails
return throwsMessage(err)
}
seen.pop()
return safeArray
}

seen.push(obj)
const safeObj = {}
for (const propName in obj) {
safeObj[propName] = visit(safelyGetProp(obj, propName))
}
seen.pop()

return safeObj
}

return visit(data)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ export class BreadcrumbsJsManualScenario extends Scenario {
const metaData = {
from: 'javascript'
}

// ensure that circular references are safely handled
metaData.circle = metaData

Bugsnag.leaveBreadcrumb('oh crumbs', metaData, 'state')
Bugsnag.notify(new Error('BreadcrumbsJsManualScenario'))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ export class MetadataJsScenario extends Scenario {
}

run () {
const recursiveMetadata = {}
recursiveMetadata.data = 'some valid data'
recursiveMetadata.circle = recursiveMetadata

Bugsnag.addMetadata('jsdata', 'some_more_data', 'set via client')
Bugsnag.addMetadata('jsdata', 'redacted_data', 'not present')
Bugsnag.addMetadata('jsdata', 'recursive', recursiveMetadata)
Bugsnag.notify(new Error('MetadataJsScenario'), (event) => {
event.addMetadata('jsdata', 'even_more_data', 'set via event')
event.addMetadata('jsarraydata', 'items', ['a', 'b', 'c'])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"name": "oh crumbs",
"timestamp": "^\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}:\\d{2}:[\\d\\.]+Z?$",
"metaData": {
"from": "javascript"
"from": "javascript",
"circle": "[Circular]"
}
}
}
2 changes: 2 additions & 0 deletions test/react-native/features/metadata.feature
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Scenario: Setting metadata (JS)
And the event "metaData.jsdata.some_more_data" equals "set via client"
And the event "metaData.jsdata.even_more_data" equals "set via event"
And the event "metaData.jsdata.redacted_data" equals "[REDACTED]"
And the event "metaData.jsdata.recursive.data" equals "some valid data"
And the event "metaData.jsdata.recursive.circle" equals "[Circular]"
And the error payload field "events.0.metaData.jsarraydata.items" is an array with 3 elements

Scenario: Setting metadata (native handled)
Expand Down

0 comments on commit bccfe5b

Please sign in to comment.