@multiplayer-app/session-recorder-react-native 0.0.1-alpha.9 → 0.0.1-beta.10
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/SessionRecorderNative.podspec +29 -0
- package/android/build.gradle +32 -0
- package/app.plugin.js +42 -0
- package/copy-react-native-dist.sh +4 -10
- package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.js +1 -0
- package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.js.map +1 -0
- package/dist/components/GestureCaptureWrapper/index.d.ts +1 -0
- package/dist/components/GestureCaptureWrapper/index.js +1 -0
- package/dist/components/GestureCaptureWrapper/index.js.map +1 -0
- package/dist/components/MaskableComponent.d.ts +22 -0
- package/dist/components/MaskableComponent.js +1 -0
- package/dist/components/MaskableComponent.js.map +1 -0
- package/dist/components/MaskableTextInput.d.ts +14 -0
- package/dist/components/MaskableTextInput.js +1 -0
- package/dist/components/MaskableTextInput.js.map +1 -0
- package/dist/components/ScreenRecorderView/ScreenRecorderView.d.ts +5 -0
- package/dist/components/ScreenRecorderView/ScreenRecorderView.js +1 -0
- package/dist/components/ScreenRecorderView/ScreenRecorderView.js.map +1 -0
- package/dist/components/ScreenRecorderView/index.d.ts +1 -0
- package/dist/components/ScreenRecorderView/index.js +1 -0
- package/dist/components/ScreenRecorderView/index.js.map +1 -0
- package/dist/components/SessionRecorderWidget/FinalPopover.d.ts +11 -0
- package/dist/components/SessionRecorderWidget/FinalPopover.js +1 -0
- package/dist/components/SessionRecorderWidget/FinalPopover.js.map +1 -0
- package/dist/components/SessionRecorderWidget/FloatingButton.d.ts +8 -0
- package/dist/components/SessionRecorderWidget/FloatingButton.js +1 -0
- package/dist/components/SessionRecorderWidget/FloatingButton.js.map +1 -0
- package/dist/components/SessionRecorderWidget/InitialPopover.d.ts +13 -0
- package/dist/components/SessionRecorderWidget/InitialPopover.js +1 -0
- package/dist/components/SessionRecorderWidget/InitialPopover.js.map +1 -0
- package/dist/components/SessionRecorderWidget/ModalContainer.d.ts +8 -0
- package/dist/components/SessionRecorderWidget/ModalContainer.js +1 -0
- package/dist/components/SessionRecorderWidget/ModalContainer.js.map +1 -0
- package/dist/components/SessionRecorderWidget/ModalHeader.d.ts +6 -0
- package/dist/components/SessionRecorderWidget/ModalHeader.js +1 -0
- package/dist/components/SessionRecorderWidget/ModalHeader.js.map +1 -0
- package/dist/components/SessionRecorderWidget/SessionRecorderWidget.d.ts +5 -0
- package/dist/components/SessionRecorderWidget/SessionRecorderWidget.js +1 -0
- package/dist/components/SessionRecorderWidget/SessionRecorderWidget.js.map +1 -0
- package/dist/components/SessionRecorderWidget/icons.d.ts +11 -0
- package/dist/components/SessionRecorderWidget/icons.js +1 -0
- package/dist/components/SessionRecorderWidget/icons.js.map +1 -0
- package/dist/components/SessionRecorderWidget/index.d.ts +2 -0
- package/dist/components/SessionRecorderWidget/index.js +1 -0
- package/dist/components/SessionRecorderWidget/index.js.map +1 -0
- package/dist/components/SessionRecorderWidget/styles.d.ts +145 -0
- package/dist/components/SessionRecorderWidget/styles.js +1 -0
- package/dist/components/SessionRecorderWidget/styles.js.map +1 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -0
- package/dist/config/defaults.js +1 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/masking.js +1 -1
- package/dist/config/masking.js.map +1 -1
- package/dist/context/SessionRecorderContext.d.ts +5 -3
- package/dist/context/SessionRecorderContext.js +1 -1
- package/dist/context/SessionRecorderContext.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/native/ScreenMasking.d.ts +21 -0
- package/dist/native/ScreenMasking.js +1 -0
- package/dist/native/ScreenMasking.js.map +1 -0
- package/dist/native/SessionRecorderNative.d.ts +21 -0
- package/dist/native/SessionRecorderNative.js +1 -0
- package/dist/native/SessionRecorderNative.js.map +1 -0
- package/dist/otel/index.d.ts +0 -2
- package/dist/otel/index.js.map +1 -1
- package/dist/otel/instrumentations/index.d.ts +0 -3
- package/dist/otel/instrumentations/index.js +1 -1
- package/dist/otel/instrumentations/index.js.map +1 -1
- package/dist/patch/xhr.js +1 -1
- package/dist/patch/xhr.js.map +1 -1
- package/dist/recorder/gestureRecorder.d.ts +0 -9
- package/dist/recorder/gestureRecorder.js +1 -1
- package/dist/recorder/gestureRecorder.js.map +1 -1
- package/dist/recorder/index.d.ts +4 -3
- package/dist/recorder/index.js.map +1 -1
- package/dist/recorder/screenRecorder.d.ts +2 -6
- package/dist/recorder/screenRecorder.js +1 -1
- package/dist/recorder/screenRecorder.js.map +1 -1
- package/dist/recorder/screenshotManager.d.ts +10 -0
- package/dist/recorder/screenshotManager.js +1 -0
- package/dist/recorder/screenshotManager.js.map +1 -0
- package/dist/services/screenMaskingService.d.ts +39 -0
- package/dist/services/screenMaskingService.js +1 -0
- package/dist/services/screenMaskingService.js.map +1 -0
- package/dist/services/storage.service.d.ts +18 -2
- package/dist/services/storage.service.js +1 -1
- package/dist/services/storage.service.js.map +1 -1
- package/dist/session-recorder.d.ts +4 -2
- package/dist/session-recorder.js +1 -1
- package/dist/session-recorder.js.map +1 -1
- package/dist/types/index.d.ts +2 -16
- package/dist/types/index.js +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/session-recorder.d.ts +6 -0
- package/dist/types/session-recorder.js.map +1 -1
- package/dist/utils/app-metadata.d.ts +16 -0
- package/dist/utils/app-metadata.js +1 -0
- package/dist/utils/app-metadata.js.map +1 -0
- package/dist/utils/componentRegistry.d.ts +64 -0
- package/dist/utils/componentRegistry.js +1 -0
- package/dist/utils/componentRegistry.js.map +1 -0
- package/dist/utils/nativeModuleTest.d.ts +8 -0
- package/dist/utils/nativeModuleTest.js +1 -0
- package/dist/utils/nativeModuleTest.js.map +1 -0
- package/dist/utils/platform.d.ts +35 -0
- package/dist/utils/platform.js +1 -1
- package/dist/utils/platform.js.map +1 -1
- package/dist/utils/reactNativeHierarchyExtractor.d.ts +38 -0
- package/dist/utils/reactNativeHierarchyExtractor.js +1 -0
- package/dist/utils/reactNativeHierarchyExtractor.js.map +1 -0
- package/dist/utils/rrweb-events.d.ts +1 -1
- package/dist/utils/rrweb-events.js +1 -1
- package/dist/utils/rrweb-events.js.map +1 -1
- package/dist/utils/screenshotMasker.d.ts +96 -0
- package/dist/utils/screenshotMasker.js +1 -0
- package/dist/utils/screenshotMasker.js.map +1 -0
- package/dist/utils/viewHierarchyTracker.d.ts +89 -0
- package/dist/utils/viewHierarchyTracker.js +1 -0
- package/dist/utils/viewHierarchyTracker.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/NATIVE_MODULE_SETUP.md +177 -0
- package/ios/SessionRecorderNative.m +17 -0
- package/ios/SessionRecorderNative.podspec +26 -0
- package/ios/SessionRecorderNative.swift +205 -0
- package/package.json +25 -14
- package/plugin/package.json +20 -0
- package/plugin/src/index.js +42 -0
- package/react-native.config.js +15 -0
- package/RRWEB_INTEGRATION.md +0 -336
- package/VIEWSHOT_INTEGRATION_TEST.md +0 -123
- package/babel.config.js +0 -13
- package/dist/components/GestureCaptureWrapper.js +0 -1
- package/dist/components/GestureCaptureWrapper.js.map +0 -1
- package/dist/expo.d.ts +0 -7
- package/dist/expo.js +0 -1
- package/dist/expo.js.map +0 -1
- package/dist/otel/instrumentations/gestureInstrumentation.d.ts +0 -15
- package/dist/otel/instrumentations/gestureInstrumentation.js +0 -1
- package/dist/otel/instrumentations/gestureInstrumentation.js.map +0 -1
- package/dist/otel/instrumentations/reactNativeInstrumentation.d.ts +0 -8
- package/dist/otel/instrumentations/reactNativeInstrumentation.js +0 -1
- package/dist/otel/instrumentations/reactNativeInstrumentation.js.map +0 -1
- package/dist/otel/instrumentations/reactNavigationInstrumentation.d.ts +0 -13
- package/dist/otel/instrumentations/reactNavigationInstrumentation.js +0 -1
- package/dist/otel/instrumentations/reactNavigationInstrumentation.js.map +0 -1
- package/dist/recorder/gestureHandlerRecorder.d.ts +0 -19
- package/dist/recorder/gestureHandlerRecorder.js +0 -1
- package/dist/recorder/gestureHandlerRecorder.js.map +0 -1
- package/dist/types/rrweb.d.ts +0 -118
- package/dist/types/rrweb.js +0 -1
- package/dist/types/rrweb.js.map +0 -1
- package/src/components/GestureCaptureWrapper.tsx +0 -110
- package/src/config/constants.ts +0 -60
- package/src/config/defaults.ts +0 -82
- package/src/config/index.ts +0 -6
- package/src/config/masking.ts +0 -27
- package/src/config/session-recorder.ts +0 -55
- package/src/config/validators.ts +0 -31
- package/src/context/SessionRecorderContext.tsx +0 -143
- package/src/expo.ts +0 -11
- package/src/index.ts +0 -9
- package/src/otel/helpers.ts +0 -275
- package/src/otel/index.ts +0 -149
- package/src/otel/instrumentations/gestureInstrumentation.ts +0 -141
- package/src/otel/instrumentations/index.ts +0 -120
- package/src/otel/instrumentations/reactNativeInstrumentation.ts +0 -77
- package/src/otel/instrumentations/reactNavigationInstrumentation.ts +0 -119
- package/src/patch/index.ts +0 -1
- package/src/patch/xhr.ts +0 -142
- package/src/recorder/eventExporter.ts +0 -141
- package/src/recorder/gestureHandlerRecorder.ts +0 -157
- package/src/recorder/gestureRecorder.ts +0 -622
- package/src/recorder/index.ts +0 -178
- package/src/recorder/navigationTracker.ts +0 -449
- package/src/recorder/screenRecorder.ts +0 -506
- package/src/services/api.service.ts +0 -203
- package/src/services/storage.service.ts +0 -158
- package/src/session-recorder.ts +0 -601
- package/src/types/expo.d.ts +0 -23
- package/src/types/index.ts +0 -46
- package/src/types/session-recorder.ts +0 -423
- package/src/types/session.ts +0 -65
- package/src/utils/index.ts +0 -8
- package/src/utils/logger.ts +0 -225
- package/src/utils/platform.ts +0 -87
- package/src/utils/request-utils.ts +0 -61
- package/src/utils/rrweb-events.ts +0 -311
- package/src/utils/session.ts +0 -18
- package/src/utils/time.ts +0 -17
- package/src/utils/type-utils.ts +0 -75
- package/src/version.ts +0 -1
- package/tsconfig.json +0 -24
- /package/dist/components/{GestureCaptureWrapper.d.ts → GestureCaptureWrapper/GestureCaptureWrapper.d.ts} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports,"__esModule",{value:true});exports.ScreenshotMasker=void 0;var _regenerator=_interopRequireDefault(require("@babel/runtime/regenerator"));var _defineProperty2=_interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));var _asyncToGenerator2=_interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));var _classCallCheck2=_interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));var _createClass2=_interopRequireDefault(require("@babel/runtime/helpers/createClass"));var _index=require("./index");function ownKeys(e,r){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);r&&(o=o.filter(function(r){return Object.getOwnPropertyDescriptor(e,r).enumerable;})),t.push.apply(t,o);}return t;}function _objectSpread(e){for(var r=1;r<arguments.length;r++){var t=null!=arguments[r]?arguments[r]:{};r%2?ownKeys(Object(t),!0).forEach(function(r){(0,_defineProperty2["default"])(e,r,t[r]);}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(t)):ownKeys(Object(t)).forEach(function(r){Object.defineProperty(e,r,Object.getOwnPropertyDescriptor(t,r));});}return e;}function _createForOfIteratorHelper(r,e){var t="undefined"!=typeof Symbol&&r[Symbol.iterator]||r["@@iterator"];if(!t){if(Array.isArray(r)||(t=_unsupportedIterableToArray(r))||e&&r&&"number"==typeof r.length){t&&(r=t);var _n=0,F=function F(){};return{s:F,n:function n(){return _n>=r.length?{done:!0}:{done:!1,value:r[_n++]};},e:function e(r){throw r;},f:F};}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");}var o,a=!0,u=!1;return{s:function s(){t=t.call(r);},n:function n(){var r=t.next();return a=r.done,r;},e:function e(r){u=!0,o=r;},f:function f(){try{a||null==t["return"]||t["return"]();}finally{if(u)throw o;}}};}function _unsupportedIterableToArray(r,a){if(r){if("string"==typeof r)return _arrayLikeToArray(r,a);var t={}.toString.call(r).slice(8,-1);return"Object"===t&&r.constructor&&(t=r.constructor.name),"Map"===t||"Set"===t?Array.from(r):"Arguments"===t||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t)?_arrayLikeToArray(r,a):void 0;}}function _arrayLikeToArray(r,a){(null==a||a>r.length)&&(a=r.length);for(var e=0,n=Array(a);e<a;e++)n[e]=r[e];return n;}var ScreenshotMasker=exports.ScreenshotMasker=function(){function ScreenshotMasker(config,screenDimensions){(0,_classCallCheck2["default"])(this,ScreenshotMasker);this.screenDimensions=null;this.config=config;this.screenDimensions=screenDimensions||null;}return(0,_createClass2["default"])(ScreenshotMasker,[{key:"maskScreenshot",value:function(){var _maskScreenshot=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee(base64Image,regions){var _t;return _regenerator["default"].wrap(function(_context){while(1)switch(_context.prev=_context.next){case 0:if(!(!this.config.enabled||regions.length===0)){_context.next=1;break;}return _context.abrupt("return",base64Image);case 1:_context.prev=1;if(!this.config.maskScreenshot){_context.next=3;break;}_context.next=2;return this.config.maskScreenshot(base64Image,regions);case 2:return _context.abrupt("return",_context.sent);case 3:_context.next=4;return this.applyDefaultMasking(base64Image,regions);case 4:return _context.abrupt("return",_context.sent);case 5:_context.prev=5;_t=_context["catch"](1);_index.logger.error('ScreenshotMasker','Failed to mask screenshot:',_t);return _context.abrupt("return",base64Image);case 6:case"end":return _context.stop();}},_callee,this,[[1,5]]);}));function maskScreenshot(_x,_x2){return _maskScreenshot.apply(this,arguments);}return maskScreenshot;}()},{key:"applyDefaultMasking",value:function(){var _applyDefaultMasking=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee2(base64Image,regions){var maskType,_t2;return _regenerator["default"].wrap(function(_context2){while(1)switch(_context2.prev=_context2.next){case 0:maskType=this.config.maskType||'blackout';_t2=maskType;_context2.next=_t2==='blur'?1:_t2==='pixelate'?3:_t2==='blackout'?5:5;break;case 1:_context2.next=2;return this.applyBlurMasking(base64Image,regions);case 2:return _context2.abrupt("return",_context2.sent);case 3:_context2.next=4;return this.applyPixelateMasking(base64Image,regions);case 4:return _context2.abrupt("return",_context2.sent);case 5:_context2.next=6;return this.applyBlackoutMasking(base64Image,regions);case 6:return _context2.abrupt("return",_context2.sent);case 7:case"end":return _context2.stop();}},_callee2,this);}));function applyDefaultMasking(_x3,_x4){return _applyDefaultMasking.apply(this,arguments);}return applyDefaultMasking;}()},{key:"applyBlackoutMasking",value:function(){var _applyBlackoutMasking=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee3(base64Image,regions){var imageData,_iterator,_step,region;return _regenerator["default"].wrap(function(_context3){while(1)switch(_context3.prev=_context3.next){case 0:_context3.next=1;return this.base64ToImageData(base64Image);case 1:imageData=_context3.sent;_iterator=_createForOfIteratorHelper(regions);try{for(_iterator.s();!(_step=_iterator.n()).done;){region=_step.value;this.blackoutRegion(imageData,region);}}catch(err){_iterator.e(err);}finally{_iterator.f();}_context3.next=2;return this.imageDataToBase64(imageData);case 2:return _context3.abrupt("return",_context3.sent);case 3:case"end":return _context3.stop();}},_callee3,this);}));function applyBlackoutMasking(_x5,_x6){return _applyBlackoutMasking.apply(this,arguments);}return applyBlackoutMasking;}()},{key:"applyBlurMasking",value:function(){var _applyBlurMasking=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee4(base64Image,regions){return _regenerator["default"].wrap(function(_context4){while(1)switch(_context4.prev=_context4.next){case 0:_index.logger.warn('ScreenshotMasker','Blur masking not implemented, using blackout');_context4.next=1;return this.applyBlackoutMasking(base64Image,regions);case 1:return _context4.abrupt("return",_context4.sent);case 2:case"end":return _context4.stop();}},_callee4,this);}));function applyBlurMasking(_x7,_x8){return _applyBlurMasking.apply(this,arguments);}return applyBlurMasking;}()},{key:"applyPixelateMasking",value:function(){var _applyPixelateMasking=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee5(base64Image,regions){return _regenerator["default"].wrap(function(_context5){while(1)switch(_context5.prev=_context5.next){case 0:_index.logger.warn('ScreenshotMasker','Pixelate masking not implemented, using blackout');_context5.next=1;return this.applyBlackoutMasking(base64Image,regions);case 1:return _context5.abrupt("return",_context5.sent);case 2:case"end":return _context5.stop();}},_callee5,this);}));function applyPixelateMasking(_x9,_x0){return _applyPixelateMasking.apply(this,arguments);}return applyPixelateMasking;}()},{key:"base64ToImageData",value:function(){var _base64ToImageData=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee6(base64Image){var _a,_b;return _regenerator["default"].wrap(function(_context6){while(1)switch(_context6.prev=_context6.next){case 0:return _context6.abrupt("return",{data:base64Image,width:((_a=this.screenDimensions)===null||_a===void 0?void 0:_a.width)||400,height:((_b=this.screenDimensions)===null||_b===void 0?void 0:_b.height)||800});case 1:case"end":return _context6.stop();}},_callee6,this);}));function base64ToImageData(_x1){return _base64ToImageData.apply(this,arguments);}return base64ToImageData;}()},{key:"imageDataToBase64",value:function(){var _imageDataToBase=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee7(imageData){return _regenerator["default"].wrap(function(_context7){while(1)switch(_context7.prev=_context7.next){case 0:return _context7.abrupt("return",imageData.data);case 1:case"end":return _context7.stop();}},_callee7);}));function imageDataToBase64(_x10){return _imageDataToBase.apply(this,arguments);}return imageDataToBase64;}()},{key:"blackoutRegion",value:function blackoutRegion(imageData,region){_index.logger.debug('ScreenshotMasker',"Blacking out region: ".concat(region.x,", ").concat(region.y,", ").concat(region.width,"x").concat(region.height));}},{key:"detectInputFields",value:function(){var _detectInputFields=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee8(base64Image){var imageData,regions,candidates,_iterator2,_step2,candidate,_t3;return _regenerator["default"].wrap(function(_context8){while(1)switch(_context8.prev=_context8.next){case 0:if(!(!this.config.maskAllInputs&&this.config.detectionMethod!=='heuristic')){_context8.next=1;break;}return _context8.abrupt("return",[]);case 1:_context8.prev=1;_context8.next=2;return this.base64ToImageData(base64Image);case 2:imageData=_context8.sent;regions=[];_context8.next=3;return this.findRectangularRegions(imageData);case 3:candidates=_context8.sent;_iterator2=_createForOfIteratorHelper(candidates);try{for(_iterator2.s();!(_step2=_iterator2.n()).done;){candidate=_step2.value;if(this.isLikelyInputField(candidate)){regions.push({x:candidate.x,y:candidate.y,width:candidate.width,height:candidate.height,type:this.config.maskType||'blackout',confidence:candidate.confidence});}}}catch(err){_iterator2.e(err);}finally{_iterator2.f();}_index.logger.debug('ScreenshotMasker',"Detected ".concat(regions.length," input fields"));return _context8.abrupt("return",regions);case 4:_context8.prev=4;_t3=_context8["catch"](1);_index.logger.error('ScreenshotMasker','Failed to detect input fields:',_t3);return _context8.abrupt("return",[]);case 5:case"end":return _context8.stop();}},_callee8,this,[[1,4]]);}));function detectInputFields(_x11){return _detectInputFields.apply(this,arguments);}return detectInputFields;}()},{key:"findRectangularRegions",value:function(){var _findRectangularRegions=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee9(imageData){var regions,_this$screenDimension,width,height,commonPositions,_i,_commonPositions,pos;return _regenerator["default"].wrap(function(_context9){while(1)switch(_context9.prev=_context9.next){case 0:regions=[];if(this.screenDimensions){_this$screenDimension=this.screenDimensions,width=_this$screenDimension.width,height=_this$screenDimension.height;commonPositions=[{x:width*0.1,y:height*0.3,width:width*0.8,height:40},{x:width*0.1,y:height*0.4,width:width*0.8,height:40},{x:width*0.1,y:height*0.5,width:width*0.8,height:40},{x:width*0.1,y:height*0.6,width:width*0.8,height:40}];for(_i=0,_commonPositions=commonPositions;_i<_commonPositions.length;_i++){pos=_commonPositions[_i];regions.push(_objectSpread(_objectSpread({},pos),{},{confidence:0.7}));}}return _context9.abrupt("return",regions);case 1:case"end":return _context9.stop();}},_callee9,this);}));function findRectangularRegions(_x12){return _findRectangularRegions.apply(this,arguments);}return findRectangularRegions;}()},{key:"isLikelyInputField",value:function isLikelyInputField(region){if(region.width<80||region.width>400)return false;if(region.height<20||region.height>60)return false;var aspectRatio=region.width/region.height;if(aspectRatio<2||aspectRatio>10)return false;if(this.screenDimensions){var height=this.screenDimensions.height;var isInCommonArea=region.y>height*0.2&®ion.y<height*0.8;if(!isInCommonArea)return false;}var confidence=this.config.confidence||0.5;if(region.confidence<confidence)return false;return true;}},{key:"updateConfig",value:function updateConfig(config){this.config=config;}},{key:"updateScreenDimensions",value:function updateScreenDimensions(dimensions){this.screenDimensions=dimensions;}}]);}();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshotMasker.js","sourceRoot":"","sources":["../../src/utils/screenshotMasker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { ViewHierarchyNode, MaskRegion } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Class for tracking view hierarchy and detecting elements that should be masked
|
|
4
|
+
*/
|
|
5
|
+
export declare class ViewHierarchyTracker {
|
|
6
|
+
private hierarchy;
|
|
7
|
+
private maskedElements;
|
|
8
|
+
private screenDimensions;
|
|
9
|
+
constructor(screenDimensions?: {
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* Update the view hierarchy
|
|
15
|
+
* @param hierarchy - The current view hierarchy
|
|
16
|
+
*/
|
|
17
|
+
updateHierarchy(hierarchy: ViewHierarchyNode[]): void;
|
|
18
|
+
/**
|
|
19
|
+
* Extract elements that should be masked from the hierarchy
|
|
20
|
+
*/
|
|
21
|
+
private extractMaskedElements;
|
|
22
|
+
/**
|
|
23
|
+
* Traverse the hierarchy tree to find masked elements
|
|
24
|
+
* @param nodes - Array of hierarchy nodes
|
|
25
|
+
*/
|
|
26
|
+
private traverseHierarchy;
|
|
27
|
+
/**
|
|
28
|
+
* Determine if an element should be masked
|
|
29
|
+
* @param node - The hierarchy node to check
|
|
30
|
+
* @returns true if the element should be masked
|
|
31
|
+
*/
|
|
32
|
+
private shouldMaskElement;
|
|
33
|
+
/**
|
|
34
|
+
* Check if a node represents an input field
|
|
35
|
+
* @param node - The hierarchy node to check
|
|
36
|
+
* @returns true if the node is an input field
|
|
37
|
+
*/
|
|
38
|
+
private isInputField;
|
|
39
|
+
/**
|
|
40
|
+
* Check if a node has sensitive data markers
|
|
41
|
+
* @param node - The hierarchy node to check
|
|
42
|
+
* @returns true if the node has sensitive data markers
|
|
43
|
+
*/
|
|
44
|
+
private hasSensitiveDataMarker;
|
|
45
|
+
/**
|
|
46
|
+
* Get mask regions from the current hierarchy
|
|
47
|
+
* @returns Array of mask regions
|
|
48
|
+
*/
|
|
49
|
+
getMaskRegions(): MaskRegion[];
|
|
50
|
+
/**
|
|
51
|
+
* Traverse hierarchy to collect mask regions
|
|
52
|
+
* @param nodes - Array of hierarchy nodes
|
|
53
|
+
* @param regions - Array to collect mask regions
|
|
54
|
+
*/
|
|
55
|
+
private traverseHierarchyForRegions;
|
|
56
|
+
/**
|
|
57
|
+
* Add a manual mask region
|
|
58
|
+
* @param region - The region to mask
|
|
59
|
+
*/
|
|
60
|
+
addManualRegion(region: MaskRegion): void;
|
|
61
|
+
/**
|
|
62
|
+
* Clear all mask regions
|
|
63
|
+
*/
|
|
64
|
+
clearRegions(): void;
|
|
65
|
+
/**
|
|
66
|
+
* Get the current hierarchy
|
|
67
|
+
* @returns Current view hierarchy
|
|
68
|
+
*/
|
|
69
|
+
getHierarchy(): ViewHierarchyNode[];
|
|
70
|
+
/**
|
|
71
|
+
* Get masked element IDs
|
|
72
|
+
* @returns Set of masked element IDs
|
|
73
|
+
*/
|
|
74
|
+
getMaskedElements(): Set<string>;
|
|
75
|
+
/**
|
|
76
|
+
* Update screen dimensions
|
|
77
|
+
* @param dimensions - Screen dimensions
|
|
78
|
+
*/
|
|
79
|
+
updateScreenDimensions(dimensions: {
|
|
80
|
+
width: number;
|
|
81
|
+
height: number;
|
|
82
|
+
}): void;
|
|
83
|
+
/**
|
|
84
|
+
* Check if an element is masked by ID
|
|
85
|
+
* @param elementId - Element ID to check
|
|
86
|
+
* @returns true if the element is masked
|
|
87
|
+
*/
|
|
88
|
+
isElementMasked(elementId: string): boolean;
|
|
89
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports,"__esModule",{value:true});exports.ViewHierarchyTracker=void 0;var _typeof2=_interopRequireDefault(require("@babel/runtime/helpers/typeof"));var _classCallCheck2=_interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));var _createClass2=_interopRequireDefault(require("@babel/runtime/helpers/createClass"));function _createForOfIteratorHelper(r,e){var t="undefined"!=typeof Symbol&&r[Symbol.iterator]||r["@@iterator"];if(!t){if(Array.isArray(r)||(t=_unsupportedIterableToArray(r))||e&&r&&"number"==typeof r.length){t&&(r=t);var _n=0,F=function F(){};return{s:F,n:function n(){return _n>=r.length?{done:!0}:{done:!1,value:r[_n++]};},e:function e(r){throw r;},f:F};}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");}var o,a=!0,u=!1;return{s:function s(){t=t.call(r);},n:function n(){var r=t.next();return a=r.done,r;},e:function e(r){u=!0,o=r;},f:function f(){try{a||null==t["return"]||t["return"]();}finally{if(u)throw o;}}};}function _unsupportedIterableToArray(r,a){if(r){if("string"==typeof r)return _arrayLikeToArray(r,a);var t={}.toString.call(r).slice(8,-1);return"Object"===t&&r.constructor&&(t=r.constructor.name),"Map"===t||"Set"===t?Array.from(r):"Arguments"===t||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t)?_arrayLikeToArray(r,a):void 0;}}function _arrayLikeToArray(r,a){(null==a||a>r.length)&&(a=r.length);for(var e=0,n=Array(a);e<a;e++)n[e]=r[e];return n;}var ViewHierarchyTracker=exports.ViewHierarchyTracker=function(){function ViewHierarchyTracker(screenDimensions){(0,_classCallCheck2["default"])(this,ViewHierarchyTracker);this.hierarchy=[];this.maskedElements=new Set();this.screenDimensions=null;this.screenDimensions=screenDimensions||null;}return(0,_createClass2["default"])(ViewHierarchyTracker,[{key:"updateHierarchy",value:function updateHierarchy(hierarchy){this.hierarchy=hierarchy;this.extractMaskedElements();}},{key:"extractMaskedElements",value:function extractMaskedElements(){this.maskedElements.clear();this.traverseHierarchy(this.hierarchy);}},{key:"traverseHierarchy",value:function traverseHierarchy(nodes){var _iterator=_createForOfIteratorHelper(nodes),_step;try{for(_iterator.s();!(_step=_iterator.n()).done;){var node=_step.value;if(this.shouldMaskElement(node)){if(node.id){this.maskedElements.add(node.id);}node.masked=true;}if(node.children){this.traverseHierarchy(node.children);}}}catch(err){_iterator.e(err);}finally{_iterator.f();}}},{key:"shouldMaskElement",value:function shouldMaskElement(node){var _a;if(node.accessibilityLabel==='ph-no-capture'||node.accessibilityLabel==='mask-input'||((_a=node.props)===null||_a===void 0?void 0:_a.testID)==='mask-input'){return true;}if(this.isInputField(node)){return true;}if(this.hasSensitiveDataMarker(node)){return true;}return false;}},{key:"isInputField",value:function isInputField(node){var inputTypes=['TextInput','TextInputMask','Input','PasswordInput','SecureTextInput'];if(inputTypes.includes(node.type)){return true;}var props=node.props||{};if(props.secureTextEntry===true){return true;}if(props.password===true||props.isPassword===true||props.inputType==='password'){return true;}var placeholder=props.placeholder||'';var sensitivePlaceholders=['password','pass','pwd','secret','token','key','credit card','card number','cvv','ssn','social security','phone','email','address','zip','postal'];if(sensitivePlaceholders.some(function(sensitive){return placeholder.toLowerCase().includes(sensitive);})){return true;}return false;}},{key:"hasSensitiveDataMarker",value:function hasSensitiveDataMarker(node){var props=node.props||{};if(props.sensitive===true||props["private"]===true||props.confidential===true){return true;}var style=props.style||{};if((0,_typeof2["default"])(style)==='object'&&style.sensitive===true){return true;}return false;}},{key:"getMaskRegions",value:function getMaskRegions(){var regions=[];this.traverseHierarchyForRegions(this.hierarchy,regions);return regions;}},{key:"traverseHierarchyForRegions",value:function traverseHierarchyForRegions(nodes,regions){var _iterator2=_createForOfIteratorHelper(nodes),_step2;try{for(_iterator2.s();!(_step2=_iterator2.n()).done;){var node=_step2.value;if(node.masked&&node.bounds){var region={x:node.bounds.x,y:node.bounds.y,width:node.bounds.width,height:node.bounds.height,type:'blackout',elementId:node.id};regions.push(region);}if(node.children){this.traverseHierarchyForRegions(node.children,regions);}}}catch(err){_iterator2.e(err);}finally{_iterator2.f();}}},{key:"addManualRegion",value:function addManualRegion(region){}},{key:"clearRegions",value:function clearRegions(){this.maskedElements.clear();this.hierarchy=[];}},{key:"getHierarchy",value:function getHierarchy(){return this.hierarchy;}},{key:"getMaskedElements",value:function getMaskedElements(){return new Set(this.maskedElements);}},{key:"updateScreenDimensions",value:function updateScreenDimensions(dimensions){this.screenDimensions=dimensions;}},{key:"isElementMasked",value:function isElementMasked(elementId){return this.maskedElements.has(elementId);}}]);}();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewHierarchyTracker.js","sourceRoot":"","sources":["../../src/utils/viewHierarchyTracker.ts"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "0.0.1
|
|
1
|
+
export declare const version = "0.0.1";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.version=void 0;var version=exports.version="0.0.1
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.version=void 0;var version=exports.version="0.0.1";
|
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAA"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Native Module Setup Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to properly set up the Session Recorder React Native library with native modules in both React Native and Expo projects.
|
|
4
|
+
|
|
5
|
+
## Issues Fixed
|
|
6
|
+
|
|
7
|
+
### 1. Missing iOS Native Files in NPM Package
|
|
8
|
+
|
|
9
|
+
**Problem**: The iOS native implementation files (`SessionRecorderNative.m` and `SessionRecorderNative.swift`) were not being included in the published npm package.
|
|
10
|
+
|
|
11
|
+
**Solution**: Updated `.npmignore` to exclude source files but include the compiled native modules.
|
|
12
|
+
|
|
13
|
+
### 2. Auto-linking Configuration
|
|
14
|
+
|
|
15
|
+
**Problem**: The library wasn't properly configured for auto-linking in Expo projects.
|
|
16
|
+
|
|
17
|
+
**Solution**:
|
|
18
|
+
|
|
19
|
+
- Added `expo` configuration section to `package.json`
|
|
20
|
+
- Created an Expo plugin for automatic Podfile configuration
|
|
21
|
+
- Updated podspec with proper Expo compatibility settings
|
|
22
|
+
|
|
23
|
+
### 3. Podspec Configuration
|
|
24
|
+
|
|
25
|
+
**Problem**: The podspec wasn't optimized for Expo compatibility.
|
|
26
|
+
|
|
27
|
+
**Solution**: Added proper header search paths and Expo-specific configurations.
|
|
28
|
+
|
|
29
|
+
## Setup Instructions
|
|
30
|
+
|
|
31
|
+
### For Expo Projects
|
|
32
|
+
|
|
33
|
+
1. **Install the package**:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @multiplayer-app/session-recorder-react-native
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
2. **Add to app.json/app.config.js**:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"expo": {
|
|
44
|
+
"plugins": ["@multiplayer-app/session-recorder-react-native"]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The plugin will automatically be detected via the `app.plugin.js` file in the package root.
|
|
50
|
+
|
|
51
|
+
3. **Run prebuild** (if using development build):
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx expo prebuild --clean
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
4. **Install iOS dependencies**:
|
|
58
|
+
```bash
|
|
59
|
+
cd ios && pod install && cd ..
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### For React Native CLI Projects
|
|
63
|
+
|
|
64
|
+
1. **Install the package**:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm install @multiplayer-app/session-recorder-react-native
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
2. **iOS Setup**:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
cd ios && pod install && cd ..
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
3. **Android Setup**: No additional steps required (auto-linking handles it)
|
|
77
|
+
|
|
78
|
+
## Troubleshooting
|
|
79
|
+
|
|
80
|
+
### "Pod SessionRecorderNative files are missing"
|
|
81
|
+
|
|
82
|
+
This error occurs when the iOS native files aren't properly included in the package or auto-linking fails.
|
|
83
|
+
|
|
84
|
+
**Solutions**:
|
|
85
|
+
|
|
86
|
+
1. **Check package installation**:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
ls node_modules/@multiplayer-app/session-recorder-react-native/ios/
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Should show: `SessionRecorderNative.m`, `SessionRecorderNative.podspec`, `SessionRecorderNative.swift`
|
|
93
|
+
|
|
94
|
+
2. **Clear and reinstall**:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
rm -rf node_modules
|
|
98
|
+
npm install
|
|
99
|
+
cd ios && pod install --repo-update && cd ..
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
3. **For Expo projects, ensure plugin is configured**:
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"expo": {
|
|
106
|
+
"plugins": ["@multiplayer-app/session-recorder-react-native"]
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Auto-linking Issues
|
|
112
|
+
|
|
113
|
+
If auto-linking doesn't work:
|
|
114
|
+
|
|
115
|
+
1. **Check react-native.config.js** (should be automatically created):
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
module.exports = {
|
|
119
|
+
dependencies: {
|
|
120
|
+
'@multiplayer-app/session-recorder-react-native': {
|
|
121
|
+
platforms: {
|
|
122
|
+
android: {
|
|
123
|
+
sourceDir: '../android',
|
|
124
|
+
packageImportPath: 'import com.multiplayer.sessionrecorder.SessionRecorderPackage;'
|
|
125
|
+
},
|
|
126
|
+
ios: {
|
|
127
|
+
podspecPath: '../ios/SessionRecorderNative.podspec'
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
2. **Manual linking** (if auto-linking fails):
|
|
136
|
+
- iOS: Add to Podfile manually
|
|
137
|
+
- Android: Add to MainApplication.java manually
|
|
138
|
+
|
|
139
|
+
## File Structure
|
|
140
|
+
|
|
141
|
+
The package includes these native files:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
ios/
|
|
145
|
+
├── SessionRecorderNative.m # Objective-C bridge
|
|
146
|
+
├── SessionRecorderNative.podspec # CocoaPods specification
|
|
147
|
+
└── SessionRecorderNative.swift # Swift implementation
|
|
148
|
+
|
|
149
|
+
android/
|
|
150
|
+
├── build.gradle # Android build configuration
|
|
151
|
+
└── src/main/java/com/multiplayer/sessionrecorder/
|
|
152
|
+
├── SessionRecorderModule.kt # Main Android module
|
|
153
|
+
├── SessionRecorderPackage.kt # Package registration
|
|
154
|
+
├── ScreenMaskingModule.kt # Screen masking module
|
|
155
|
+
└── ScreenMaskingPackage.kt # Screen masking package
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Verification
|
|
159
|
+
|
|
160
|
+
To verify the setup is working:
|
|
161
|
+
|
|
162
|
+
1. **Check iOS**:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
cd ios && pod list | grep SessionRecorderNative
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
2. **Check Android**: Look for the package in `android/app/src/main/java/`
|
|
169
|
+
|
|
170
|
+
3. **Test in code**:
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
import { SessionRecorderNative } from '@multiplayer-app/session-recorder-react-native'
|
|
174
|
+
|
|
175
|
+
// This should not throw an error
|
|
176
|
+
console.log('Native module loaded:', SessionRecorderNative)
|
|
177
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_MODULE(SessionRecorderNative, NSObject)
|
|
4
|
+
|
|
5
|
+
RCT_EXTERN_METHOD(captureAndMask:(RCTPromiseResolveBlock)resolve
|
|
6
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
7
|
+
|
|
8
|
+
RCT_EXTERN_METHOD(captureAndMaskWithOptions:(NSDictionary *)options
|
|
9
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
10
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
11
|
+
|
|
12
|
+
+ (BOOL)requiresMainQueueSetup
|
|
13
|
+
{
|
|
14
|
+
return YES;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "..", "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "SessionRecorderNative"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = "Native session recorder module for React Native"
|
|
9
|
+
s.description = "A native module that provides session recording with automatic masking of sensitive UI elements"
|
|
10
|
+
s.homepage = "https://github.com/multiplayer-app/multiplayer-session-recorder-javascript"
|
|
11
|
+
s.license = "MIT"
|
|
12
|
+
s.authors = { "Multiplayer Software, Inc." => "https://www.multiplayer.app" }
|
|
13
|
+
s.platforms = { :ios => "12.0" }
|
|
14
|
+
s.source = { :git => "https://github.com/multiplayer-app/multiplayer-session-recorder-javascript.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
17
|
+
s.requires_arc = true
|
|
18
|
+
|
|
19
|
+
s.dependency "React-Core"
|
|
20
|
+
s.dependency "React"
|
|
21
|
+
|
|
22
|
+
# Ensure proper linking for Expo
|
|
23
|
+
s.pod_target_xcconfig = {
|
|
24
|
+
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/React-Core/React\""
|
|
25
|
+
}
|
|
26
|
+
end
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import React
|
|
3
|
+
|
|
4
|
+
@objc(SessionRecorderNative)
|
|
5
|
+
class SessionRecorderNative: NSObject {
|
|
6
|
+
|
|
7
|
+
@objc func captureAndMask(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
8
|
+
DispatchQueue.main.async {
|
|
9
|
+
guard let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) else {
|
|
10
|
+
reject("NO_WINDOW", "Unable to get key window", nil)
|
|
11
|
+
return
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
UIGraphicsBeginImageContextWithOptions(window.bounds.size, false, UIScreen.main.scale)
|
|
15
|
+
window.drawHierarchy(in: window.bounds, afterScreenUpdates: true)
|
|
16
|
+
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
|
|
17
|
+
UIGraphicsEndImageContext()
|
|
18
|
+
|
|
19
|
+
guard let image = screenshot else {
|
|
20
|
+
reject("CAPTURE_FAILED", "Failed to capture screen", nil)
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Apply masking to sensitive elements
|
|
25
|
+
let maskedImage = self.applyMasking(to: image, in: window)
|
|
26
|
+
|
|
27
|
+
if let data = maskedImage.jpegData(compressionQuality: 0.5) {
|
|
28
|
+
let base64 = data.base64EncodedString()
|
|
29
|
+
resolve(base64)
|
|
30
|
+
} else {
|
|
31
|
+
reject("ENCODING_FAILED", "Failed to encode image", nil)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@objc func captureAndMaskWithOptions(_ options: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
37
|
+
DispatchQueue.main.async {
|
|
38
|
+
guard let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) else {
|
|
39
|
+
reject("NO_WINDOW", "Unable to get key window", nil)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
UIGraphicsBeginImageContextWithOptions(window.bounds.size, false, UIScreen.main.scale)
|
|
44
|
+
window.drawHierarchy(in: window.bounds, afterScreenUpdates: true)
|
|
45
|
+
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
|
|
46
|
+
UIGraphicsEndImageContext()
|
|
47
|
+
|
|
48
|
+
guard let image = screenshot else {
|
|
49
|
+
reject("CAPTURE_FAILED", "Failed to capture screen", nil)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Apply masking with custom options
|
|
54
|
+
let maskedImage = self.applyMaskingWithOptions(to: image, in: window, options: options)
|
|
55
|
+
|
|
56
|
+
if let data = maskedImage.jpegData(compressionQuality: 0.5) {
|
|
57
|
+
let base64 = data.base64EncodedString()
|
|
58
|
+
resolve(base64)
|
|
59
|
+
} else {
|
|
60
|
+
reject("ENCODING_FAILED", "Failed to encode image", nil)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private func applyMasking(to image: UIImage, in window: UIWindow) -> UIImage {
|
|
66
|
+
return applyMaskingWithOptions(to: image, in: window, options: [:])
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private func applyMaskingWithOptions(to image: UIImage, in window: UIWindow, options: NSDictionary) -> UIImage {
|
|
70
|
+
UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale)
|
|
71
|
+
guard let context = UIGraphicsGetCurrentContext() else { return image }
|
|
72
|
+
|
|
73
|
+
// Draw the original image
|
|
74
|
+
image.draw(in: CGRect(origin: .zero, size: image.size))
|
|
75
|
+
|
|
76
|
+
// Find and mask sensitive elements
|
|
77
|
+
let sensitiveElements = findSensitiveElements(in: window)
|
|
78
|
+
|
|
79
|
+
for element in sensitiveElements {
|
|
80
|
+
let frame = element.frame
|
|
81
|
+
let maskingType = getMaskingType(for: element)
|
|
82
|
+
|
|
83
|
+
switch maskingType {
|
|
84
|
+
case .blur:
|
|
85
|
+
applyBlurMask(in: context, frame: frame)
|
|
86
|
+
case .rectangle:
|
|
87
|
+
applyRectangleMask(in: context, frame: frame)
|
|
88
|
+
case .pixelate:
|
|
89
|
+
applyPixelateMask(in: context, frame: frame, image: image)
|
|
90
|
+
case .none:
|
|
91
|
+
break
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let maskedImage = UIGraphicsGetImageFromCurrentImageContext() ?? image
|
|
96
|
+
UIGraphicsEndImageContext()
|
|
97
|
+
|
|
98
|
+
return maskedImage
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private func findSensitiveElements(in view: UIView) -> [UIView] {
|
|
102
|
+
var sensitiveElements: [UIView] = []
|
|
103
|
+
|
|
104
|
+
func traverseView(_ view: UIView) {
|
|
105
|
+
// Check if this view should be masked
|
|
106
|
+
if shouldMaskView(view) {
|
|
107
|
+
sensitiveElements.append(view)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Recursively check subviews
|
|
111
|
+
for subview in view.subviews {
|
|
112
|
+
traverseView(subview)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
traverseView(view)
|
|
117
|
+
return sensitiveElements
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private func shouldMaskView(_ view: UIView) -> Bool {
|
|
121
|
+
// Check for UITextField - mask all text fields when inputMasking is enabled
|
|
122
|
+
if view is UITextField {
|
|
123
|
+
return true
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Check for UITextView - mask all text views when inputMasking is enabled
|
|
127
|
+
if view is UITextView {
|
|
128
|
+
return true
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return false
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private func getMaskingType(for view: UIView) -> MaskingType {
|
|
135
|
+
// Default masking type for all text inputs
|
|
136
|
+
return .rectangle
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private func applyBlurMask(in context: CGContext, frame: CGRect) {
|
|
140
|
+
// Create a blur effect
|
|
141
|
+
context.setFillColor(UIColor.black.withAlphaComponent(0.8).cgColor)
|
|
142
|
+
context.fill(frame)
|
|
143
|
+
|
|
144
|
+
// Add some noise to make it look blurred
|
|
145
|
+
context.setFillColor(UIColor.white.withAlphaComponent(0.3).cgColor)
|
|
146
|
+
for _ in 0..<20 {
|
|
147
|
+
let randomX = frame.origin.x + CGFloat.random(in: 0...frame.width)
|
|
148
|
+
let randomY = frame.origin.y + CGFloat.random(in: 0...frame.height)
|
|
149
|
+
let randomSize = CGFloat.random(in: 2...8)
|
|
150
|
+
context.fillEllipse(in: CGRect(x: randomX, y: randomY, width: randomSize, height: randomSize))
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private func applyRectangleMask(in context: CGContext, frame: CGRect) {
|
|
155
|
+
// Simple rectangle fill
|
|
156
|
+
context.setFillColor(UIColor.gray.cgColor)
|
|
157
|
+
context.fill(frame)
|
|
158
|
+
|
|
159
|
+
// Add some text-like pattern
|
|
160
|
+
context.setFillColor(UIColor.darkGray.cgColor)
|
|
161
|
+
let lineHeight: CGFloat = 4
|
|
162
|
+
let spacing: CGFloat = 8
|
|
163
|
+
|
|
164
|
+
for i in stride(from: frame.origin.y + spacing, to: frame.origin.y + frame.height - spacing, by: lineHeight + spacing) {
|
|
165
|
+
let lineWidth = CGFloat.random(in: frame.width * 0.3...frame.width * 0.8)
|
|
166
|
+
let lineX = frame.origin.x + CGFloat.random(in: 0...(frame.width - lineWidth))
|
|
167
|
+
context.fill(CGRect(x: lineX, y: i, width: lineWidth, height: lineHeight))
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private func applyPixelateMask(in context: CGContext, frame: CGRect, image: UIImage) {
|
|
172
|
+
// Create a pixelated effect
|
|
173
|
+
let pixelSize: CGFloat = 8
|
|
174
|
+
let pixelCountX = Int(frame.width / pixelSize)
|
|
175
|
+
let pixelCountY = Int(frame.height / pixelSize)
|
|
176
|
+
|
|
177
|
+
for x in 0..<pixelCountX {
|
|
178
|
+
for y in 0..<pixelCountY {
|
|
179
|
+
let pixelFrame = CGRect(
|
|
180
|
+
x: frame.origin.x + CGFloat(x) * pixelSize,
|
|
181
|
+
y: frame.origin.y + CGFloat(y) * pixelSize,
|
|
182
|
+
width: pixelSize,
|
|
183
|
+
height: pixelSize
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
// Use a random color for each pixel
|
|
187
|
+
let randomColor = UIColor(
|
|
188
|
+
red: CGFloat.random(in: 0...1),
|
|
189
|
+
green: CGFloat.random(in: 0...1),
|
|
190
|
+
blue: CGFloat.random(in: 0...1),
|
|
191
|
+
alpha: 1.0
|
|
192
|
+
)
|
|
193
|
+
context.setFillColor(randomColor.cgColor)
|
|
194
|
+
context.fill(pixelFrame)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private enum MaskingType {
|
|
201
|
+
case blur
|
|
202
|
+
case rectangle
|
|
203
|
+
case pixelate
|
|
204
|
+
case none
|
|
205
|
+
}
|