From 5f3972897f725dc4d7233b6360945bd2da46bb43 Mon Sep 17 00:00:00 2001 From: Jeff Hoffer Date: Wed, 14 Apr 2021 06:56:59 -0400 Subject: [PATCH] refactor(files): moved everything out of src/index.js into separate files --- src/constants.js | 13 + src/error-boundary.js | 75 ++++++ src/history-context.js | 24 ++ src/hooks/index.js | 5 + src/hooks/use-rollbar-capture-event.js | 12 + src/hooks/use-rollbar-config.js | 6 + src/hooks/use-rollbar-context.js | 21 ++ src/hooks/use-rollbar-logs.js | 42 +++ src/hooks/use-rollbar-person.js | 5 + src/hooks/use-rollbar.js | 7 + src/hooks/use-scoped-rollbar-config.js | 15 ++ src/hooks/utils.js | 15 ++ src/index.js | 354 +------------------------ src/provider.js | 57 ++++ src/rollbar-configuration.js | 40 +++ src/rollbar-context.js | 65 +++++ src/utils.js | 10 + 17 files changed, 419 insertions(+), 347 deletions(-) create mode 100644 src/constants.js create mode 100644 src/error-boundary.js create mode 100644 src/history-context.js create mode 100644 src/hooks/index.js create mode 100644 src/hooks/use-rollbar-capture-event.js create mode 100644 src/hooks/use-rollbar-config.js create mode 100644 src/hooks/use-rollbar-context.js create mode 100644 src/hooks/use-rollbar-logs.js create mode 100644 src/hooks/use-rollbar-person.js create mode 100644 src/hooks/use-rollbar.js create mode 100644 src/hooks/use-scoped-rollbar-config.js create mode 100644 src/hooks/utils.js create mode 100644 src/provider.js create mode 100644 src/rollbar-configuration.js create mode 100644 src/rollbar-context.js create mode 100644 src/utils.js diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..710d443 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,13 @@ +export const LEVEL_DEBUG = 'debug'; +export const LEVEL_INFO = 'info'; +export const LEVEL_WARN = 'warn'; +export const LEVEL_ERROR = 'error'; +export const LEVEL_CRITICAL = 'critical'; + +export default { + [LEVEL_DEBUG]: 1, + [LEVEL_INFO]: 2, + [LEVEL_WARN]: 3, + [LEVEL_ERROR]: 4, + [LEVEL_CRITICAL]: 5, +} diff --git a/src/error-boundary.js b/src/error-boundary.js new file mode 100644 index 0000000..4a8da34 --- /dev/null +++ b/src/error-boundary.js @@ -0,0 +1,75 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import invariant from 'tiny-invariant'; +import VALID_LEVELS, { LEVEL_ERROR } from './constants'; +import { Context, getRollbarFromContext } from './provider'; + +const INITIAL_ERROR_STATE = { hasError: false, error: null }; + +export class ErrorBoundary extends Component { + static contextType = Context; + + static propTypes = { + fallbackUI: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), + errorMessage: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), + extra: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + level: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), + callback: PropTypes.func, + } + + static defaultProps = { + level: LEVEL_ERROR, + } + + constructor(props) { + super(props); + invariant(VALID_LEVELS.includes(props.level), `${props.level} is not a valid level setting for Rollbar`); + this.state = { ...INITIAL_ERROR_STATE }; + } + + static getDerivedStateFromError(error) { + return { hasError: true, error }; + } + + componentDidCatch(error, info) { + const { errorMessage, extra, level: targetLevel, callback } = this.props; + const custom = utils.value(extra, {}, error, info); + const data = { ...info, ...custom }; + const level = utils.value(targetLevel, LEVEL_ERROR, error, info); + const rollbar = getRollbarFromContext(this.context); + if (!errorMessage) { + rollbar[level](error, data, callback); + } else { + let logMessage = utils.value(errorMessage, '', error, info); + rollbar[level](logMessage, error, data, callback); + } + } + + resetError = () => { + this.setState(INITIAL_ERROR_STATE); + } + + render() { + const { hasError, error } = this.state; + const { fallbackUI, children } = this.props; + + if (!hasError) { + return children; + } + + if (!fallbackUI) { + return null; + } + + if (React.isValidElement(fallbackUI)) { + return ; + } + + if (typeof fallbackUI === 'function') { + const fallbackComponent = fallbackUI(error, this.resetError); + return React.isValidElement(fallbackComponent) ? fallbackComponent : null; + } + + return null; + } +} diff --git a/src/history-context.js b/src/history-context.js new file mode 100644 index 0000000..c791f2c --- /dev/null +++ b/src/history-context.js @@ -0,0 +1,24 @@ +import Rollbar from 'rollbar'; +import invariant from 'tiny-invariant'; + +export function historyContext(rollbar, { formatter, filter }) { + invariant(rollbar instanceof Rollbar, 'historyContext must have an instance of Rollbar'); + invariant(formatter == null || typeof formatter === 'function', `formatter option must be a function, received ${typeof formatter} instead`); + invariant(filter == null || typeof filter === 'function', `filter option must be a function, received ${typeof filter} instead`); + // v4 of history.listen callback signature is (location, action) + // v5 of history.listen callback signature is ({ location, action }) + // this implementation translates it to work for both + return (v4location, v4action) => { + let { action, location } = v4location; + if (v4action) { + action = v4action; + location = v4location; + } + if (filter && !filter(location, action)) { + return; + } + const context = formatter ? formatter(location, action) : location.pathname; + invariant(typeof context === 'string', 'formatter must return a string value to set the context'); + rollbar.configure({ payload: { context }}); + } +} diff --git a/src/hooks/index.js b/src/hooks/index.js new file mode 100644 index 0000000..26a27de --- /dev/null +++ b/src/hooks/index.js @@ -0,0 +1,5 @@ +export { useRollbar } from './use-rollbar'; +export { useRollbarConfiguration } from './use-rollbar-config'; +export { useRollbarContext } from './use-rollbar-context'; +export { useRollbarPerson } from './use-rollbar-person'; +export { useRollbarCaptureEvent } from './use-rollbar-capture-event'; diff --git a/src/hooks/use-rollbar-capture-event.js b/src/hooks/use-rollbar-capture-event.js new file mode 100644 index 0000000..6168b83 --- /dev/null +++ b/src/hooks/use-rollbar-capture-event.js @@ -0,0 +1,12 @@ +import invariant from 'tiny-invariant'; +import VALID_LEVELS, { LEVEL_DEBUG, LEVEL_INFO, LEVEL_CRITICAL } = '../constants'; +import [ useRollbar } from './use-rollbar'; + +export function useRollbarCaptureEvent(metadata, level = LEVEL_INFO) { + invariant(VALID_LEVELS[level] <= LEVEL_CRITICAL && VALID_LEVELS[level] >= LEVEL_DEBUG, '') + const rollbar = useRollbar(); + useEffect(() => { + rollbar.captureEvent(metadata, level); + }, [metadata]); +} + diff --git a/src/hooks/use-rollbar-config.js b/src/hooks/use-rollbar-config.js new file mode 100644 index 0000000..ccffc11 --- /dev/null +++ b/src/hooks/use-rollbar-config.js @@ -0,0 +1,6 @@ +import [ useRollbar } from './use-rollbar'; + +export function useRollbarConfiguration(config) { + const rollbar = useRollbar(); + rollbar.configure(config); +} diff --git a/src/hooks/use-rollbar-context.js b/src/hooks/use-rollbar-context.js new file mode 100644 index 0000000..45856cc --- /dev/null +++ b/src/hooks/use-rollbar-context.js @@ -0,0 +1,21 @@ +import invariant from 'tiny-invariant'; +import { useRollbar } from './use-rollbar'; +import { useRollbarConfiguration } from './use-rollbar-config'; + +// Simple version does its job +// export function useRollbarContext(context) { +// useRollbarConfiguration({ payload: { context }}); +// } + +// Complex version will set the context when part of the tree and reset back to original context when removed +export function useRollbarContext(ctx = '', isLayout = false) { + invariant(typeof ctx === 'string', '`ctx` must be a string'); + const rollbar = useRollbar(); + (isLayout ? useLayoutEffect : useEffect)(() => { + const origCtx = rollbar.options.payload.context; + rollbar.configure({ payload: { context: ctx }}); + return () => { + rollbar.configure({ payload: { context: origCtx }}); + }; + }, [ctx]); +} diff --git a/src/hooks/use-rollbar-logs.js b/src/hooks/use-rollbar-logs.js new file mode 100644 index 0000000..aa08172 --- /dev/null +++ b/src/hooks/use-rollbar-logs.js @@ -0,0 +1,42 @@ +// EXPERIMENTAL +// NOT EXPORTED AS PART OF PUBLIC API YET +// NO TEST COVERAGE + +import { useEffect, useLayoutEffect } from 'react'; +import invariant from 'tiny-invariant'; +import VALID_LEVELS, { LEVEL_DEBUG, LEVEL_INFO, LEVEL_WARN, LEVEL_ERROR, LEVEL_CRITICAL } from '../constants'; +import { useRollbar } from './use-rollbar'; + +const LOG = 'log'; + +function useRollbarNotify(type, isLayout, ...args) { + invariant(type === LOG || VALID_LEVELS[type] >= LEVEL_DEBUG && VALID_LEVELS[type] <= LEVEL_CRITICAL, `cannot notify rollbar using method '${type}'`); + const rollbar = useRollbar(); + (isLayout ? useLayoutEffect : useEffect)(() => { + rollbar[type](...args); + }, args) +} + +export function useRollbarLog(isLayout, ...args) { + useRollbarNotify(LOG, isLayout, ...args); +} + +export function useRollbarDebug(isLayout, ...args) { + useRollbarNotify(LEVEL_DEBUG, isLayout, ...args); +} + +export function useRollbarInfo(isLayout, ...args) { + useRollbarNotify(LEVEL_INFO, isLayout, ...args); +} + +export function useRollbarWarn(isLayout, ...args) { + useRollbarNotify(LEVEL_WARN, isLayout, ...args); +} + +export function useRollbarError(isLayout, ...args) { + useRollbarNotify(LEVEL_ERROR, isLayout, ...args); +} + +export function useRollbarCritical(isLayout, ...args) { + useRollbarNotify(LEVEL_ERROR, isLayout, ...args); +} diff --git a/src/hooks/use-rollbar-person.js b/src/hooks/use-rollbar-person.js new file mode 100644 index 0000000..f6bd24b --- /dev/null +++ b/src/hooks/use-rollbar-person.js @@ -0,0 +1,5 @@ +import { useRollbarConfiguration } from './use-rollbar-config'; + +export function useRollbarPerson(person) { + useRollbarConfiguration({ payload: { person }}); +} diff --git a/src/hooks/use-rollbar.js b/src/hooks/use-rollbar.js new file mode 100644 index 0000000..666b52f --- /dev/null +++ b/src/hooks/use-rollbar.js @@ -0,0 +1,7 @@ +import { useContext } from 'react'; +import { Context, getRollbarFromContext } from '../provider'; + +export function useRollbar() { + const context = useContext(Context); + return getRollbarFromContext(context); +} diff --git a/src/hooks/use-scoped-rollbar-config.js b/src/hooks/use-scoped-rollbar-config.js new file mode 100644 index 0000000..df74050 --- /dev/null +++ b/src/hooks/use-scoped-rollbar-config.js @@ -0,0 +1,15 @@ +// EXPERIMENTAL +// NOT EXPORTED AS PART OF PUBLIC API YET +// NO TEST COVERAGE + +import { useContext } from 'react'; +import { Context, getRollbarFromContext, getRollbarConstructorFromContext } from '../provider'; + +export function useScopedConfiguration(config) { + const ctx = useContext(Context); + const rollbar = getRollbarFromContext(ctx); + const ctor = getRollbarConstructorFromContext(ctx); + const rollbar = new ctor(base.options); + rollbar.configure(config); + return rollbar; +} diff --git a/src/hooks/utils.js b/src/hooks/utils.js new file mode 100644 index 0000000..56ce4ac --- /dev/null +++ b/src/hooks/utils.js @@ -0,0 +1,15 @@ +import { useRef } from 'react'; + +// EXPERIMENTAL (NOT IN USE): wrap the instance of rollbar to prevent modification +export function wrapRollbarApi(rollbar) { + const rb = useRef(rollbar); + return { + log: (...args) => rb.current.log(...args), + debug: (...args) => rb.current.debug(...args), + info: (...args) => rb.current.info(...args), + warn: (...args) => rb.current.warn(...args), + error: (...args) => rb.current.error(...args), + critical: (...args) => rb.current.critical(...args), + captureEvent: (...args) => rb.current.captureEvent(...args), + } +} diff --git a/src/index.js b/src/index.js index 15f3bc6..5cab95d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,347 +1,7 @@ -import React, { Component, createContext, useContext, useEffect, useLayoutEffect, useRef } from 'react'; -import PropTypes from 'prop-types'; -import Rollbar from 'rollbar'; -import invariant from 'tiny-invariant'; - -const LEVEL_DEBUG = 'debug'; -const LEVEL_INFO = 'info'; -const LEVEL_WARN = 'warn'; -const LEVEL_ERROR = 'error'; -const LEVEL_CRITICAL = 'critical'; - -const Context = createContext(); -Context.displayName = 'Rollbar'; - -const RollbarInstance = Symbol('RollbarInstance'); -const BaseOptions = Symbol('BaseOptions'); -const RollbarCtor = Symbol('RollbarCtor'); - -function value(val, defaultTo, ...args) { - if (typeof val === 'function') { - return val(...args); - } - return val; -} - -function wrapValue(val, defaultAs) { - return (defaultTo, ...args) => value(val, defaultAs === undefined ? defaultTo : defaultAs, ...args); -} - -export function historyContext(rollbar, { formatter, filter }) { - invariant(rollbar instanceof Rollbar, 'historyContext must have an instance of Rollbar'); - invariant(formatter == null || typeof formatter === 'function', `formatter option must be a function, received ${typeof formatter} instead`); - invariant(filter == null || typeof filter === 'function', `filter option must be a function, received ${typeof filter} instead`); - return (v4location, v4action) => { - let { action, location } = v4location; - if (v4action) { - action = v4action; - location = v4location; - } - if (filter && !filter(location, action)) { - return; - } - const context = formatter ? formatter(location, action) : location.pathname; - rollbar.configure({ payload: { context }}); - } -} - -export class Provider extends Component { - static propTypes = { - Rollbar: PropTypes.func, - config: PropTypes.oneOfType([PropTypes.object, PropTypes.func]).isRequired, - instance: PropTypes.instanceOf(Rollbar), - } - - constructor(props) { - super(props); - const { config, Rollbar: ctor = Rollbar, instance } = this.props; - invariant(!instance || instance instanceof Rollbar, 'providing `instance` must be of type Rollbar'); - const options = typeof config === 'function' ? config() : config; - const rollbar = instance || new ctor(options); - // TODO: use isUncaught to filter if this is 2nd Provider added - // unless customer wants that - this.state = { rollbar, options }; - } - - // componentDidUpdate() - - render() { - const { children, Rollbar: ctor = Rollbar } = this.props; - const { rollbar, options } = this.state; - - return ( - - {children} - - ) - } -} - -export class RollbarContext extends Component { - static propTypes = { - context: PropTypes.string.isRequired, - onRender: PropTypes.bool, - } - - static defaultProps = { - onRender: false, - } - - static contextType = Context; - - firstRender = true; - - constructor(props) { - super(props); - this.state = { previousContext: null }; - } - - // static getDerivedStateFromProps() {} - - changeContext = (storePrevious = true) => { - const { [RollbarInstance]: rollbar } = this.context; - const { context } = this.props; - if (storePrevious) { - this.setState({ previousContext: rollbar.options.payload.context }); - } - rollbar.configure({ payload: { context }}); - } - - componentDidMount() { - const { onRender } = this.props; - if (!onRender) { - this.changeContext(true); - } - } - - componentDidUpdate() { - const { onRender } = this.props; - if (!onRender) { - this.changeContext(false); - } - } - - componentWillUnmount() { - const { [RollbarInstance]: rollbar } = this.context; - const { previousContext } = this.state; - rollbar.configure({ payload: { context: previousContext }}); - } - - render() { - const { onRender } = this.props; - if (onRender && this.firstRender) { - this.changeContext(true); - } - this.firstRender = false; - return this.props.children; - } -} - -// export class RollbarConfiguration extends Component { -// static propTypes = { -// options: PropTypes.object.isRequired, -// } - -// static contextType = Context; - -// constructor(props) { -// super(props); -// this.state = { parentConfig: null }; -// } - -// componentDidMount() { -// const { [RollbarInstance]: rollbar } = this.context; -// const { options } = this.props; -// // need to clone this somehow to prevent downstream changes from manipulating it -// const parentConfig = (o => o)(rollbar.options); -// this.setState({ parentConfig }); -// rollbar.configure(options); -// } - -// componentWillUnmount() { -// const { [RollbarInstance]: rollbar } = this.context; -// const { parentConfig } = this.state; -// rollbar.configure(parentConfig); -// } - -// render() { -// return this.props.children; -// } -// } - -const NOOP = () => {}; -const INITIAL_ERROR_STATE = { hasError: false, error: null }; - -export class ErrorBoundary extends Component { - static contextType = Context; - - static propTypes = { - fallbackUI: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), - errorMessage: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), - extra: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), - level: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), - callback: PropTypes.func, - } - - static defaultProps = { - level: 'error', - } - - constructor(props) { - super(props); - this.state = { ...INITIAL_ERROR_STATE }; - } - - static getDerivedStateFromError(error) { - return { hasError: true, error }; - } - - componentDidCatch(error, info) { - const { errorMessage, extra, level: targetLevel, callback } = this.props; - const custom = value(extra, {}, error, info); - const data = { ...info, ...custom }; - const level = value(targetLevel, 'error', error, info); - const { [RollbarInstance]: rollbar } = this.context; - if (!errorMessage) { - rollbar[level](error, data, callback); - } else { - let logMessage = value(errorMessage, '', error, info); - rollbar[level](logMessage, error, data, callback); - } - } - - resetError = () => { - this.setState(INITIAL_ERROR_STATE); - } - - // wrapRollbar = (rollbar, children) => { - // return ( - // - // {children} - // - // ); - // } - - // getChildren = () => { - render() { - const { hasError, error } = this.state; - const { fallbackUI, children } = this.props; - - if (!hasError) { - return children; - } - - if (!fallbackUI) { - return null; - } - - if (React.isValidElement(fallbackUI)) { - return ; - } - - if (typeof fallbackUI === 'function') { - const fallbackComponent = fallbackUI(error, this.resetError); - return React.isValidElement(fallbackComponent) ? fallbackComponent : null; - } - - return null; - } - - // render() { - // const { rollbar, doWrapRollbar } = this.state; - // const children = this.getChildren(); - // if (doWrapRollbar) { - // return this.wrapRollbar(rollbar, children); - // } - // return children; - // } -} - -export function useRollbar() { - const { [RollbarInstance]: rollbar } = useContext(Context); - return rollbar; -} - -export function useRollbarConfiguration(config) { - const rollbar = useRollbar(); - rollbar.configure(config); -} - -export function useScopedConfiguration(config) { - const { [RollbarInstance]: base, [RollbarCtor]: ctor } = useContext(Context); - // const base = useRollbar(); - const rollbar = new ctor(base.options); - rollbar.configure(config); - return rollbar; -} - -export function useRollbarPerson(person) { - useRollbarConfiguration({ payload: { person }}); -} - -export function useRollbarContext(context) { - useRollbarConfiguration({ payload: { context }}); -} - -function useRollbarNotify(type, isLayout, ...args) { - const rollbar = useRollbar(); - (isLayout ? useLayoutEffect : useEffect)(() => { - rollbar[type](...args); - }, args) -} - -function wrapRollbarApi(rollbar) { - const rb = useRef(rollbar); - return { - log: (...args) => rb.current.log(...args), - debug: (...args) => rb.current.debug(...args), - info: (...args) => rb.current.info(...args), - warn: (...args) => rb.current.warn(...args), - error: (...args) => rb.current.error(...args), - critical: (...args) => rb.current.critical(...args), - captureEvent: (...args) => rb.current.captureEvent(...args), - } -} - -export function useRollbarLog(...args) { - useRollbarNotify('log', ...args); -} - -export function useRollbarDebug(...args) { - useRollbarNotify('debug', ...args); -} - -export function useRollbarInfo(...args) { - useRollbarNotify('info', ...args); -} - -export function useRollbarWarn(...args) { - useRollbarNotify('warn', ...args); -} - -export function useRollbarError(...args) { - useRollbarNotify('error', ...args); -} - -export function useRollbarCritical(...args) { - useRollbarNotify('critical', ...args); -} - -export function useRollbarCaptureEvent(metadata, level) { - const rollbar = useRollbar(); - useEffect(() => { - rollbar.captureEvent(metadata, level); - }, [metadata]); -} - -export function useContext(ctx = '', isLayout = false) { - invariant(ctx && typeof ctx === 'string', '`ctx` must be a non-empty string'); - const rollbar = useRollbar(); - (isLayout ? useLayoutEffect : useEffect)(() => { - const origCtx = rollbar.options.payload.context; - rollbar.configure({ payload: { context: ctx }}); - return () => { - rollbar.configure({ payload: { context: origCtx }}); - }; - }, [ctx]); -} +import VALID_LEVELS, { LEVEL_DEBUG, LEVEL_INFO, LEVEL_WARN, LEVEL_ERROR, LEVEL_CRITICAL } from './constants'; +export { VALID_LEVELS, LEVEL_DEBUG, LEVEL_INFO, LEVEL_WARN, LEVEL_ERROR, LEVEL_CRITICAL } +export { historyContext } from './history-context'; +export { Provider } from './provider'; +export { ErrorBoundary } from './error-boundary'; +export { RollbarContext } from './rollbar-context'; +export * from './hooks'; diff --git a/src/provider.js b/src/provider.js new file mode 100644 index 0000000..d4db946 --- /dev/null +++ b/src/provider.js @@ -0,0 +1,57 @@ +import React, { Component, createContext, useContext, useEffect, useLayoutEffect, useRef } from 'react'; +import PropTypes from 'prop-types'; +import Rollbar from 'rollbar'; +import invariant from 'tiny-invariant'; +import * as constants from './constants'; +import * as utils from './utils'; + +export const Context = createContext(); +Context.displayName = 'Rollbar'; + +const RollbarInstance = Symbol('RollbarInstance'); +const BaseOptions = Symbol('BaseOptions'); +const RollbarCtor = Symbol('RollbarCtor'); + +export function getRollbarFromContext(context) { + const { [RollbarInstance]: rollbar } = context; + return rollbar; +} + +export function getRollbarConstructorFromContext(context) { + const { [RollbarCtor]: ctor } = context; + return ctor; +} + +export class Provider extends Component { + static propTypes = { + Rollbar: PropTypes.func, + config: PropTypes.oneOfType([PropTypes.object, PropTypes.func]).isRequired, + instance: PropTypes.instanceOf(Rollbar), + } + + constructor(props) { + super(props); + const { config, Rollbar: ctor = Rollbar, instance } = this.props; + invariant(!instance || instance instanceof Rollbar, 'providing `instance` must be of type Rollbar'); + const options = typeof config === 'function' ? config() : config; + const rollbar = instance || new ctor(options); + // TODO: use isUncaught to filter if this is 2nd Provider added + // unless customer wants that + this.state = { rollbar, options }; + } + + + + // componentDidUpdate() + + render() { + const { children, Rollbar: ctor = Rollbar } = this.props; + const { rollbar, options } = this.state; + + return ( + + {children} + + ) + } +} diff --git a/src/rollbar-configuration.js b/src/rollbar-configuration.js new file mode 100644 index 0000000..7d7d423 --- /dev/null +++ b/src/rollbar-configuration.js @@ -0,0 +1,40 @@ +// EXPERIMENTAL +// NOT EXPORTED AS PART OF PUBLIC API YET: no current test coverage + +// PURPOSE provide a wrapping around Rollbar configuration for a subtree + +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { Context, getRollbarFromContext } from './provider'; + +export class RollbarConfiguration extends Component { + static propTypes = { + options: PropTypes.object.isRequired, + } + + static contextType = Context; + + constructor(props) { + super(props); + this.state = { parentConfig: null }; + } + + componentDidMount() { + const rollbar = getRollbarFromContext(this.context); + const { options } = this.props; + // TODO: need to clone this somehow to prevent downstream changes from manipulating it + const parentConfig = (o => o)(rollbar.options); + this.setState({ parentConfig }); + rollbar.configure(options); + } + + componentWillUnmount() { + const rollbar = getRollbarFromContext(this.context); + const { parentConfig } = this.state; + rollbar.configure(parentConfig); + } + + render() { + return this.props.children; + } +} diff --git a/src/rollbar-context.js b/src/rollbar-context.js new file mode 100644 index 0000000..2345007 --- /dev/null +++ b/src/rollbar-context.js @@ -0,0 +1,65 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +// import Rollbar from 'rollbar'; +import invariant from 'tiny-invariant'; +import { Context, getRollbarFromContext } from './provider'; + +export class RollbarContext extends Component { + static propTypes = { + context: PropTypes.string.isRequired, + onRender: PropTypes.bool, + } + + static defaultProps = { + onRender: false, + } + + static contextType = Context; + + firstRender = true; + + constructor(props) { + super(props); + this.state = { previousContext: null }; + } + + // static getDerivedStateFromProps() {} + + changeContext = (storePrevious = true) => { + const rollbar = getRollbarFromContext(this.context); + const { context } = this.props; + if (storePrevious) { + this.setState({ previousContext: rollbar.options.payload.context }); + } + rollbar.configure({ payload: { context }}); + } + + componentDidMount() { + const { onRender } = this.props; + if (!onRender) { + this.changeContext(true); + } + } + + componentDidUpdate() { + const { onRender } = this.props; + if (!onRender) { + this.changeContext(false); + } + } + + componentWillUnmount() { + const rollbar = getRollbarFromContext(this.context); + const { previousContext } = this.state; + rollbar.configure({ payload: { context: previousContext }}); + } + + render() { + const { onRender } = this.props; + if (onRender && this.firstRender) { + this.changeContext(true); + } + this.firstRender = false; + return this.props.children; + } +} diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..465023a --- /dev/null +++ b/src/utils.js @@ -0,0 +1,10 @@ +export function value(val, defaultTo, ...args) { + if (typeof val === 'function') { + return val(...args); + } + return val; +} + +export function wrapValue(val, defaultAs) { + return (defaultTo, ...args) => value(val, defaultAs === undefined ? defaultTo : defaultAs, ...args); +}