react-native-xenon
Version:
A powerful in-app debugging tool for React Native.
185 lines (150 loc) • 5.21 kB
text/typescript
import { URL } from 'react-native-url-polyfill';
import { NetworkType, type HttpRequest, type LogMessage, type WebSocketRequest } from '../types';
import colors from '../theme/colors';
import { Share } from 'react-native';
import refs, { DebuggerVisibility } from './refs';
let isSharing = false;
export const shareText = async (text: string) => {
if (isSharing) return;
try {
isSharing = true;
refs.debugger.current?.setCurrentIndex(DebuggerVisibility.Bubble);
await Share.share({
message: text.trim(),
});
} catch (error) {
// Handle error
} finally {
refs.debugger.current?.setCurrentIndex(DebuggerVisibility.Panel);
isSharing = false;
}
};
export const getNetworkUtils = (data?: HttpRequest | WebSocketRequest) => {
if (!data || !data.url) return {};
const isWS = data?.type === NetworkType.WS;
const requestUrl = new URL(data.url);
const overviewShown = !!data.url;
const httpHeadersShown = !isWS && (!!data.requestHeaders?.size || !!data.responseHeaders?.size);
const websocketHeadersShown = isWS && !!Object.keys(data.options?.headers ?? {}).length;
const headersShown = httpHeadersShown || websocketHeadersShown;
const requestShown = !isWS && (!!requestUrl.search || !!data.body);
const responseShown = !isWS && !!data.response;
const messagesShown = isWS && !!data.messages;
return {
isWS,
requestUrl,
overviewShown,
headersShown,
requestShown,
responseShown,
messagesShown,
};
};
const hexToHexAlpha = (hex: string, opacity: number) =>
`${hex}${`${(Math.min(Math.max(opacity, 0), 1) * 255).toString(16)}0`.slice(0, 2)}`;
export const getConsoleTypeColor = (type: LogMessage['type']) => {
let color: string;
switch (type) {
case 'log':
color = colors.white;
break;
case 'info':
color = colors.blue;
break;
case 'warn':
case 'debug':
case 'trace':
color = colors.yellow;
break;
case 'error':
color = colors.red;
break;
default:
color = colors.white;
}
return hexToHexAlpha(color, 0.25);
};
//#region metrics
export const getVerticalSafeMargin = (screenHeight: number) => screenHeight / 8;
export const clamp = (min: number, max: number, value: number) =>
Math.max(min, Math.min(max, value));
export const getHttpInterceptorId = () => {
const timestamp = Date.now().toString(36);
const randomNum = Math.random().toString(36).substring(2, 10);
return timestamp + randomNum;
};
//#endregion
//#region formatters
export const showNewLine = (when: boolean) => (when ? '\n' : '');
const limitChar = (value: any, limit = 100000) => {
const stringValue = typeof value === 'string' ? value : JSON.stringify(value ?? '');
return stringValue.length > limit
? `${stringValue.slice(0, limit)}\n---LIMITED TO ${limit} CHARACTERS---`
: stringValue;
};
export const keyValueToString = (
key: string,
value: any,
newLine: 'leading' | 'trailing' | null = 'trailing',
): string =>
`${newLine === 'leading' ? '\n' : ''}${key}: ${limitChar(value)}${showNewLine(newLine === 'trailing')}`;
export const formatRequestMethod = (method?: string) => method ?? 'GET';
export const formatRequestDuration = (startTime?: number, endTime?: number) => {
if (typeof startTime !== 'number' || typeof endTime !== 'number') return 'pending';
return `${endTime - startTime}ms`;
};
export const formatRequestStatusCode = (statusCode?: number) => `${statusCode ?? 'pending'}`;
export const formatLogMessage = (values: any[]) => {
return values.reduce((pre, cur, index) => pre + (!index ? '' : ', ') + limitChar(cur), '');
};
export const beautify = (data: any, beautified: boolean) => {
if (!data) return '';
try {
const res = typeof data === 'string' ? JSON.parse(data) : data;
return beautified ? JSON.stringify(res, null, 4) : limitChar(res);
} catch (error) {
return limitChar(data);
}
};
export const convertToCurl = (
method: HttpRequest['method'],
url: HttpRequest['url'],
headers: HttpRequest['requestHeaders'],
body: HttpRequest['body'],
) => {
let curlCommand = `curl -X ${method.toUpperCase()} "${url}"`;
if (headers) {
for (const [key, value] of headers.entries()) {
curlCommand += ` -H "${key}: ${value}"`;
}
}
if (body) {
const bodyString = typeof body === 'string' ? body : JSON.stringify(body);
curlCommand += ` -d '${bodyString}'`;
}
return curlCommand;
};
export const formatCount = (count: number) => {
if (count < 1000) return count.toString();
if (count < 1000000) return `${(count / 1000).toFixed(1)}K`;
return `${(count / 1000000).toFixed(1)}M`;
};
//#endregion
//#region decorators
export function frozen(_target: Object) {
const descriptor: PropertyDescriptor = arguments[2];
descriptor.configurable = false;
descriptor.writable = false;
}
export function singleton<T extends { new (...args: any[]): {} }>(constructor: T) {
class Singleton extends constructor {
static #instance: Singleton;
constructor(...args: any[]) {
if (Singleton.#instance) return Singleton.#instance;
super(...args);
Singleton.#instance = this;
}
}
return Singleton;
}
//#endregion