Skip to content

Commit

Permalink
localStorage / AsyncStorage getEntireStorage feature +
Browse files Browse the repository at this point in the history
build diagnostics + ReadMe update
  • Loading branch information
RNEvok committed Jul 18, 2024
1 parent 4aa8ca7 commit 8de2250
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 3 deletions.
42 changes: 42 additions & 0 deletions Connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ class Connector {
private _encryption: any = null;
private _asyncStorageHandler: any = null;
private _trackAsyncStorage: (() => void) | undefined;
private _getEntireAsyncStorageAsObject: (() => Promise<T.ObjectT<any>>) | undefined;
private _untrackAsyncStorage: (() => void) | undefined;
private _localStorageHandler: any = null;
private _trackLocalStorage: (() => void) | undefined;
private _getEntireLocalStorageAsObject: (() => T.ObjectT<any>) | undefined;
private _untrackLocalStorage: (() => void) | undefined;
private _storageActionsBatchingTimeMs: number = 500;
private _sendStorageActionsBatchingTimer: NodeJS.Timeout | null = null;
Expand Down Expand Up @@ -270,6 +272,32 @@ class Connector {
);
};

private async _getEntireStorageAsObject() {
let obj: T.ObjectT<any>;
let storageType: T.StorageType = "unknown";

if (this._getEntireLocalStorageAsObject) {
obj = this._getEntireLocalStorageAsObject();
storageType = "localStorage";
} else if (this._getEntireAsyncStorageAsObject) {
obj = await this._getEntireAsyncStorageAsObject();
storageType = "AsyncStorage";
} else {
const info = "Unable to get entire storage as object: No AsyncStorage / localStorage monitor set up";
codebudConsoleWarn(info);
obj = {info};
}

const storageSnapshot: T.StorageSnapshot = {
timestamp: moment().valueOf(),
storageType,
storageAsObject: obj
};

const encryptedData = this._encryptData(storageSnapshot);
encryptedData.ok && this._socket?.emit(SOCKET_EVENTS_EMIT.SAVE_FULL_STORAGE_SNAPSHOT, encryptedData.result);
};

public init(apiKey: string, instructions: T.Instruction[], usersCustomCallback: T.OnEventUsersCustomCallback, config?: T.PackageConfig) {
this._apiKey = apiKey;
this._fillInstructionsTable(instructions);
Expand Down Expand Up @@ -332,6 +360,16 @@ class Connector {

this._socket.on(SOCKET_EVENTS_LISTEN.SAVE_NEW_REMOTE_SETTINGS, (r: T.RemoteSettings) => remoteSettingsService.onGotNewRemoteSettings(r));

this._socket.on(SOCKET_EVENTS_LISTEN.FORCE_REFRESH, (data: T.ForceRefreshPayload) => {
switch (data.type) {
case "storage":
this._getEntireStorageAsObject();
break;
default:
break;
}
});

this._socket.on(SOCKET_EVENTS_LISTEN.CONNECT_ERROR, (err) => {
codebudConsoleWarn(`Socket connect_error: ${err.message}`);
});
Expand Down Expand Up @@ -445,6 +483,7 @@ class Connector {
// passing Connector class context to asyncStoragePlugin function
const controlFunctions = asyncStoragePlugin.bind(this as any)(ignoreKeys);
this._trackAsyncStorage = controlFunctions.trackAsyncStorage;
this._getEntireAsyncStorageAsObject = controlFunctions.getEntireAsyncStorageAsObject;
this._untrackAsyncStorage = controlFunctions.untrackAsyncStorage;
}

Expand All @@ -454,6 +493,7 @@ class Connector {
// passing Connector class context to localStoragePlugin function
const controlFunctions = localStoragePlugin.bind(this as any)(ignoreKeys);
this._trackLocalStorage = controlFunctions.trackLocalStorage;
this._getEntireLocalStorageAsObject = controlFunctions.getEntireLocalStorageAsObject;
this._untrackLocalStorage = controlFunctions.untrackLocalStorage;
}

Expand Down Expand Up @@ -729,13 +769,15 @@ class Connector {
if (this._asyncStorageHandler) {
this._untrackAsyncStorage?.();
this._untrackAsyncStorage = undefined;
this._getEntireAsyncStorageAsObject = undefined;
this._trackAsyncStorage = undefined;
this._asyncStorageHandler = null;
}

if (this._localStorageHandler) {
this._untrackLocalStorage?.();
this._untrackLocalStorage = undefined;
this._getEntireLocalStorageAsObject = undefined;
this._trackLocalStorage = undefined;
this._localStorageHandler = null;
}
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ JavaScript package for remote app testing & debugging
* Redux state observer + action monitor
* Zustand state observer
* TanStack query observer
* MobX state observer
* React contexts monitoring
* Storage actions monitoring
* Feature management
Expand Down
2 changes: 2 additions & 0 deletions api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const SOCKET_EVENTS_LISTEN = {
DISCONNECT: "disconnect",
SAVE_NEW_REMOTE_SETTINGS: "saveNewRemoteSettings",
ADMIN_CONNECTED: "ADMIN_CONNECTED",
FORCE_REFRESH: "forceRefresh"
};

const SOCKET_EVENTS_EMIT = {
Expand All @@ -27,6 +28,7 @@ const SOCKET_EVENTS_EMIT = {
SAVE_INTERCEPTED_RESPONSE: "saveInterceptedResponse",
SAVE_MOBILE_APP_STATE: "saveMobileAppState",
SAVE_INTERCEPTED_STORAGE_ACTIONS_BATCH: "saveInterceptedStorageActionsBatch",
SAVE_FULL_STORAGE_SNAPSHOT: "saveFullStorageSnapshot",
CAPTURE_EVENT: "captureEvent",
CAPTURE_CRASH_REPORT : "captureCrashReport",
SAVE_TANSTACK_QUERIES_DATA_COPY: "saveTanStackQueriesDataCopy",
Expand Down
31 changes: 31 additions & 0 deletions asyncStorage/asyncStorage.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { CONFIG } from "./../config";
import { codebudConsoleWarn, errorToJSON, stringIsJson } from "./../helpers/helperFunctions";

Check failure on line 2 in asyncStorage/asyncStorage.ts

View workflow job for this annotation

GitHub Actions / Prepare_and_publish

Module '"./../helpers/helperFunctions"' has no exported member 'stringIsJson'.
import { ObjectT } from "./../types";

type ConnectorContext = {
_asyncStorageHandler: any;
_handleInterceptedStorageAction: (action: string, data?: any) => void;
Expand Down Expand Up @@ -114,6 +118,32 @@ export function asyncStoragePlugin (this: ConnectorContext, ignoreKeys: string[]
return swizzMultiMerge(pairs, callback)
}

const getEntireAsyncStorageAsObject = async (): Promise<ObjectT<any>> => {
try {
if (!isSwizzled)
throw new Error("AsyncStorage monitor not set up");

const keys = await this._asyncStorageHandler.getAllKeys();

if (keys.length > CONFIG.PAYLOAD_LIMITS.MAX_KEYS_IN_STORAGE)
throw new Error(`AsyncStorage is too large to handle (${keys.length} keys found)`);

const values = await swizzMultiGet(keys);
const obj: ObjectT<any> = {};

values.forEach(([key, value]: [string, string]) => {
obj[key] = stringIsJson(value) ? JSON.parse(value) : value;
});

return obj;
} catch (e) {
const info = "Unable to prepare entire AsyncStorage as object";
codebudConsoleWarn(info, e);

return { info, error: errorToJSON(e) };
}
}

const trackAsyncStorage = () => {
if (isSwizzled)
return;
Expand Down Expand Up @@ -172,6 +202,7 @@ export function asyncStoragePlugin (this: ConnectorContext, ignoreKeys: string[]

return {
trackAsyncStorage,
getEntireAsyncStorageAsObject,
untrackAsyncStorage
};
}
3 changes: 2 additions & 1 deletion config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ const CONFIG_INNER = {
PAYLOAD_LIMITS: {
MAX_KB_SIZE: PAYLOAD_LIMIT_MAX_KB_SIZE,
MAX_BYTE_SIZE: 1024 * PAYLOAD_LIMIT_MAX_KB_SIZE,
MIN_STRING_LENGTH_POSSIBLE_OVERLOAD: 1024 * PAYLOAD_LIMIT_MAX_KB_SIZE / 4 // MAX_BYTE_SIZE / 4 (4 is max possible byteSize of UTF char)
MIN_STRING_LENGTH_POSSIBLE_OVERLOAD: 1024 * PAYLOAD_LIMIT_MAX_KB_SIZE / 4, // MAX_BYTE_SIZE / 4 (4 is max possible byteSize of UTF char)
MAX_KEYS_IN_STORAGE: 2048
}
};

Expand Down
29 changes: 29 additions & 0 deletions localStorage/localStorage.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { CONFIG } from "./../config";
import { codebudConsoleWarn, errorToJSON, stringIsJson } from "./../helpers/helperFunctions";

Check failure on line 2 in localStorage/localStorage.ts

View workflow job for this annotation

GitHub Actions / Prepare_and_publish

Module '"./../helpers/helperFunctions"' has no exported member 'stringIsJson'.
import { ObjectT } from "./../types";

type ConnectorContext = {
_localStorageHandler: any;
_handleInterceptedStorageAction: (action: string, data?: any) => void;
Expand Down Expand Up @@ -43,6 +47,30 @@ export function localStoragePlugin (this: ConnectorContext, ignoreKeys: string[]
swizzRemoveItem(key);
}

const getEntireLocalStorageAsObject = (): ObjectT<any> => {
try {
if (!isSwizzled)
throw new Error("localStorage monitor not set up");

const obj = {...this._localStorageHandler};
const keys = Object.keys(obj);

if (keys.length > CONFIG.PAYLOAD_LIMITS.MAX_KEYS_IN_STORAGE)
throw new Error(`localStorage is too large to handle (${keys.length} keys found)`);

for (const key of keys)
if (stringIsJson(obj[key]))
obj[key] = JSON.parse(obj[key]);

return obj;
} catch (e) {
const info = "Unable to prepare entire localStorage as object";
codebudConsoleWarn(info, e);

return { info, error: errorToJSON(e) };
}
}

const trackLocalStorage = () => {
if (isSwizzled)
return;
Expand Down Expand Up @@ -81,6 +109,7 @@ export function localStoragePlugin (this: ConnectorContext, ignoreKeys: string[]

return {
trackLocalStorage,
getEntireLocalStorageAsObject,
untrackLocalStorage
};
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"test": "jest",
"start": "ts-node index.ts",
"build": "tsc --build tsconfig.build.json",
"build": "tsc --build tsconfig.build.json --diagnostics",
"clean": "tsc --build --clean"
},
"author": "Aleksandr Nikolotov",
Expand Down
14 changes: 13 additions & 1 deletion types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,16 @@ export type InterceptedMobxEventPreparedData = WithStackTrace<{
mobxEventId: string;
event: MobxSpyEvent;
timestamp: number;
}>;
}>;

export type StorageType = "unknown" | "localStorage" | "AsyncStorage";

export type ForceRefreshPayload = {
type: "storage";
};

export type StorageSnapshot = {
timestamp: number;
storageType: StorageType;
storageAsObject: ObjectT<any>;
};

0 comments on commit 8de2250

Please sign in to comment.