UNPKG

@johnf/react-native-owl

Version:
200 lines (190 loc) 6.83 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.waitForWebSocket = exports.patchReact = exports.initClient = exports.handleMessage = exports.applyJsxChildrenElementTracking = exports.applyElementTracking = void 0; var _react = _interopRequireDefault(require("react")); var _reactNative = require("react-native"); var _logger = require("../logger.js"); var _constants = require("./constants.js"); var _websocket = require("./websocket.js"); var _trackedElements = require("./trackedElements.js"); var _handleAction = require("./handleAction.js"); require("../NativeOwl.js"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } // NOTE: On new arch the module isn't autoloaded const logger = new _logger.Logger(true); let isReactUpdating = true; let owlClient; const initClient = () => { logger.info('[OWL - Client] Initialising OWL client'); patchReact(); waitForWebSocket(); }; /** * Based on an elements props, store element tracking data and return updated props */ exports.initClient = initClient; const applyElementTracking = (props, isJsx = false) => { if (isJsx) { applyJsxChildrenElementTracking(props); } const testID = props?.testID; const returnProps = { ...props, showsHorizontalScrollIndicator: false, showsVerticalScrollIndicator: false }; if (!testID) { return returnProps; } const existingTrackedElement = (0, _trackedElements.get)(testID); const ref = props?.ref || existingTrackedElement?.ref || /*#__PURE__*/_react.default.createRef(); const trackData = { ref: existingTrackedElement?.ref || ref, onPress: existingTrackedElement?.onPress || props?.onPress, onLongPress: existingTrackedElement?.onLongPress || props?.onLongPress, onChangeText: existingTrackedElement?.onChangeText || props?.onChangeText }; (0, _trackedElements.add)(logger, testID, trackData); return { ...returnProps, ref }; }; /** * To get access to the prop callbacks when the element is created, we need to check the children */ exports.applyElementTracking = applyElementTracking; const applyJsxChildrenElementTracking = props => { if (props.children && Array.isArray(props.children)) { props.children.forEach(child => { const testID = child?.props?.testID; if (!testID) { return; } const existingTrackedElement = (0, _trackedElements.get)(testID); const ref = child?.props?.ref || existingTrackedElement?.ref || /*#__PURE__*/_react.default.createRef(); const trackData = { ref: existingTrackedElement?.ref || ref, onPress: existingTrackedElement?.onPress || child?.props?.onPress, onLongPress: existingTrackedElement?.onLongPress || child?.props?.onLongPress, onChangeText: existingTrackedElement?.onChangeText || child?.props?.onChangeText }; (0, _trackedElements.add)(logger, testID, trackData); }); } }; /** * We patch react so that we can maintain a list of elements that have testID's * We can then use this list to find the element when we receive an action */ exports.applyJsxChildrenElementTracking = applyJsxChildrenElementTracking; const patchReact = () => { const originalReactCreateElement = _react.default.createElement; let automateTimeout; if (parseInt(_react.default.version.split('.')?.[0] || '0', 10) >= 18) { const jsxRuntime = require('react/jsx-runtime'); const origJsx = jsxRuntime.jsx; // @ts-ignore jsxRuntime.jsx = (type, config, maybeKey) => { const newProps = applyElementTracking(config, true); clearTimeout(automateTimeout); automateTimeout = setTimeout(() => { isReactUpdating = false; }, _constants.CHECK_INTERVAL); isReactUpdating = true; return origJsx(type, newProps, maybeKey); }; } // @ts-ignore _react.default.createElement = (type, props, ...children) => { const newProps = applyElementTracking(props); clearTimeout(automateTimeout); automateTimeout = setTimeout(() => { isReactUpdating = false; }, _constants.CHECK_INTERVAL); isReactUpdating = true; return originalReactCreateElement(type, newProps, ...children); }; }; /** * The app might launch before the OWL server starts, so we need to keep trying... */ exports.patchReact = patchReact; const waitForWebSocket = async () => { try { owlClient = await (0, _websocket.initWebSocket)(logger, _reactNative.Platform.OS === 'android' ? 'android' : 'ios', handleMessage); logger.info('[OWL - Websocket] Connection established'); } catch { setTimeout(waitForWebSocket, _constants.SOCKET_WAIT_TIMEOUT); } }; /** * When we receive a message, we need to find the element that corresponds to the testID, * then attempt to handle the requested action on it. */ exports.waitForWebSocket = waitForWebSocket; const handleMessage = async message => { const socketEvent = JSON.parse(message); const testID = socketEvent.testID; let element; try { element = await getElementByTestId(testID); } catch (error) { sendNotFound(testID); } if (element) { try { if (socketEvent.type === 'ACTION') { (0, _handleAction.handleAction)(logger, testID, element, socketEvent.action, socketEvent.value); setTimeout(sendDone, 1000); } else { sendDone(); } } catch (error) { let message = 'Unknown error'; if (error instanceof Error) { message = error.message; } sendError(testID, message); } } }; exports.handleMessage = handleMessage; const sendEvent = event => owlClient.send(JSON.stringify(event)); const sendNotFound = testID => sendEvent({ type: 'NOT_FOUND', testID }); const sendDone = () => sendEvent({ type: 'DONE' }); const sendError = (testID, message) => sendEvent({ type: 'ERROR', testID, message }); /** * This function resolves the tracked element by its testID, so that we can handle events on it. * If the element is not immedietly available, we wait for it to be available for some time. */ const getElementByTestId = async testID => new Promise((resolve, reject) => { logger.info(`[OWL - Client] Looking for Element with testID ${testID}`); const rejectTimeout = setTimeout(() => { logger.error(`[OWL - Client] ❌ not found`); clearInterval(checkInterval); reject(new Error(`Element with testID ${testID} not found`)); }, _constants.MAX_CHECK_TIMEOUT); const checkInterval = setInterval(() => { const element = (0, _trackedElements.get)(testID); if (isReactUpdating || !element) { return; } logger.info(`[OWL - Client] ✓ found`); clearInterval(checkInterval); clearTimeout(rejectTimeout); resolve(element); }, _constants.CHECK_INTERVAL); }); //# sourceMappingURL=client.js.map