diff --git a/Connector.ts b/Connector.ts index 1cbf964..e8a931a 100644 --- a/Connector.ts +++ b/Connector.ts @@ -15,14 +15,15 @@ import { NetworkInterceptorInstance, NetworkInterceptorOnRequestPayload, NetworkInterceptorOnResponsePayload, - AdminConnectedData + AdminConnectedData, + InterceptedReduxAction } from './types'; import { CONFIG } from './config'; import { EventHandleError, ScenarioHandleError } from './Errors'; import { api, SOCKET_EVENTS_LISTEN, SOCKET_EVENTS_EMIT } from './api/api'; import { SPECIAL_INSTRUCTIONS_TABLE, SPECIAL_INSTRUCTIONS } from './constants/events'; import { io, Socket } from "socket.io-client"; -import { codebudConsoleLog, codebudConsoleWarn, stringifyIfNotString } from './helperFunctions'; +import { codebudConsoleLog, codebudConsoleWarn, jsonStringifyKeepMethods, stringifyIfNotString } from './helperFunctions'; import moment from 'moment'; export class Connector { @@ -30,6 +31,7 @@ export class Connector { private static _remoteSettings: RemoteSettings | null = null; private static _eventListenersTable: EventListenersTable = {}; private static _remoteSettingsListenersTable: RemoteSettingsListenersTable = {}; + private static _currentInterceptedReduxActionId = 0; private _apiKey: string; private _instructionsTable: InstructionsTable = {}; private _onEventUsersCustomCallback: OnEventUsersCustomCallback; @@ -41,6 +43,8 @@ export class Connector { private _socket: Socket; private _sendReduxStateBatchingTimer: NodeJS.Timeout | null = null; private _currentReduxStateCopy: any = null; + private _sendReduxActionsBatchingTimer: NodeJS.Timeout | null = null; + private _currentReduxActionsBatch: any[] = []; private _encryption: any = null; public static lastEvent: RemoteEvent | null = null; @@ -64,7 +68,7 @@ export class Connector { private _encryptData(json: any) { if (!this._encryption) - return JSON.stringify(json); + return jsonStringifyKeepMethods(json); return this._encryption.encryptData(json); }; @@ -350,6 +354,23 @@ export class Connector { } } + public handleDispatchedReduxAction(action: InterceptedReduxAction, batchingTimeMs: number) { + if (this._socket.connected) { + const timestamp = moment().valueOf(); + const actionId = Connector._currentInterceptedReduxActionId++; + this._currentReduxActionsBatch.push({actionId: `RA_${actionId}`, action, timestamp}); + + if (this._sendReduxActionsBatchingTimer) + clearTimeout(this._sendReduxActionsBatchingTimer); + + this._sendReduxActionsBatchingTimer = setTimeout(() => { + const encryptedData = this._encryptData({actions: this._currentReduxActionsBatch}); + this._socket?.emit(SOCKET_EVENTS_EMIT.SAVE_REDUX_ACTIONS_BATCH, encryptedData); + this._currentReduxActionsBatch = []; + }, batchingTimeMs); + } + } + public static get remoteSettings(): RemoteSettings | null { return Connector._remoteSettings; } @@ -363,6 +384,7 @@ export class Connector { this._lastEventLog = undefined; this._dataToForward = null; this._currentReduxStateCopy = null; + this._currentReduxActionsBatch = []; this._encryption = null; if (this._networkInterceptor) { diff --git a/Network/NetworkInterceptorClassic.ts b/Network/NetworkInterceptorClassic.ts index 23cdd32..f059271 100644 --- a/Network/NetworkInterceptorClassic.ts +++ b/Network/NetworkInterceptorClassic.ts @@ -8,7 +8,6 @@ class NetworkInterceptorClassic extends NetworkInterceptorApi { private _interceptor: ClientRequestInterceptor | null = null; protected async formatRequest(request: any) { - console.log('DEBUG', Object.fromEntries(request.headers)); let body: any; const isJsonContentType = request.headers.get("content-type")?.includes("application/json"); diff --git a/api/api.ts b/api/api.ts index 4eb4727..56cadb0 100644 --- a/api/api.ts +++ b/api/api.ts @@ -23,6 +23,7 @@ const SOCKET_EVENTS_EMIT = { EXECUTING_SCENARIO: "executingScenario", SAVE_SCENARIO_LOG: "saveScenarioLog", SAVE_REDUX_STATE_COPY: "saveReduxStateCopy", + SAVE_REDUX_ACTIONS_BATCH: "saveReduxActionsBatch", SAVE_INTERCEPTED_REQUEST: "saveInterceptedRequest", SAVE_INTERCEPTED_RESPONSE: "saveInterceptedResponse", SAVE_MOBILE_APP_STATE: "saveMobileAppState" diff --git a/encryption/index.ts b/encryption/index.ts index 9b027dc..a4b74e2 100644 --- a/encryption/index.ts +++ b/encryption/index.ts @@ -1,6 +1,6 @@ import { box, randomBytes } from 'tweetnacl'; import { decodeUTF8, encodeBase64 } from 'tweetnacl-util'; -import { codebudConsoleLog, codebudConsoleWarn } from '../helperFunctions'; +import { codebudConsoleLog, codebudConsoleWarn, jsonStringifyKeepMethods } from '../helperFunctions'; export class EncryptionPlugin { private _keyPair: nacl.BoxKeyPair | null = null; @@ -17,7 +17,7 @@ export class EncryptionPlugin { throw new Error("Shared key not generated"); const nonce = this.newNonce(); - const messageUint8 = decodeUTF8(JSON.stringify(json)); + const messageUint8 = decodeUTF8(jsonStringifyKeepMethods(json)); const encrypted = box.after(messageUint8, nonce, this._sharedKey); diff --git a/helperFunctions.ts b/helperFunctions.ts index e1b919f..c554484 100644 --- a/helperFunctions.ts +++ b/helperFunctions.ts @@ -1,4 +1,5 @@ import { CONFIG } from "./config"; +import { ObjectT } from "./types"; export function delay(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); @@ -35,4 +36,22 @@ export const codebudConsoleWarn = (...data: any[]) => { export const codebudConsoleLog = (...data: any[]) => { console.log(`${CONFIG.PRODUCT_NAME}:`, ...data); +} + +// @ts-ignore +export const emptyMiddleware = () => next => action => { + return next(action); +} + +export const jsonStringifyKeepMethods = (data: ObjectT) => { + return JSON.stringify( + data, + function(key, value) { + if (typeof value === 'function') { + return "Function (...)"; + } else { + return value; + } + } + ); } \ No newline at end of file diff --git a/index.d.ts b/index.d.ts index b1e61ac..7b506cb 100644 --- a/index.d.ts +++ b/index.d.ts @@ -143,6 +143,12 @@ declare module '@appklaar/codebud' { * @returns {Function} Store change handler function. */ createReduxStoreChangeHandler: (store: any, selectFn: (state: any) => any, batchingTimeMs?: number) => (() => void); + /** + * Function that creates Redux middleware for actions monitoring. + * @param {number} [batchingTimeMs = 200] batching time of sending dispatched redux actions (in ms). Defaults to 200. This param only affects logging delay and does not slow down your redux flow. + * @returns {Function} Middleware + */ + createReduxActionMonitorMiddleware: (batchingTimeMs?: number) => any; /** * Close the connection. */ diff --git a/index.ts b/index.ts index d4122d9..dce567d 100644 --- a/index.ts +++ b/index.ts @@ -5,7 +5,7 @@ import { EXISTING_SPECIAL_INSTRUCTION_IDS } from './constants/events'; import { validateApiKey } from './constants/regex'; import { AppKlaarSdk as ModuleInterface } from './moduleInterface'; import { CONFIG } from './config'; -import { codebudConsoleWarn } from './helperFunctions'; +import { codebudConsoleWarn, emptyMiddleware } from './helperFunctions'; export type { Instruction } from './types'; @@ -109,6 +109,16 @@ export const CodeBud: ModuleInterface = { } }, + createReduxActionMonitorMiddleware(batchingTimeMs = 200) { + // @ts-ignore + return () => next => action => { + if (this._connector) + this._connector.handleDispatchedReduxAction(action, batchingTimeMs); + + return next(action); + } + }, + disconnect() { this._connector && this._connector.disconnect(); this._connector = null; diff --git a/moduleInterface.ts b/moduleInterface.ts index 67760ae..52184d6 100644 --- a/moduleInterface.ts +++ b/moduleInterface.ts @@ -44,6 +44,12 @@ export interface AppKlaarSdk { * @returns {Function} Store change handler function. */ createReduxStoreChangeHandler: (store: any, selectFn: (state: any) => any, batchingTimeMs?: number) => (() => void); + /** + * Function that creates Redux middleware for actions monitoring. + * @param {number} [batchingTimeMs = 200] batching time of sending dispatched redux actions (in ms). Defaults to 200. This param only affects logging delay and does not slow down your redux flow. + * @returns {Function} Middleware + */ + createReduxActionMonitorMiddleware: (batchingTimeMs?: number) => any; /** * Close the connection. */ diff --git a/types.ts b/types.ts index e50d30b..866b2ac 100644 --- a/types.ts +++ b/types.ts @@ -26,6 +26,11 @@ export type InterceptedResponse = { responseHeaders?: ObjectT | undefined; }; +export type InterceptedReduxAction = { + type: string; + payload?: any; +} + export type NetworkInterceptorOnRequestPayload = { request: InterceptedRequest; requestId: string;