@dynatrace/react-native-plugin 2.341.1 → 2.345.1
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/README.md +674 -668
- package/android/build.gradle +16 -3
- package/android/src/main/java/com/dynatrace/android/agent/DynatraceAppStartModule.kt +9 -31
- package/android/src/main/java/com/dynatrace/android/agent/DynatraceConfigurationModule.kt +1 -0
- package/android/src/main/java/com/dynatrace/android/agent/DynatraceRNBridgeImpl.kt +41 -0
- package/android/src/main/java/com/dynatrace/android/agent/DynatraceUtils.kt +1 -0
- package/android/src/main/java/com/dynatrace/android/agent/ScreenshotSelfMonitor.kt +175 -0
- package/android/src/main/java/com/dynatrace/android/agent/UIChangeScreenshotListener.kt +124 -0
- package/android/src/new/java/com/dynatrace/android/agent/DynatraceRNBridge.kt +33 -0
- package/android/src/old/java/com/dynatrace/android/agent/DynatraceRNBridge.kt +40 -0
- package/files/plugin-runtime.gradle +17 -33
- package/files/plugin.gradle +1 -1
- package/instrumentation/BabelPluginDynatrace.js +1 -1
- package/instrumentation/libs/UserInteraction.js +21 -7
- package/instrumentation/libs/react-navigation/ReactNavigation.js +14 -58
- package/instrumentation/libs/withOnPressMonitoring.js +3 -54
- package/internal.d.ts +1 -0
- package/internal.js +3 -1
- package/ios/DTXScreenshotSelfMonitor.h +29 -0
- package/ios/DTXScreenshotSelfMonitor.mm +213 -0
- package/ios/DTXScreenshotSelfMonitorSwizzler.h +15 -0
- package/ios/DTXScreenshotSelfMonitorSwizzler.mm +68 -0
- package/ios/DynatraceRNBridge.h +6 -0
- package/ios/DynatraceRNBridge.mm +62 -0
- package/lib/core/Dynatrace.js +2 -0
- package/lib/core/configuration/ConfigurationHandler.js +0 -21
- package/lib/core/logging/LogMessages.js +42 -0
- package/lib/dynatrace-reporter.js +1 -30
- package/lib/dynatrace-transformer.js +6 -34
- package/lib/next/Dynatrace.js +26 -1
- package/lib/next/configuration/INativeRuntimeConfiguration.js +0 -1
- package/lib/next/configuration/RuntimeConfigurationObserver.js +1 -6
- package/lib/next/events/EventPipeline.js +48 -31
- package/lib/next/events/HttpRequestEventData.js +1 -1
- package/lib/next/events/spec/EventSpecContstants.js +1 -1
- package/lib/next/navigation/NavigationTracker.js +93 -0
- package/lib/next/navigation/useNavigationTracking.js +54 -0
- package/lib/next/userAction/NullUserAction.js +15 -0
- package/lib/next/userAction/UserActionConfiguration.js +10 -0
- package/lib/next/userAction/UserActionImpl.js +70 -0
- package/lib/next/util/TraceContextUtils.js +8 -14
- package/lib/next/util/Utils.js +13 -0
- package/package.json +10 -18
- package/public.js +5 -1
- package/react-native-dynatrace.podspec +10 -3
- package/scripts/Android.js +1 -20
- package/scripts/Config.js +17 -7
- package/scripts/Deprecations.js +18 -0
- package/scripts/Install.js +13 -0
- package/scripts/PathsConstants.js +0 -4
- package/scripts/Uninstall.js +0 -15
- package/scripts/core/InstrumentCall.js +14 -7
- package/scripts/util/DeprecationReport.js +24 -0
- package/src/lib/core/interface/NativeDynatraceBridge.ts +34 -1
- package/types.d.ts +236 -7
- package/instrumentation/DynatraceInstrumentation.js +0 -1
- package/instrumentation/libs/community/Picker.InstrInfo.js +0 -20
- package/instrumentation/libs/community/gesture-handler/Touchables.InstrInfo.js +0 -25
- package/instrumentation/libs/react-native/RefreshControl.InstrInfo.js +0 -12
- package/instrumentation/libs/react-native/Switch.InstrInfo.js +0 -12
- package/instrumentation/libs/react-native/Touchables.InstrInfo.js +0 -25
- package/instrumentation/parser/Babel.js +0 -60
- package/instrumentation/parser/ParserUtil.js +0 -11
- package/lib/features/ui-interaction/Config.js +0 -42
- package/lib/features/ui-interaction/IUserInteractionEvent.js +0 -16
- package/lib/features/ui-interaction/Plugin.Fragment.Test.js +0 -170
- package/lib/features/ui-interaction/Plugin.js +0 -289
- package/lib/features/ui-interaction/RootDetection.js +0 -51
- package/lib/features/ui-interaction/RootWrapper.js +0 -236
- package/lib/features/ui-interaction/Run.js +0 -38
- package/lib/features/ui-interaction/Runtime.js +0 -843
- package/lib/features/ui-interaction/TouchMetaResolver.js +0 -493
- package/lib/features/ui-interaction/Types.js +0 -14
- package/lib/metro/getSourceMapInfo.js +0 -26
- package/scripts/LineOffsetAnalyze.js +0 -25
- package/scripts/core/LineOffsetAnalyzeCall.js +0 -165
- package/scripts/util/SourceMapUtil.js +0 -68
- /package/{instrumentation/model/Reference.js → lib/next/userAction/UserAction.js} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
var _templateObject,_templateObject2,_templateObject3,_templateObject4,_templateObject5,_templateObject6,_interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault"),_taggedTemplateLiteral2=_interopRequireDefault(require("@babel/runtime/helpers/taggedTemplateLiteral"));function _createForOfIteratorHelper(e,t){var n,r,a,i,o="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(o)return a=!(r=!0),{s:function(){o=o.call(e)},n:function(){var e=o.next();return r=e.done,e},e:function(e){a=!0,n=e},f:function(){try{r||null==o.return||o.return()}finally{if(a)throw n}}};if(Array.isArray(e)||(o=_unsupportedIterableToArray(e))||t&&e&&"number"==typeof e.length)return o&&(e=o),i=0,{s:t=function(){},n:function(){return i>=e.length?{done:!0}:{done:!1,value:e[i++]}},e:function(e){throw e},f:t};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function _unsupportedIterableToArray(e,t){var n;if(e)return"string"==typeof e?_arrayLikeToArray(e,t):"Map"===(n="Object"===(n={}.toString.call(e).slice(8,-1))&&e.constructor?e.constructor.name:n)||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?_arrayLikeToArray(e,t):void 0}function _arrayLikeToArray(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}Object.defineProperty(exports,"__esModule",{value:!0});var reactOptions,fspath=require("path"),fs=require("fs"),core_1=require("@babel/core"),generator_1=require("@babel/generator"),PathsConstants_1=require("../scripts/PathsConstants"),Config_1=require("../scripts/Config"),GetValuesFromPackage_1=require("../lib/core/util/GetValuesFromPackage"),Types_1=require("./model/Types"),INSTRUMENTATION_LIBS="@dynatrace/react-native-plugin/instrumentation/libs",COMPONENTS_EXCLUDED_FROM_UIA=new Set(["Fragment","React.Fragment"]),hasJSX=function(e){var t=!1;return e.traverse({"JSXElement|JSXFragment":function(){t=!0}}),t},instrumentUserInteraction=function(e){e.traverse({"FunctionDeclaration|ObjectMethod":function(e){var e=e.node,t=core_1.types.isFunctionDeclaration(e)?e.id:e.key;core_1.types.isIdentifier(t,{name:"registerComponent"})&&(t="".concat(INSTRUMENTATION_LIBS,"/UserInteraction"),e.body.body.unshift(core_1.template.statement('componentProvider = require("'.concat(t,'").wrapProvider(componentProvider);'))()))}})},instrumentAppRegistry=function(e){e.traverse({"FunctionDeclaration|ObjectMethod":function(e){var e=e.node,t=core_1.types.isFunctionDeclaration(e)?e.id:e.key;core_1.types.isIdentifier(t,{name:"runApplication"})&&e.body.body.unshift(core_1.template.statement(_templateObject=_templateObject||(0,_taggedTemplateLiteral2.default)(['require("@dynatrace/react-native-plugin").ApplicationHandler.startup();']))())}})},instrumentExceptionsManager=function(e){var t,n=null!=(t=null==(t=null==reactOptions?void 0:reactOptions.errorHandler)?void 0:t.reportFatalErrorAsCrash)&&t,r=(null==reactOptions?void 0:reactOptions.autoStart)&&(null==(t=null==reactOptions?void 0:reactOptions.errorHandler)?void 0:t.enabled);e.traverse({FunctionDeclaration:function(e){var t;core_1.types.isIdentifier(e.node.id,{name:"handleException"})&&(t=core_1.template.statement(_templateObject2=_templateObject2||(0,_taggedTemplateLiteral2.default)(['\n setTimeout(() => BODY, require("@dynatrace/react-native-plugin/lib/core/ErrorHandler")\n .reportErrorToDynatrace(e, isFatal, CRASH, AUTO));\n ']))({BODY:core_1.types.cloneNode(e.node.body),CRASH:core_1.types.booleanLiteral(n),AUTO:core_1.types.booleanLiteral(!!r)}),e.node.body.body=[t])}})},instrumentCssInterop=function(e){e.traverse({CallExpression:function(e){core_1.types.isIdentifier(e.node.callee,{name:"require"})&&(e=e.node.arguments[0],core_1.types.isStringLiteral(e))&&("react/jsx-runtime"===e.value?e.value="@dynatrace/react-native-plugin/jsx-runtime":"react/jsx-dev-runtime"===e.value&&(e.value="@dynatrace/react-native-plugin/jsx-dev-runtime"))}})},instrumentNavigation=function(e){e.traverse({VariableDeclarator:function(e){var t;core_1.types.isIdentifier(e.node.id,{name:"getRootState"})&&(t=core_1.template.statement(_templateObject3=_templateObject3||(0,_taggedTemplateLiteral2.default)(["\n require(PATH).monitorNavigation(getRootState);\n "]))({PATH:core_1.types.stringLiteral("".concat(INSTRUMENTATION_LIBS,"/react-navigation/ReactNavigation"))}),e.parentPath.insertAfter(t))}})},instrumentReactCreateElement=function(e){var t=core_1.template.statement(_templateObject4=_templateObject4||(0,_taggedTemplateLiteral2.default)(["\n require(PATH).instrumentCreateElement(module.exports);\n "]))({PATH:core_1.types.stringLiteral("@dynatrace/react-native-plugin/instrumentation/jsx/ElementHelper")});e.pushContainer("body",t)},instrumentComponents=function(e,n){e.traverse({FunctionDeclaration:function(e){var t;hasJSX(e)&&(t=e.node,core_1.types.isIdentifier(t.id))&&null!=(e=e.getStatementParent())&&e.insertAfter(n(t.id.name,Types_1.Types.FunctionalComponent))},ClassDeclaration:function(e){var t;hasJSX(e)&&(t=e.node,core_1.types.isIdentifier(t.id))&&null!=(e=e.getStatementParent())&&e.insertAfter(n(t.id.name,Types_1.Types.ClassComponent))},"FunctionExpression|ArrowFunctionExpression":function(e){var t;hasJSX(e)&&(t=e.parent,void 0!==(t=core_1.types.isVariableDeclarator(t)&&core_1.types.isIdentifier(t.id)?t.id.name:core_1.types.isAssignmentExpression(t)&&core_1.types.isIdentifier(t.left)?t.left.name:void 0))&&null!=(e=e.getStatementParent())&&e.insertAfter(n(t,Types_1.Types.FunctionalComponent))}})},instrumentLifecycle=function(e){instrumentComponents(e,function(e,t){return core_1.template.statement(_templateObject5=_templateObject5||(0,_taggedTemplateLiteral2.default)(["NAME._dtInfo = { type: TYPE, name: 'NAME_STR' }"]))({NAME:core_1.types.identifier(e),TYPE:core_1.types.numericLiteral(t),NAME_STR:core_1.types.stringLiteral(e)})})},instrumentComponentNames=function(e){instrumentComponents(e,function(e){return core_1.template.statement(_templateObject6=_templateObject6||(0,_taggedTemplateLiteral2.default)(["NAME.dtName = 'NAME_STR'"]))({NAME:core_1.types.identifier(e),NAME_STR:core_1.types.stringLiteral(e)})})},instrumentInput=function(e){var u=new Map([["react-native",{proxy:"".concat(INSTRUMENTATION_LIBS,"/react-native/"),components:new Set(["TouchableHighlight","TouchableNativeFeedback","TouchableOpacity","TouchableWithoutFeedback","Button","RefreshControl","Text","Pressable","Switch"])}],["react-native-gesture-handler",{proxy:"".concat(INSTRUMENTATION_LIBS,"/community/gesture-handler/"),components:new Set(["TouchableHighlight","TouchableNativeFeedback","TouchableOpacity","TouchableWithoutFeedback","RectButton","BorderlessButton","BaseButton"])}],["@react-native-picker/picker",{proxy:"".concat(INSTRUMENTATION_LIBS,"/community/Picker"),components:new Set(["Picker","PickerIOS"])}]]);e.traverse({ImportDeclaration:function(e){if(void 0===e.node.processed){var t=e.node.source;if(u.has(t.value)){var r=u.get(t.value),n=e.node.specifiers,a=[],i=[];if(n.forEach(function(e,t){var n;core_1.types.isImportSpecifier(e)&&(n=core_1.types.isIdentifier(e.imported)?e.imported.name:e.imported.value,r.components.has(n)||(a.push(e),i.push(t)))}),a.length!==n.length){var o,s=_createForOfIteratorHelper([].concat(i).reverse());try{for(s.s();!(o=s.n()).done;){var c=o.value;n.splice(c,1)}}catch(e){s.e(e)}finally{s.f()}0<a.length&&((t=core_1.types.importDeclaration(a,core_1.types.stringLiteral(t.value))).processed=!0,e.insertBefore(t)),e.node.source=core_1.types.stringLiteral(r.proxy),e.scope.crawl()}}}},CallExpression:function(e){core_1.types.isIdentifier(e.node.callee,{name:"require"})&&(e=e.node.arguments[0],core_1.types.isStringLiteral(e))&&u.has(e.value)&&(e.value=u.get(e.value).proxy)}})},instrumentJsxNames=function(e){e.traverse({JSXOpeningElement:function(e){var t=e.node.name,t=core_1.types.isJSXIdentifier(t)?t.name:core_1.types.isJSXMemberExpression(t)?(0,generator_1.default)(t).code:void 0;t&&!COMPONENTS_EXCLUDED_FROM_UIA.has(t)&&e.node.attributes.unshift(core_1.types.jsxAttribute(core_1.types.jsxIdentifier("dtName"),core_1.types.stringLiteral(t)))}})},instrumentConfigurationPreset=function(e){var t,n=(0,GetValuesFromPackage_1.getHostAppBundleInfo)(PathsConstants_1.default.getPackageJsonFile()),r={getLifecycleUpdate:reactOptions.lifecycle.includeUpdate,getLogLevel:reactOptions.debug?0:1,getBundleName:null!=(t=reactOptions.bundleName)?t:null==n?void 0:n.name,getBundleVersion:null!=(t=reactOptions.bundleVersion)?t:null==n?void 0:n.version,getActionNamePrivacy:reactOptions.input.actionNamePrivacy,getActionNamePreference:reactOptions.input.actionNamePreference,getActionNameAlgorithm:reactOptions.input.actionNameAlgorithm,isErrorHandlerEnabled:reactOptions.errorHandler.enabled,isReportFatalErrorAsCrash:reactOptions.errorHandler.reportFatalErrorAsCrash,isAutoStartupEnabled:reactOptions.autoStart};e.traverse({ClassMethod:function(e){var t;core_1.types.isIdentifier(e.node.key)&&void 0!==(t=r[e.node.key.name])&&(e=e.node.body.body[0],core_1.types.isReturnStatement(e))&&(e.argument=core_1.types.valueToNode(t))}})},createFilenameMatcher=function(r){return{isModule:function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return t.some(function(e){return r.includes(fspath.join("node_modules",e)+fspath.sep)})},isFile:function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return t.some(function(e){return r.endsWith(e)})}}},captureOriginalCode=function(e){return!0!==reactOptions.debugBabelPlugin?"":(0,generator_1.default)(e.node).code},isConfigurationPresetTarget=function(e){return e.isModule("@dynatrace")&&e.isFile("ConfigurationPreset.js")},isAppRegistryTarget=function(e){return e.isModule("react-native")&&e.isFile("AppRegistry.js","AppRegistryImpl.js")},isExceptionsManagerTarget=function(e){return e.isModule("react-native")&&e.isFile("ExceptionsManager.js")},isCssInteropTarget=function(e){return e.isModule("react-native-css-interop")&&e.isFile("jsx-runtime.js","jsx-dev-runtime.js")},isNavigationTarget=function(e){return reactOptions.navigation.enabled&&e.isModule("@react-navigation")&&e.isFile("BaseNavigationContainer.js","BaseNavigationContainer.tsx")},isReactIndexTarget=function(e){return e.isModule("react")&&e.isFile("index.js")},shouldInstrumentLifecycleForFile=function(e,t,n){var r,a,n=n.isModule("react-native")&&n.isFile("renderApplication.js");return(null==(a=null==(r=reactOptions.lifecycle)?void 0:r.instrument)?void 0:a.call(r,t))&&(!t.includes("node_modules")||n)&&hasJSX(e)},shouldInstrumentInputForFile=function(e,t){var n,r,t=t.isModule("@react-navigation","react-native-drawer-layout");return(null==(r=null==(n=reactOptions.input)?void 0:n.instrument)?void 0:r.call(n,e))&&(!e.includes("node_modules")||t)},shouldInstrumentUserInteractionNames=function(e){return reactOptions.userInteraction&&!e.includes("node_modules")},instrumentByModuleAndFile=function(e,t,n){isConfigurationPresetTarget(n)&&instrumentConfigurationPreset(e),isAppRegistryTarget(n)&&(reactOptions.autoStart&&instrumentAppRegistry(e),reactOptions.userInteraction)&&instrumentUserInteraction(e),isExceptionsManagerTarget(n)&&instrumentExceptionsManager(e),isCssInteropTarget(n)&&instrumentCssInterop(e),isNavigationTarget(n)&&instrumentNavigation(e),isReactIndexTarget(n)&&instrumentReactCreateElement(e),shouldInstrumentLifecycleForFile(e,t,n)&&instrumentLifecycle(e),shouldInstrumentInputForFile(t,n)&&instrumentInput(e),shouldInstrumentUserInteractionNames(t)&&(instrumentJsxNames(e),instrumentComponentNames(e))},writeDebugOutputIfChanged=function(e,t,n,r){!0===reactOptions.debugBabelPlugin&&(e=(0,generator_1.default)(e.node).code)!==r&&(r=fspath.join(PathsConstants_1.default.getBuildPath(),fspath.relative(PathsConstants_1.default.getApplicationPath(),n)+".dtx"),fs.mkdirSync(fspath.dirname(r),{recursive:!0}),fs.writeFileSync(r,e),t.set("fileDidChange",!0))};exports.default=function(){return{visitor:{Program:function(e,t){reactOptions=(0,Config_1.readConfigDefault)().react;var n,r,a=t.filename;void 0!==a&&(n=createFilenameMatcher(a),r=captureOriginalCode(e),instrumentByModuleAndFile(e,a,n),writeDebugOutputIfChanged(e,t,a,r))}},post:function(e){var t;!0===reactOptions.debugBabelPlugin&&!0===this.get("fileDidChange")&&(t=(0,generator_1.default)(e.ast).code,e=fspath.join(PathsConstants_1.default.getBuildPath(),fspath.relative(PathsConstants_1.default.getApplicationPath(),e.opts.filename)+".dtx.downstream"),fs.mkdirSync(fspath.dirname(e),{recursive:!0}),fs.writeFileSync(e,t))}}};
|
|
1
|
+
var _templateObject,_templateObject2,_templateObject3,_templateObject4,_templateObject5,_templateObject6,_interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault"),_taggedTemplateLiteral2=_interopRequireDefault(require("@babel/runtime/helpers/taggedTemplateLiteral"));function _createForOfIteratorHelper(e,t){var n,r,a,i,o="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(o)return a=!(r=!0),{s:function(){o=o.call(e)},n:function(){var e=o.next();return r=e.done,e},e:function(e){a=!0,n=e},f:function(){try{r||null==o.return||o.return()}finally{if(a)throw n}}};if(Array.isArray(e)||(o=_unsupportedIterableToArray(e))||t&&e&&"number"==typeof e.length)return o&&(e=o),i=0,{s:t=function(){},n:function(){return i>=e.length?{done:!0}:{done:!1,value:e[i++]}},e:function(e){throw e},f:t};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function _unsupportedIterableToArray(e,t){var n;if(e)return"string"==typeof e?_arrayLikeToArray(e,t):"Map"===(n="Object"===(n={}.toString.call(e).slice(8,-1))&&e.constructor?e.constructor.name:n)||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?_arrayLikeToArray(e,t):void 0}function _arrayLikeToArray(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}Object.defineProperty(exports,"__esModule",{value:!0});var reactOptions,fspath=require("path"),fs=require("fs"),core_1=require("@babel/core"),generator_1=require("@babel/generator"),PathsConstants_1=require("../scripts/PathsConstants"),Config_1=require("../scripts/Config"),GetValuesFromPackage_1=require("../lib/core/util/GetValuesFromPackage"),Types_1=require("./model/Types"),INSTRUMENTATION_LIBS="@dynatrace/react-native-plugin/instrumentation/libs",COMPONENTS_EXCLUDED_FROM_UIA=new Set(["Fragment","React.Fragment"]),hasJSX=function(e){var t=!1;return e.traverse({"JSXElement|JSXFragment":function(){t=!0}}),t},instrumentUserInteraction=function(e){e.traverse({"FunctionDeclaration|ObjectMethod":function(e){var e=e.node,t=core_1.types.isFunctionDeclaration(e)?e.id:e.key;core_1.types.isIdentifier(t,{name:"registerComponent"})&&(t="".concat(INSTRUMENTATION_LIBS,"/UserInteraction"),e.body.body.unshift(core_1.template.statement('componentProvider = require("'.concat(t,'").wrapProvider(componentProvider);'))()))}})},instrumentAppRegistry=function(e){e.traverse({"FunctionDeclaration|ObjectMethod":function(e){var e=e.node,t=core_1.types.isFunctionDeclaration(e)?e.id:e.key;core_1.types.isIdentifier(t,{name:"runApplication"})&&e.body.body.unshift(core_1.template.statement(_templateObject=_templateObject||(0,_taggedTemplateLiteral2.default)(['require("@dynatrace/react-native-plugin").ApplicationHandler.startup();']))())}})},instrumentExceptionsManager=function(e){var t,n=null!=(t=null==(t=null==reactOptions?void 0:reactOptions.errorHandler)?void 0:t.reportFatalErrorAsCrash)&&t,r=(null==reactOptions?void 0:reactOptions.autoStart)&&(null==(t=null==reactOptions?void 0:reactOptions.errorHandler)?void 0:t.enabled);e.traverse({FunctionDeclaration:function(e){var t;core_1.types.isIdentifier(e.node.id,{name:"handleException"})&&(t=core_1.template.statement(_templateObject2=_templateObject2||(0,_taggedTemplateLiteral2.default)(['\n setTimeout(() => BODY, require("@dynatrace/react-native-plugin/lib/core/ErrorHandler")\n .reportErrorToDynatrace(e, isFatal, CRASH, AUTO));\n ']))({BODY:core_1.types.cloneNode(e.node.body),CRASH:core_1.types.booleanLiteral(n),AUTO:core_1.types.booleanLiteral(!!r)}),e.node.body.body=[t])}})},instrumentCssInterop=function(e){e.traverse({CallExpression:function(e){core_1.types.isIdentifier(e.node.callee,{name:"require"})&&(e=e.node.arguments[0],core_1.types.isStringLiteral(e))&&("react/jsx-runtime"===e.value?e.value="@dynatrace/react-native-plugin/jsx-runtime":"react/jsx-dev-runtime"===e.value&&(e.value="@dynatrace/react-native-plugin/jsx-dev-runtime"))}})},instrumentNavigation=function(e){e.traverse({VariableDeclarator:function(e){var t;core_1.types.isIdentifier(e.node.id,{name:"getRootState"})&&(t=core_1.template.statement(_templateObject3=_templateObject3||(0,_taggedTemplateLiteral2.default)(["\n require(PATH).useNavigationMonitor(getRootState);\n "]))({PATH:core_1.types.stringLiteral("".concat(INSTRUMENTATION_LIBS,"/react-navigation/ReactNavigation"))}),e.parentPath.insertAfter(t))}})},instrumentReactCreateElement=function(e){var t=core_1.template.statement(_templateObject4=_templateObject4||(0,_taggedTemplateLiteral2.default)(["\n require(PATH).instrumentCreateElement(module.exports);\n "]))({PATH:core_1.types.stringLiteral("@dynatrace/react-native-plugin/instrumentation/jsx/ElementHelper")});e.pushContainer("body",t)},instrumentComponents=function(e,n){e.traverse({FunctionDeclaration:function(e){var t;hasJSX(e)&&(t=e.node,core_1.types.isIdentifier(t.id))&&null!=(e=e.getStatementParent())&&e.insertAfter(n(t.id.name,Types_1.Types.FunctionalComponent))},ClassDeclaration:function(e){var t;hasJSX(e)&&(t=e.node,core_1.types.isIdentifier(t.id))&&null!=(e=e.getStatementParent())&&e.insertAfter(n(t.id.name,Types_1.Types.ClassComponent))},"FunctionExpression|ArrowFunctionExpression":function(e){var t;hasJSX(e)&&(t=e.parent,void 0!==(t=core_1.types.isVariableDeclarator(t)&&core_1.types.isIdentifier(t.id)?t.id.name:core_1.types.isAssignmentExpression(t)&&core_1.types.isIdentifier(t.left)?t.left.name:void 0))&&null!=(e=e.getStatementParent())&&e.insertAfter(n(t,Types_1.Types.FunctionalComponent))}})},instrumentLifecycle=function(e){instrumentComponents(e,function(e,t){return core_1.template.statement(_templateObject5=_templateObject5||(0,_taggedTemplateLiteral2.default)(["NAME._dtInfo = { type: TYPE, name: 'NAME_STR' }"]))({NAME:core_1.types.identifier(e),TYPE:core_1.types.numericLiteral(t),NAME_STR:core_1.types.stringLiteral(e)})})},instrumentComponentNames=function(e){instrumentComponents(e,function(e){return core_1.template.statement(_templateObject6=_templateObject6||(0,_taggedTemplateLiteral2.default)(["NAME.dtName = 'NAME_STR'"]))({NAME:core_1.types.identifier(e),NAME_STR:core_1.types.stringLiteral(e)})})},instrumentInput=function(e){var u=new Map([["react-native",{proxy:"".concat(INSTRUMENTATION_LIBS,"/react-native/"),components:new Set(["TouchableHighlight","TouchableNativeFeedback","TouchableOpacity","TouchableWithoutFeedback","Button","RefreshControl","Text","Pressable","Switch"])}],["react-native-gesture-handler",{proxy:"".concat(INSTRUMENTATION_LIBS,"/community/gesture-handler/"),components:new Set(["TouchableHighlight","TouchableNativeFeedback","TouchableOpacity","TouchableWithoutFeedback","RectButton","BorderlessButton","BaseButton"])}],["@react-native-picker/picker",{proxy:"".concat(INSTRUMENTATION_LIBS,"/community/Picker"),components:new Set(["Picker","PickerIOS"])}]]);e.traverse({ImportDeclaration:function(e){if(void 0===e.node.processed){var t=e.node.source;if(u.has(t.value)){var r=u.get(t.value),n=e.node.specifiers,a=[],i=[];if(n.forEach(function(e,t){var n;core_1.types.isImportSpecifier(e)&&(n=core_1.types.isIdentifier(e.imported)?e.imported.name:e.imported.value,r.components.has(n)||(a.push(e),i.push(t)))}),a.length!==n.length){var o,s=_createForOfIteratorHelper([].concat(i).reverse());try{for(s.s();!(o=s.n()).done;){var c=o.value;n.splice(c,1)}}catch(e){s.e(e)}finally{s.f()}0<a.length&&((t=core_1.types.importDeclaration(a,core_1.types.stringLiteral(t.value))).processed=!0,e.insertBefore(t)),e.node.source=core_1.types.stringLiteral(r.proxy),e.scope.crawl()}}}},CallExpression:function(e){core_1.types.isIdentifier(e.node.callee,{name:"require"})&&(e=e.node.arguments[0],core_1.types.isStringLiteral(e))&&u.has(e.value)&&(e.value=u.get(e.value).proxy)}})},instrumentJsxNames=function(e){e.traverse({JSXOpeningElement:function(e){var t=e.node.name,t=core_1.types.isJSXIdentifier(t)?t.name:core_1.types.isJSXMemberExpression(t)?(0,generator_1.default)(t).code:void 0;t&&!COMPONENTS_EXCLUDED_FROM_UIA.has(t)&&e.node.attributes.unshift(core_1.types.jsxAttribute(core_1.types.jsxIdentifier("dtName"),core_1.types.stringLiteral(t)))}})},instrumentConfigurationPreset=function(e){var t,n=(0,GetValuesFromPackage_1.getHostAppBundleInfo)(PathsConstants_1.default.getPackageJsonFile()),r={getLifecycleUpdate:reactOptions.lifecycle.includeUpdate,getLogLevel:reactOptions.debug?0:1,getBundleName:null!=(t=reactOptions.bundleName)?t:null==n?void 0:n.name,getBundleVersion:null!=(t=reactOptions.bundleVersion)?t:null==n?void 0:n.version,getActionNamePrivacy:reactOptions.input.actionNamePrivacy,getActionNamePreference:reactOptions.input.actionNamePreference,getActionNameAlgorithm:reactOptions.input.actionNameAlgorithm,isErrorHandlerEnabled:reactOptions.errorHandler.enabled,isReportFatalErrorAsCrash:reactOptions.errorHandler.reportFatalErrorAsCrash,isAutoStartupEnabled:reactOptions.autoStart};e.traverse({ClassMethod:function(e){var t;core_1.types.isIdentifier(e.node.key)&&void 0!==(t=r[e.node.key.name])&&(e=e.node.body.body[0],core_1.types.isReturnStatement(e))&&(e.argument=core_1.types.valueToNode(t))}})},createFilenameMatcher=function(r){return{isModule:function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return t.some(function(e){return r.includes(fspath.join("node_modules",e)+fspath.sep)})},isFile:function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return t.some(function(e){return r.endsWith(e)})}}},captureOriginalCode=function(e){return!0!==reactOptions.debugBabelPlugin?"":(0,generator_1.default)(e.node).code},isConfigurationPresetTarget=function(e){return e.isModule("@dynatrace")&&e.isFile("ConfigurationPreset.js")},isAppRegistryTarget=function(e){return e.isModule("react-native")&&e.isFile("AppRegistry.js","AppRegistryImpl.js")},isExceptionsManagerTarget=function(e){return e.isModule("react-native")&&e.isFile("ExceptionsManager.js")},isCssInteropTarget=function(e){return e.isModule("react-native-css-interop")&&e.isFile("jsx-runtime.js","jsx-dev-runtime.js")},isNavigationTarget=function(e){return reactOptions.navigation.enabled&&e.isModule("@react-navigation")&&e.isFile("BaseNavigationContainer.js","BaseNavigationContainer.tsx")},isReactIndexTarget=function(e){return e.isModule("react")&&e.isFile("index.js")},shouldInstrumentLifecycleForFile=function(e,t,n){var r,a,n=n.isModule("react-native")&&n.isFile("renderApplication.js");return(null==(a=null==(r=reactOptions.lifecycle)?void 0:r.instrument)?void 0:a.call(r,t))&&(!t.includes("node_modules")||n)&&hasJSX(e)},shouldInstrumentInputForFile=function(e,t){var n,r,t=t.isModule("@react-navigation","react-native-drawer-layout");return(null==(r=null==(n=reactOptions.input)?void 0:n.instrument)?void 0:r.call(n,e))&&(!e.includes("node_modules")||t)},shouldInstrumentUserInteractionNames=function(e){return reactOptions.userInteraction&&!e.includes("node_modules")},instrumentByModuleAndFile=function(e,t,n){isConfigurationPresetTarget(n)&&instrumentConfigurationPreset(e),isAppRegistryTarget(n)&&(reactOptions.autoStart&&instrumentAppRegistry(e),reactOptions.userInteraction)&&instrumentUserInteraction(e),isExceptionsManagerTarget(n)&&instrumentExceptionsManager(e),isCssInteropTarget(n)&&instrumentCssInterop(e),isNavigationTarget(n)&&instrumentNavigation(e),isReactIndexTarget(n)&&instrumentReactCreateElement(e),shouldInstrumentLifecycleForFile(e,t,n)&&instrumentLifecycle(e),shouldInstrumentInputForFile(t,n)&&instrumentInput(e),shouldInstrumentUserInteractionNames(t)&&(instrumentJsxNames(e),instrumentComponentNames(e))},writeDebugOutputIfChanged=function(e,t,n,r){!0===reactOptions.debugBabelPlugin&&(e=(0,generator_1.default)(e.node).code)!==r&&(r=fspath.join(PathsConstants_1.default.getBuildPath(),fspath.relative(PathsConstants_1.default.getApplicationPath(),n)+".dtx"),fs.mkdirSync(fspath.dirname(r),{recursive:!0}),fs.writeFileSync(r,e),t.set("fileDidChange",!0))};exports.default=function(){return{visitor:{Program:function(e,t){reactOptions=(0,Config_1.readConfigDefault)().react;var n,r,a=t.filename;void 0!==a&&(n=createFilenameMatcher(a),r=captureOriginalCode(e),instrumentByModuleAndFile(e,a,n),writeDebugOutputIfChanged(e,t,a,r))}},post:function(e){var t;!0===reactOptions.debugBabelPlugin&&!0===this.get("fileDidChange")&&(t=(0,generator_1.default)(e.ast).code,e=fspath.join(PathsConstants_1.default.getBuildPath(),fspath.relative(PathsConstants_1.default.getApplicationPath(),e.opts.filename)+".dtx.downstream"),fs.mkdirSync(fspath.dirname(e),{recursive:!0}),fs.writeFileSync(e,t))}}};
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RootWrapper = exports.wrapProvider = exports.notifyPress = void 0;
|
|
3
|
+
exports.RootWrapper = exports.wrapProvider = exports.notifyPress = exports.UserInteractionEventKey = void 0;
|
|
4
4
|
const React = require("react");
|
|
5
5
|
const react_native_1 = require("react-native");
|
|
6
|
-
const ConfigurationHandler_1 = require("../../lib/core/configuration/ConfigurationHandler");
|
|
7
6
|
const EventPipeline_1 = require("../../lib/next/events/EventPipeline");
|
|
8
7
|
const EventTimestamp_1 = require("../../lib/next/events/EventTimestamp");
|
|
9
8
|
const TimestampProvider_1 = require("../../lib/next/provider/TimestampProvider");
|
|
9
|
+
const ConfigurationHandler_1 = require("../../lib/core/configuration/ConfigurationHandler");
|
|
10
|
+
var UserInteractionEventKey;
|
|
11
|
+
(function (UserInteractionEventKey) {
|
|
12
|
+
UserInteractionEventKey["EventHasUserInteraction"] = "characteristics.has_user_interaction";
|
|
13
|
+
UserInteractionEventKey["UiElementDetectedName"] = "ui_element.detected_name";
|
|
14
|
+
UserInteractionEventKey["UiElementComponents"] = "ui_element.components";
|
|
15
|
+
UserInteractionEventKey["UiElementId"] = "ui_element.id";
|
|
16
|
+
UserInteractionEventKey["UiElementNameOrigin"] = "ui_element.name_origin";
|
|
17
|
+
UserInteractionEventKey["InteractionType"] = "interaction.type";
|
|
18
|
+
UserInteractionEventKey["UiElementResponderDetectedName"] = "ui_element.responder.detected_name";
|
|
19
|
+
UserInteractionEventKey["UiElementResponderComponents"] = "ui_element.responder.components";
|
|
20
|
+
UserInteractionEventKey["UiElementResponderOriginName"] = "ui_element.responder.name_origin";
|
|
21
|
+
UserInteractionEventKey["UiElementResponderId"] = "ui_element.responder.id";
|
|
22
|
+
})(UserInteractionEventKey = exports.UserInteractionEventKey || (exports.UserInteractionEventKey = {}));
|
|
10
23
|
const TOUCH_FLUSH_DELAY = 250;
|
|
11
24
|
let pendingTouch = null;
|
|
12
25
|
const getFiberPath = (fiber) => {
|
|
@@ -43,7 +56,7 @@ function findRCTTextChild(fiber) {
|
|
|
43
56
|
return null;
|
|
44
57
|
}
|
|
45
58
|
const getHostComponentInfo = (event) => {
|
|
46
|
-
var _a, _b;
|
|
59
|
+
var _a, _b, _c, _d, _e;
|
|
47
60
|
let fiber = event._targetInst;
|
|
48
61
|
if (!fiber) {
|
|
49
62
|
console.warn('Assumption violated: _targetInst not found');
|
|
@@ -64,7 +77,7 @@ const getHostComponentInfo = (event) => {
|
|
|
64
77
|
let detectedName;
|
|
65
78
|
if (fiber.type !== 'RCTText') {
|
|
66
79
|
nameOrigin = 'component';
|
|
67
|
-
detectedName = (_b = fiber.memoizedProps.accessibilityLabel) !== null &&
|
|
80
|
+
detectedName = (_c = (_b = fiber.memoizedProps) === null || _b === void 0 ? void 0 : _b.accessibilityLabel) !== null && _c !== void 0 ? _c : leaf;
|
|
68
81
|
}
|
|
69
82
|
else if (anyParentMasked) {
|
|
70
83
|
nameOrigin = 'masked';
|
|
@@ -72,7 +85,7 @@ const getHostComponentInfo = (event) => {
|
|
|
72
85
|
}
|
|
73
86
|
else {
|
|
74
87
|
nameOrigin = 'text';
|
|
75
|
-
detectedName = flatten(fiber.memoizedProps.children);
|
|
88
|
+
detectedName = flatten((_e = (_d = fiber.memoizedProps) === null || _d === void 0 ? void 0 : _d.children) !== null && _e !== void 0 ? _e : '');
|
|
76
89
|
}
|
|
77
90
|
return { id, components, detectedName, nameOrigin };
|
|
78
91
|
};
|
|
@@ -83,7 +96,7 @@ const flushTouch = (responderComponentInfo) => {
|
|
|
83
96
|
clearTimeout(timeoutId);
|
|
84
97
|
pendingTouch = null;
|
|
85
98
|
const eventTimestamp = new EventTimestamp_1.EventTimestamp(TimestampProvider_1.defaultTimestampProvider);
|
|
86
|
-
const event = Object.assign(Object.assign({ 'characteristics.has_user_interaction': true, 'ui_element.detected_name': touchedComponentInfo.detectedName, 'ui_element.components': touchedComponentInfo.components, 'ui_element.id': touchedComponentInfo.id, 'interaction.
|
|
99
|
+
const event = Object.assign(Object.assign({ 'characteristics.has_user_interaction': true, 'ui_element.detected_name': touchedComponentInfo.detectedName, 'ui_element.components': touchedComponentInfo.components, 'ui_element.id': touchedComponentInfo.id, 'interaction.type': 'touch', positions, 'ui_element.name_origin': touchedComponentInfo.nameOrigin }, (responderComponentInfo && {
|
|
87
100
|
'ui_element.responder.detected_name': responderComponentInfo.detectedName,
|
|
88
101
|
'ui_element.responder.components': responderComponentInfo.components,
|
|
89
102
|
'ui_element.responder.id': responderComponentInfo.id,
|
|
@@ -92,8 +105,9 @@ const flushTouch = (responderComponentInfo) => {
|
|
|
92
105
|
EventPipeline_1.EventPipeline.insertEvent(event);
|
|
93
106
|
};
|
|
94
107
|
const onTouch = (e) => {
|
|
95
|
-
if (!ConfigurationHandler_1.ConfigurationHandler.
|
|
108
|
+
if (!ConfigurationHandler_1.ConfigurationHandler.getThirdGenDataReportingDecision().allowed) {
|
|
96
109
|
return;
|
|
110
|
+
}
|
|
97
111
|
flushTouch();
|
|
98
112
|
const touchedComponentInfo = getHostComponentInfo(e);
|
|
99
113
|
if (!touchedComponentInfo)
|
|
@@ -1,74 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.useNavigationMonitor = void 0;
|
|
4
4
|
const react_1 = require("react");
|
|
5
5
|
const react_native_1 = require("react-native");
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const DynatraceBridge_1 = require("../../../lib/core/DynatraceBridge");
|
|
9
|
-
const BaseDataEventModifier_1 = require("../../../lib/next/events/modifier/BaseDataEventModifier");
|
|
10
|
-
const ConfigurationHandler_1 = require("../../../lib/core/configuration/ConfigurationHandler");
|
|
11
|
-
const logger = new ConsoleLogger_1.ConsoleLogger('ReactNavigation');
|
|
12
|
-
let activePath = '';
|
|
13
|
-
let appState = react_native_1.AppState.currentState;
|
|
14
|
-
const monitorNavigation = (getRootState) => {
|
|
6
|
+
const NavigationTracker_1 = require("../../../lib/next/navigation/NavigationTracker");
|
|
7
|
+
const useNavigationMonitor = (getRootState) => {
|
|
15
8
|
(0, react_1.useEffect)(() => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
let newPath = '';
|
|
19
|
-
while (currentState === null || currentState === void 0 ? void 0 : currentState.routes[(_a = currentState.index) !== null && _a !== void 0 ? _a : 0]) {
|
|
20
|
-
const currentRoute = currentState === null || currentState === void 0 ? void 0 : currentState.routes[(_b = currentState.index) !== null && _b !== void 0 ? _b : 0];
|
|
21
|
-
newPath += `/${currentRoute.name}`;
|
|
22
|
-
currentState = currentRoute.state;
|
|
23
|
-
}
|
|
24
|
-
if (newPath !== activePath) {
|
|
25
|
-
if (activePath !== '') {
|
|
26
|
-
stopView();
|
|
27
|
-
}
|
|
28
|
-
startView(newPath);
|
|
29
|
-
activePath = newPath;
|
|
30
|
-
}
|
|
9
|
+
const newPath = (0, NavigationTracker_1.buildPathFromState)(getRootState());
|
|
10
|
+
(0, NavigationTracker_1.reportScreenChange)(newPath);
|
|
31
11
|
});
|
|
12
|
+
const appStateRef = (0, react_1.useRef)(react_native_1.AppState.currentState);
|
|
32
13
|
(0, react_1.useEffect)(() => {
|
|
33
14
|
const subscription = react_native_1.AppState.addEventListener('change', (nextAppState) => {
|
|
34
|
-
|
|
35
|
-
|
|
15
|
+
const prevAppState = appStateRef.current;
|
|
16
|
+
appStateRef.current = nextAppState;
|
|
17
|
+
if (nextAppState !== 'active' && prevAppState === 'active') {
|
|
18
|
+
(0, NavigationTracker_1.reportScreenBackground)();
|
|
36
19
|
}
|
|
37
|
-
else if (nextAppState === 'active' &&
|
|
38
|
-
|
|
20
|
+
else if (nextAppState === 'active' &&
|
|
21
|
+
prevAppState !== 'active') {
|
|
22
|
+
(0, NavigationTracker_1.reportScreenResume)();
|
|
39
23
|
}
|
|
40
|
-
appState = nextAppState;
|
|
41
24
|
});
|
|
42
25
|
return () => {
|
|
43
26
|
subscription.remove();
|
|
44
27
|
};
|
|
45
28
|
}, []);
|
|
46
29
|
};
|
|
47
|
-
exports.
|
|
48
|
-
const startView = (newPath) => {
|
|
49
|
-
if (!ConfigurationHandler_1.ConfigurationHandler.isConfigurationAvailable()) {
|
|
50
|
-
logger.info(LogMessages_1.LogMessage.NAVIGATION_NOT_STARTED);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
const startViewInternalEvent = new BaseDataEventModifier_1.BaseDataEventModifier().modifyEvent(Object.assign({ 'view.detected_name': newPath }, (activePath && {
|
|
54
|
-
'view.source.detected_name': activePath,
|
|
55
|
-
})));
|
|
56
|
-
const fn = DynatraceBridge_1.DynatraceNative === null || DynatraceBridge_1.DynatraceNative === void 0 ? void 0 : DynatraceBridge_1.DynatraceNative.startViewInternal;
|
|
57
|
-
if (typeof fn === 'function') {
|
|
58
|
-
fn(startViewInternalEvent);
|
|
59
|
-
}
|
|
60
|
-
logger.debug(LogMessages_1.LogMessage.START_VIEW_INTERNAL, {
|
|
61
|
-
event: JSON.stringify(startViewInternalEvent),
|
|
62
|
-
});
|
|
63
|
-
};
|
|
64
|
-
const stopView = () => {
|
|
65
|
-
if (!ConfigurationHandler_1.ConfigurationHandler.isConfigurationAvailable()) {
|
|
66
|
-
logger.info(LogMessages_1.LogMessage.NAVIGATION_NOT_STARTED);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
const fn = DynatraceBridge_1.DynatraceNative === null || DynatraceBridge_1.DynatraceNative === void 0 ? void 0 : DynatraceBridge_1.DynatraceNative.stopViewInternal;
|
|
70
|
-
if (typeof fn === 'function') {
|
|
71
|
-
fn();
|
|
72
|
-
}
|
|
73
|
-
logger.debug(LogMessages_1.LogMessage.STOP_VIEW_INTERNAL);
|
|
74
|
-
};
|
|
30
|
+
exports.useNavigationMonitor = useNavigationMonitor;
|
|
@@ -15,68 +15,18 @@ function withOnPressMonitoring(Component, fallbackName) {
|
|
|
15
15
|
const { onPress, onLongPress } = props;
|
|
16
16
|
const touchableName = (0, exports.chooseTouchableName)(props, fallbackName);
|
|
17
17
|
const dynatraceIgnored = (0, ComponentUtil_1.isDynatraceIgnored)(props);
|
|
18
|
-
const isRNButton = 'title' in props && typeof props.title === 'string';
|
|
19
|
-
const isRectButton = fallbackName === 'Button';
|
|
20
|
-
const isButtonLike = isRNButton || isRectButton;
|
|
21
|
-
const buttonLabel = isRNButton ? String(props.title) : null;
|
|
22
18
|
const propsWithoutRef = Object.assign(Object.assign(Object.assign({}, props), (!dynatraceIgnored &&
|
|
23
19
|
onPress && {
|
|
24
|
-
onPress: (0, exports.wrapOnPress)(onPress, touchableName
|
|
20
|
+
onPress: (0, exports.wrapOnPress)(onPress, touchableName),
|
|
25
21
|
})), (!dynatraceIgnored &&
|
|
26
22
|
onLongPress && {
|
|
27
|
-
onLongPress: (0, exports.wrapOnPress)(onLongPress, touchableName
|
|
23
|
+
onLongPress: (0, exports.wrapOnPress)(onLongPress, touchableName),
|
|
28
24
|
}));
|
|
29
25
|
const propsWithRef = Object.assign(Object.assign({}, propsWithoutRef), { ref });
|
|
30
26
|
return React.createElement(Component, Object.assign({}, propsWithRef));
|
|
31
27
|
});
|
|
32
28
|
}
|
|
33
29
|
exports.withOnPressMonitoring = withOnPressMonitoring;
|
|
34
|
-
const dtGetUiBridge = () => {
|
|
35
|
-
var _a;
|
|
36
|
-
const g = globalThis;
|
|
37
|
-
return (_a = g === null || g === void 0 ? void 0 : g.__DT_UII_BRIDGE) !== null && _a !== void 0 ? _a : null;
|
|
38
|
-
};
|
|
39
|
-
const dtBuildPressPath = (startPath, touchableName) => {
|
|
40
|
-
var _a;
|
|
41
|
-
const base = (_a = startPath === null || startPath === void 0 ? void 0 : startPath.toString()) !== null && _a !== void 0 ? _a : '';
|
|
42
|
-
if (!base)
|
|
43
|
-
return `Touchable(${touchableName})`;
|
|
44
|
-
const cut1 = base.lastIndexOf('/Text');
|
|
45
|
-
const cut2 = base.lastIndexOf('/Text(');
|
|
46
|
-
const cut = Math.max(cut1, cut2);
|
|
47
|
-
const parent = cut > 0 ? base.slice(0, cut) : base;
|
|
48
|
-
return parent + `/Touchable(${touchableName})`;
|
|
49
|
-
};
|
|
50
|
-
const registerPressOnBridge = (args, touchableName, isButtonLike, buttonLabel) => {
|
|
51
|
-
const bridge = dtGetUiBridge();
|
|
52
|
-
if (!bridge || typeof bridge.registerPress !== 'function') {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
try {
|
|
56
|
-
const e = args === null || args === void 0 ? void 0 : args[0];
|
|
57
|
-
const ne = e === null || e === void 0 ? void 0 : e.nativeEvent;
|
|
58
|
-
const x = Number(ne === null || ne === void 0 ? void 0 : ne.pageX);
|
|
59
|
-
const y = Number(ne === null || ne === void 0 ? void 0 : ne.pageY);
|
|
60
|
-
const pos = Number.isFinite(x) && Number.isFinite(y) ? { x, y } : undefined;
|
|
61
|
-
const startPath = typeof bridge.getStartPath === 'function'
|
|
62
|
-
? bridge.getStartPath()
|
|
63
|
-
: null;
|
|
64
|
-
const nameForBridge = isButtonLike
|
|
65
|
-
? buttonLabel || touchableName
|
|
66
|
-
: touchableName;
|
|
67
|
-
bridge.registerPress({
|
|
68
|
-
path: dtBuildPressPath(startPath, nameForBridge),
|
|
69
|
-
elementName: nameForBridge,
|
|
70
|
-
pos,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
catch (e) {
|
|
74
|
-
const errorMsg = e instanceof Error ? e.stack || e.message : String(e);
|
|
75
|
-
Logger.debug(LogMessages_1.LogMessage.REGISTER_PRESS_HOOK_FAILED, {
|
|
76
|
-
errorMsg,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
30
|
const notifyPressFromEvent = (args) => {
|
|
81
31
|
if (args[0] && typeof args[0] === 'object' && 'nativeEvent' in args[0]) {
|
|
82
32
|
(0, UserInteraction_1.notifyPress)(args[0]);
|
|
@@ -104,9 +54,8 @@ const invokeOnPressWithAction = (onPress, args, touchableName) => {
|
|
|
104
54
|
}
|
|
105
55
|
}
|
|
106
56
|
};
|
|
107
|
-
const wrapOnPress = (onPress, touchableName
|
|
57
|
+
const wrapOnPress = (onPress, touchableName) => {
|
|
108
58
|
return (...args) => {
|
|
109
|
-
registerPressOnBridge(args, touchableName, isButtonLike, buttonLabel);
|
|
110
59
|
notifyPressFromEvent(args);
|
|
111
60
|
if (!ConfigurationHandler_1.ConfigurationHandler.isConfigurationAvailable()) {
|
|
112
61
|
invokeOnPressWithoutConfiguration(onPress, args);
|
package/internal.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerEventTrigger(fn: ((name: string, timestamp: number) => void) | null): void;
|
package/internal.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createElement = exports.ApplicationHandler = void 0;
|
|
3
|
+
exports.registerEventTrigger = exports.createElement = exports.ApplicationHandler = void 0;
|
|
4
4
|
var Application_1 = require("./lib/core/Application");
|
|
5
5
|
Object.defineProperty(exports, "ApplicationHandler", { enumerable: true, get: function () { return Application_1.ApplicationHandler; } });
|
|
6
6
|
var CreateElement_1 = require("./instrumentation/jsx/CreateElement");
|
|
7
7
|
Object.defineProperty(exports, "createElement", { enumerable: true, get: function () { return CreateElement_1.createElement; } });
|
|
8
|
+
var NavigationTracker_1 = require("./lib/next/navigation/NavigationTracker");
|
|
9
|
+
Object.defineProperty(exports, "registerEventTrigger", { enumerable: true, get: function () { return NavigationTracker_1.registerEventTrigger; } });
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DTXScreenshotSelfMonitor.h
|
|
3
|
+
//
|
|
4
|
+
// Self-monitoring counterpart of the Session Replay UI-change listener. Instead of capturing a
|
|
5
|
+
// screenshot it only increments the cross-platform screenshot counter via the agent, to estimate
|
|
6
|
+
// how many screenshots Session Replay would take in production.
|
|
7
|
+
//
|
|
8
|
+
// Monitoring is gated by the remote `replay_metrics_enabled` flag delivered through the
|
|
9
|
+
// configuration; it stays dormant until the server enables it.
|
|
10
|
+
//
|
|
11
|
+
|
|
12
|
+
#import <Foundation/Foundation.h>
|
|
13
|
+
#import "ConfigurationSubscriber.h"
|
|
14
|
+
|
|
15
|
+
// Build-time master switch for the screenshot self-monitoring (replay metrics) feature.
|
|
16
|
+
// Defaults to enabled. Disable for a build with `DYNATRACE_REPLAY_METRICS_ENABLED=0 pod install`
|
|
17
|
+
// (the podspec then injects `#define DTX_REPLAY_METRICS_ENABLED 0` via the prefix header). When 0,
|
|
18
|
+
// the monitor is never registered, regardless of the remote `replay_metrics_enabled` flag.
|
|
19
|
+
#ifndef DTX_REPLAY_METRICS_ENABLED
|
|
20
|
+
#define DTX_REPLAY_METRICS_ENABLED 1
|
|
21
|
+
#endif
|
|
22
|
+
|
|
23
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
24
|
+
|
|
25
|
+
@interface DTXScreenshotSelfMonitor : NSObject <ConfigurationSubscriber>
|
|
26
|
+
|
|
27
|
+
@end
|
|
28
|
+
|
|
29
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DTXScreenshotSelfMonitor.mm
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
#import "DTXScreenshotSelfMonitor.h"
|
|
6
|
+
#import "DTXScreenshotSelfMonitorSwizzler.h"
|
|
7
|
+
#import <UIKit/UIKit.h>
|
|
8
|
+
#import <QuartzCore/QuartzCore.h>
|
|
9
|
+
|
|
10
|
+
static const double kDebounceMs = 500.0;
|
|
11
|
+
static const double kMaxDebounceMs = 2000.0;
|
|
12
|
+
|
|
13
|
+
// Remote feature-flag key in the cross-platform configuration map. Monitoring stays off unless the
|
|
14
|
+
// server delivers this flag as `true`.
|
|
15
|
+
static NSString *const kReplayMetricsConfigKey = @"replay_metrics_enabled";
|
|
16
|
+
|
|
17
|
+
// DEM-26735: replay metrics experiment sunsets on 2026-10-15T00:00:00Z; remove this class after that date.
|
|
18
|
+
static const NSTimeInterval kExperimentExpiryEpoch = 1792022400.0;
|
|
19
|
+
|
|
20
|
+
static BOOL DTXIsReplayMetricsExperimentExpired(void) {
|
|
21
|
+
return [[NSDate date] timeIntervalSince1970] >= kExperimentExpiryEpoch;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Minimal declaration of the agent's HybridBridge facade. The implementation is provided by the
|
|
25
|
+
// Dynatrace framework; we only need the screenshot-tracking entry point here.
|
|
26
|
+
//
|
|
27
|
+
// NOTE: the underlying store moved to ios-core (DynatraceCoreStatic's
|
|
28
|
+
// Assembly.shared.crossPlatformScreenshotTracker). DynatraceCoreStatic is not exposed to this plugin
|
|
29
|
+
// (it is statically merged into the Dynatrace framework and not re-exported), so we keep calling the
|
|
30
|
+
// ObjC HybridBridge facade; the agent's HybridBridge delegates to the ios-core tracker
|
|
31
|
+
// (+[HybridBridge trackCrossPlatformScreenshot] → [Assembly.shared.crossPlatformScreenshotTracker trackScreenshot]).
|
|
32
|
+
@interface HybridBridge : NSObject
|
|
33
|
+
+ (void)trackCrossPlatformScreenshot;
|
|
34
|
+
@end
|
|
35
|
+
|
|
36
|
+
@implementation DTXScreenshotSelfMonitor {
|
|
37
|
+
BOOL _started;
|
|
38
|
+
BOOL _swizzleInstalled;
|
|
39
|
+
BOOL _pending;
|
|
40
|
+
double _lastChangeTime;
|
|
41
|
+
double _firstChangeTime;
|
|
42
|
+
uint32_t _generation;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
- (instancetype)init {
|
|
46
|
+
self = [super init];
|
|
47
|
+
if (self) {
|
|
48
|
+
_started = NO;
|
|
49
|
+
_swizzleInstalled = NO;
|
|
50
|
+
_pending = NO;
|
|
51
|
+
_lastChangeTime = 0;
|
|
52
|
+
_firstChangeTime = 0;
|
|
53
|
+
_generation = 0;
|
|
54
|
+
}
|
|
55
|
+
return self;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
- (void)dealloc {
|
|
59
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
60
|
+
if (_swizzleInstalled) {
|
|
61
|
+
[DTXScreenshotSelfMonitorSwizzler uninstall];
|
|
62
|
+
_swizzleInstalled = NO;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#pragma mark - ConfigurationSubscriber
|
|
67
|
+
|
|
68
|
+
- (void)notifyWithConfiguration:(NSDictionary<NSString *, id> *)configuration {
|
|
69
|
+
id value = configuration[kReplayMetricsConfigKey];
|
|
70
|
+
BOOL flagEnabled = [value isKindOfClass:[NSNumber class]] && [value boolValue];
|
|
71
|
+
BOOL enabled = flagEnabled && !DTXIsReplayMetricsExperimentExpired();
|
|
72
|
+
if (flagEnabled && !enabled) {
|
|
73
|
+
NSLog(@"[dtxScreenshotSelfMonitor] config: %@=YES but experiment expired — treating as disabled", kReplayMetricsConfigKey);
|
|
74
|
+
} else {
|
|
75
|
+
NSLog(@"[dtxScreenshotSelfMonitor] config: %@=%@ → %@", kReplayMetricsConfigKey, value, enabled ? @"ENABLED" : @"DISABLED");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Configuration is delivered on a background queue; start/stop touch UIApplication and the global
|
|
79
|
+
// swizzle, so they must run on the main thread. start/stop are idempotent.
|
|
80
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
81
|
+
if (enabled) {
|
|
82
|
+
[self start];
|
|
83
|
+
} else {
|
|
84
|
+
[self stop];
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#pragma mark - Lifecycle (must be called on the main thread)
|
|
90
|
+
|
|
91
|
+
- (void)start {
|
|
92
|
+
if (_started) {
|
|
93
|
+
NSLog(@"[dtxScreenshotSelfMonitor] start ignored — already started");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
_started = YES;
|
|
97
|
+
|
|
98
|
+
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
99
|
+
selector:@selector(appDidBecomeActive)
|
|
100
|
+
name:UIApplicationDidBecomeActiveNotification
|
|
101
|
+
object:nil];
|
|
102
|
+
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
103
|
+
selector:@selector(appDidEnterBackground)
|
|
104
|
+
name:UIApplicationDidEnterBackgroundNotification
|
|
105
|
+
object:nil];
|
|
106
|
+
|
|
107
|
+
UIApplicationState appState = [UIApplication sharedApplication].applicationState;
|
|
108
|
+
NSLog(@"[dtxScreenshotSelfMonitor] start (appState=%ld, mainThread=%d)", (long)appState, (int)[NSThread isMainThread]);
|
|
109
|
+
|
|
110
|
+
// Install immediately unless we are backgrounded (avoids observing while there is nothing to do).
|
|
111
|
+
if (appState != UIApplicationStateBackground) {
|
|
112
|
+
[self installSwizzle];
|
|
113
|
+
} else {
|
|
114
|
+
NSLog(@"[dtxScreenshotSelfMonitor] start: app backgrounded — deferring swizzle install to appDidBecomeActive");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
- (void)stop {
|
|
119
|
+
if (!_started) return;
|
|
120
|
+
_started = NO;
|
|
121
|
+
NSLog(@"[dtxScreenshotSelfMonitor] stop");
|
|
122
|
+
|
|
123
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
124
|
+
[self uninstallSwizzle];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#pragma mark - Swizzle install / uninstall
|
|
128
|
+
|
|
129
|
+
- (void)installSwizzle {
|
|
130
|
+
if (_swizzleInstalled) return;
|
|
131
|
+
_swizzleInstalled = YES;
|
|
132
|
+
NSLog(@"[dtxScreenshotSelfMonitor] installing layer swizzle");
|
|
133
|
+
|
|
134
|
+
__weak __typeof__(self) weakSelf = self;
|
|
135
|
+
[DTXScreenshotSelfMonitorSwizzler installWithChangeHandler:^{
|
|
136
|
+
[weakSelf onLayerChanged];
|
|
137
|
+
}];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
- (void)uninstallSwizzle {
|
|
141
|
+
if (!_swizzleInstalled) return;
|
|
142
|
+
_swizzleInstalled = NO;
|
|
143
|
+
NSLog(@"[dtxScreenshotSelfMonitor] uninstalling layer swizzle (disarming handler)");
|
|
144
|
+
|
|
145
|
+
[DTXScreenshotSelfMonitorSwizzler uninstall];
|
|
146
|
+
_generation++;
|
|
147
|
+
_pending = NO;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#pragma mark - App lifecycle
|
|
151
|
+
|
|
152
|
+
- (void)appDidBecomeActive {
|
|
153
|
+
NSLog(@"[dtxScreenshotSelfMonitor] appDidBecomeActive");
|
|
154
|
+
[self installSwizzle];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
- (void)appDidEnterBackground {
|
|
158
|
+
NSLog(@"[dtxScreenshotSelfMonitor] appDidEnterBackground");
|
|
159
|
+
[self uninstallSwizzle];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
#pragma mark - CALayer change callback
|
|
163
|
+
|
|
164
|
+
- (void)onLayerChanged {
|
|
165
|
+
double now = CACurrentMediaTime() * 1000.0;
|
|
166
|
+
_lastChangeTime = now;
|
|
167
|
+
|
|
168
|
+
if (!_pending) {
|
|
169
|
+
_pending = YES;
|
|
170
|
+
_firstChangeTime = now;
|
|
171
|
+
NSLog(@"[dtxScreenshotSelfMonitor] UI change detected — starting %.0fms debounce", kDebounceMs);
|
|
172
|
+
[self scheduleCapture];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
#pragma mark - Debounce & capture
|
|
177
|
+
|
|
178
|
+
- (void)scheduleCapture {
|
|
179
|
+
uint32_t gen = _generation;
|
|
180
|
+
__weak __typeof__(self) weakSelf = self;
|
|
181
|
+
dispatch_after(
|
|
182
|
+
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDebounceMs * NSEC_PER_MSEC)),
|
|
183
|
+
dispatch_get_main_queue(),
|
|
184
|
+
^{
|
|
185
|
+
__typeof__(self) strongSelf = weakSelf;
|
|
186
|
+
if (!strongSelf || gen != strongSelf->_generation) return;
|
|
187
|
+
[strongSelf performCapture];
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
- (void)performCapture {
|
|
193
|
+
double now = CACurrentMediaTime() * 1000.0;
|
|
194
|
+
double elapsed = now - _lastChangeTime;
|
|
195
|
+
double sinceFirst = now - _firstChangeTime;
|
|
196
|
+
|
|
197
|
+
if (elapsed < kDebounceMs && sinceFirst < kMaxDebounceMs) {
|
|
198
|
+
NSLog(@"[dtxScreenshotSelfMonitor] UI still active (%.0fms since last change) — rescheduling", elapsed);
|
|
199
|
+
[self scheduleCapture];
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
_pending = NO;
|
|
204
|
+
|
|
205
|
+
@try {
|
|
206
|
+
NSLog(@"[dtxScreenshotSelfMonitor] capture settled — calling [HybridBridge trackCrossPlatformScreenshot]");
|
|
207
|
+
[HybridBridge trackCrossPlatformScreenshot];
|
|
208
|
+
} @catch (NSException *exception) {
|
|
209
|
+
NSLog(@"[dtxScreenshotSelfMonitor] trackCrossPlatformScreenshot threw: %@", exception.name);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
@end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DTXScreenshotSelfMonitorSwizzler.h
|
|
3
|
+
//
|
|
4
|
+
// Detects UI changes by swizzling -[CALayer layoutSublayers] and invoking a change handler on
|
|
5
|
+
// the main thread. Used by the cross-platform screenshot self-monitoring feature.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import <Foundation/Foundation.h>
|
|
9
|
+
|
|
10
|
+
@interface DTXScreenshotSelfMonitorSwizzler : NSObject
|
|
11
|
+
|
|
12
|
+
+ (void)installWithChangeHandler:(void (^)(void))handler;
|
|
13
|
+
+ (void)uninstall;
|
|
14
|
+
|
|
15
|
+
@end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DTXScreenshotSelfMonitorSwizzler.mm
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
#import "DTXScreenshotSelfMonitorSwizzler.h"
|
|
6
|
+
#import <QuartzCore/QuartzCore.h>
|
|
7
|
+
#import <objc/runtime.h>
|
|
8
|
+
|
|
9
|
+
static void (^_changeHandler)(void) = nil;
|
|
10
|
+
static BOOL _installed = NO;
|
|
11
|
+
static BOOL _isHandlingChange = NO;
|
|
12
|
+
|
|
13
|
+
static IMP original_layoutSublayers_imp = NULL;
|
|
14
|
+
|
|
15
|
+
static void notifyChange(void) {
|
|
16
|
+
if (!_changeHandler) return;
|
|
17
|
+
if (![NSThread isMainThread]) return;
|
|
18
|
+
if (_isHandlingChange) return;
|
|
19
|
+
|
|
20
|
+
_isHandlingChange = YES;
|
|
21
|
+
_changeHandler();
|
|
22
|
+
_isHandlingChange = NO;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static void swizzled_layoutSublayers(id self, SEL _cmd) {
|
|
26
|
+
((void (*)(id, SEL))original_layoutSublayers_imp)(self, _cmd);
|
|
27
|
+
notifyChange();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@implementation DTXScreenshotSelfMonitorSwizzler
|
|
31
|
+
|
|
32
|
+
+ (void)installWithChangeHandler:(void (^)(void))handler {
|
|
33
|
+
_changeHandler = [handler copy];
|
|
34
|
+
|
|
35
|
+
// Swizzle exactly once for the process lifetime, then drive activity through _changeHandler.
|
|
36
|
+
//
|
|
37
|
+
// We must NOT swizzle/unswizzle on every foreground/background. -[CALayer layoutSublayers] is a
|
|
38
|
+
// shared selector that DynatraceSessionReplay also swizzles; two independent swizzlers each
|
|
39
|
+
// restoring + re-installing the IMP can interleave such that an install captures an
|
|
40
|
+
// already-swizzled IMP as its "original". swizzled_layoutSublayers() then calls back into a
|
|
41
|
+
// swizzled implementation, recursing without bound → stack overflow (EXC_BAD_ACCESS on
|
|
42
|
+
// foreground). Installing once removes all IMP churn and keeps the swizzle chain stable.
|
|
43
|
+
if (_installed) {
|
|
44
|
+
NSLog(@"[dtxScreenshotSelfMonitor] swizzler: handler re-armed (already installed)");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
_installed = YES;
|
|
48
|
+
|
|
49
|
+
Class cls = [CALayer class];
|
|
50
|
+
Method m = class_getInstanceMethod(cls, @selector(layoutSublayers));
|
|
51
|
+
IMP current = method_getImplementation(m);
|
|
52
|
+
// Defensive: never capture our own swizzled function as the original (would self-recurse).
|
|
53
|
+
if (current != (IMP)swizzled_layoutSublayers) {
|
|
54
|
+
original_layoutSublayers_imp = current;
|
|
55
|
+
}
|
|
56
|
+
method_setImplementation(m, (IMP)swizzled_layoutSublayers);
|
|
57
|
+
NSLog(@"[dtxScreenshotSelfMonitor] swizzler: installed on -[CALayer layoutSublayers] (one-time)");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
+ (void)uninstall {
|
|
61
|
+
// Intentionally does NOT restore the original IMP (see installWithChangeHandler:). Restoring is
|
|
62
|
+
// what enables the cross-swizzler recursion above, and it would also clobber any swizzler layered
|
|
63
|
+
// on top of us. We leave the one-time swizzle in place and simply stop notifying — notifyChange()
|
|
64
|
+
// no-ops while _changeHandler is nil, so there is no work and no risk when monitoring is off.
|
|
65
|
+
_changeHandler = nil;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@end
|