@rozenite/network-activity-plugin 1.0.0-alpha.12 → 1.0.0-alpha.13

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.
Files changed (42) hide show
  1. package/dist/App.html +2 -2
  2. package/dist/assets/{App-JuOeT_VQ.js → App-CZPlDq_M.js} +9056 -8965
  3. package/dist/assets/{App-DCuHdq4D.css → App-CfJuBHc_.css} +3 -0
  4. package/dist/rozenite.json +1 -1
  5. package/dist/src/shared/client.d.ts +9 -6
  6. package/dist/src/ui/components/CopyRequestDropdown.d.ts +7 -0
  7. package/dist/src/ui/components/RequestBody.d.ts +6 -0
  8. package/dist/src/ui/utils/checkRequestBodyBinary.d.ts +2 -0
  9. package/dist/src/ui/utils/generateCurlCommand.d.ts +2 -7
  10. package/dist/src/ui/utils/generateFetchCall.d.ts +2 -0
  11. package/dist/src/ui/utils/generateMultipartBody.d.ts +4 -0
  12. package/dist/src/utils/applyReactNativeRequestHeadersLogic.d.ts +1 -2
  13. package/dist/src/utils/applyReactNativeResponseHeadersLogic.d.ts +1 -2
  14. package/dist/src/utils/getHttpHeaderValueAsString.d.ts +11 -0
  15. package/dist/src/utils/typeChecks.d.ts +9 -0
  16. package/dist/useNetworkActivityDevTools.cjs +61 -31
  17. package/dist/useNetworkActivityDevTools.js +61 -31
  18. package/package.json +4 -4
  19. package/src/react-native/http/network-inspector.ts +60 -28
  20. package/src/react-native/sse/sse-inspector.ts +22 -4
  21. package/src/shared/client.ts +22 -6
  22. package/src/ui/components/CodeBlock.tsx +1 -1
  23. package/src/ui/components/CopyRequestDropdown.tsx +95 -0
  24. package/src/ui/components/KeyValueGrid.tsx +1 -4
  25. package/src/ui/components/RequestBody.tsx +86 -0
  26. package/src/ui/components/RequestList.tsx +1 -1
  27. package/src/ui/state/model.ts +6 -1
  28. package/src/ui/tabs/CookiesTab.tsx +2 -6
  29. package/src/ui/tabs/HeadersTab.tsx +2 -7
  30. package/src/ui/tabs/RequestTab.tsx +35 -51
  31. package/src/ui/utils/checkRequestBodyBinary.ts +7 -0
  32. package/src/ui/utils/generateCurlCommand.ts +9 -12
  33. package/src/ui/utils/generateFetchCall.ts +64 -0
  34. package/src/ui/utils/generateMultipartBody.ts +19 -0
  35. package/src/utils/applyReactNativeRequestHeadersLogic.ts +2 -3
  36. package/src/utils/applyReactNativeResponseHeadersLogic.ts +2 -3
  37. package/src/utils/getHttpHeaderValueAsString.ts +13 -0
  38. package/src/utils/typeChecks.ts +27 -0
  39. package/dist/src/ui/components/CopyAsCurlButton.d.ts +0 -5
  40. package/dist/src/utils/isNumber.d.ts +0 -1
  41. package/src/ui/components/CopyAsCurlButton.tsx +0 -45
  42. package/src/utils/isNumber.ts +0 -3
@@ -0,0 +1,95 @@
1
+ import { useCallback } from 'react';
2
+ import { Copy, Check, ChevronDown } from 'lucide-react';
3
+ import { Button } from './Button';
4
+ import { generateFetchCall } from '../utils/generateFetchCall';
5
+ import { generateCurlCommand } from '../utils/generateCurlCommand';
6
+ import { useCopyToClipboard } from '../hooks/useCopyToClipboard';
7
+ import { HttpNetworkEntry, SSENetworkEntry } from '../state/model';
8
+ import {
9
+ DropdownMenu,
10
+ DropdownMenuContent,
11
+ DropdownMenuItem,
12
+ DropdownMenuTrigger,
13
+ } from './DropdownMenu';
14
+ import { checkRequestBodyBinary } from '../utils/checkRequestBodyBinary';
15
+
16
+ type NetworkEntry = HttpNetworkEntry | SSENetworkEntry;
17
+
18
+ type CopyDropdownProps = {
19
+ selectedRequest: NetworkEntry;
20
+ };
21
+
22
+ type CopyOption = {
23
+ id: string;
24
+ label: string;
25
+ generate: (request: NetworkEntry) => string;
26
+ isEnabled: (request: NetworkEntry) => boolean;
27
+ };
28
+
29
+ const copyOptions: CopyOption[] = [
30
+ {
31
+ id: 'fetch',
32
+ label: 'fetch',
33
+ generate: generateFetchCall,
34
+ isEnabled: (request) => !checkRequestBodyBinary(request),
35
+ },
36
+ {
37
+ id: 'curl',
38
+ label: 'cURL',
39
+ generate: generateCurlCommand,
40
+ isEnabled: (request) =>
41
+ !checkRequestBodyBinary(request) || request.type === 'sse',
42
+ },
43
+ ];
44
+
45
+ export const CopyRequestDropdown = ({ selectedRequest }: CopyDropdownProps) => {
46
+ const { isCopied, copy } = useCopyToClipboard();
47
+
48
+ const handleCopy = useCallback(
49
+ async (option: CopyOption) => {
50
+ if (!selectedRequest) return;
51
+
52
+ try {
53
+ const content = await option.generate(selectedRequest);
54
+
55
+ await copy(content);
56
+ } catch (error) {
57
+ console.error(`Failed to copy ${option.label}:`, error);
58
+ }
59
+ },
60
+ [selectedRequest, copy]
61
+ );
62
+
63
+ const filteredCopyOptions = copyOptions.filter((option) =>
64
+ option.isEnabled(selectedRequest)
65
+ );
66
+
67
+ if (filteredCopyOptions.length === 0) {
68
+ return null;
69
+ }
70
+
71
+ return (
72
+ <DropdownMenu>
73
+ <DropdownMenuTrigger asChild>
74
+ <Button variant="ghost" size="xs" className="border border-gray-700">
75
+ {isCopied ? <Check size={16} /> : <Copy size={16} />}
76
+ Copy as ...
77
+ <ChevronDown size={12} className="ml-1" />
78
+ </Button>
79
+ </DropdownMenuTrigger>
80
+ <DropdownMenuContent align="start">
81
+ {filteredCopyOptions.map((option) => {
82
+ return (
83
+ <DropdownMenuItem
84
+ onClick={() => handleCopy(option)}
85
+ className="cursor-pointer"
86
+ key={option.id}
87
+ >
88
+ {option.label}
89
+ </DropdownMenuItem>
90
+ );
91
+ })}
92
+ </DropdownMenuContent>
93
+ </DropdownMenu>
94
+ );
95
+ };
@@ -37,10 +37,7 @@ export const KeyValueGrid = ({
37
37
  {items.map((item, index) => (
38
38
  <Fragment key={index}>
39
39
  <span
40
- className={cn(
41
- 'text-gray-400 wrap-anywhere',
42
- item.keyClassName
43
- )}
40
+ className={cn('text-gray-400 wrap-anywhere', item.keyClassName)}
44
41
  >
45
42
  {item.key}
46
43
  </span>
@@ -0,0 +1,86 @@
1
+ import { HttpRequestData } from '../state/model';
2
+ import { KeyValueGrid, KeyValueItem } from './KeyValueGrid';
3
+ import { CodeBlock } from './CodeBlock';
4
+ import { JsonTree } from './JsonTree';
5
+ import {
6
+ RequestBinaryPostData,
7
+ RequestFormDataPostData,
8
+ } from '../../shared/client';
9
+
10
+ type RequestBodyProps = {
11
+ data: HttpRequestData['data'];
12
+ };
13
+
14
+ const getFormDataBinaryEntries = (
15
+ key: string,
16
+ value: RequestBinaryPostData['value']
17
+ ): KeyValueItem[] => {
18
+ return [
19
+ {
20
+ key,
21
+ value: <span className="text-blue-400">[binary]</span>,
22
+ },
23
+ ...getBinaryEntries(value).map((item) => ({
24
+ ...item,
25
+ key: ` └─ ${item.key}`,
26
+ keyClassName: 'whitespace-pre',
27
+ })),
28
+ ];
29
+ };
30
+
31
+ const getBinaryEntries = (
32
+ value: RequestBinaryPostData['value']
33
+ ): KeyValueItem[] => {
34
+ const { size, type, name } = value;
35
+
36
+ const items: KeyValueItem[] = [];
37
+
38
+ if (name) {
39
+ items.push({ key: 'Name', value: name });
40
+ }
41
+
42
+ if (type) {
43
+ items.push({ key: 'Type', value: type });
44
+ }
45
+
46
+ items.push({ key: 'Size', value: `${size} bytes` });
47
+
48
+ return items;
49
+ };
50
+
51
+ const getFormDataEntries = (value: RequestFormDataPostData['value']) =>
52
+ Object.entries(value).flatMap(([key, { value, type }]) => {
53
+ if (type === 'binary') {
54
+ return getFormDataBinaryEntries(key, value);
55
+ }
56
+
57
+ return [{ key, value }];
58
+ });
59
+
60
+ export const RequestBody = ({ data }: RequestBodyProps) => {
61
+ const { type: dataType, value } = data;
62
+
63
+ if (dataType === 'text') {
64
+ try {
65
+ const jsonData = JSON.parse(value);
66
+
67
+ return (
68
+ <CodeBlock>
69
+ <JsonTree data={jsonData} />
70
+ </CodeBlock>
71
+ );
72
+ } catch {
73
+ return <CodeBlock>{value}</CodeBlock>;
74
+ }
75
+ }
76
+
77
+ if (dataType === 'form-data') {
78
+ return <KeyValueGrid items={getFormDataEntries(value)} />;
79
+ }
80
+
81
+ if (dataType === 'binary') {
82
+ return <KeyValueGrid items={getBinaryEntries(value)} />;
83
+ }
84
+
85
+ return null;
86
+ };
@@ -17,7 +17,7 @@ import {
17
17
  } from '../state/hooks';
18
18
  import { getStatusColor } from '../utils/getStatusColor';
19
19
  import { FilterState } from './FilterBar';
20
- import { isNumber } from '../../utils/isNumber';
20
+ import { isNumber } from '../../utils/typeChecks';
21
21
 
22
22
  type NetworkRequest = {
23
23
  id: RequestId;
@@ -1,4 +1,9 @@
1
- import { Initiator, ResourceType, HttpHeaders, RequestPostData } from '../../shared/client';
1
+ import {
2
+ Initiator,
3
+ ResourceType,
4
+ HttpHeaders,
5
+ RequestPostData,
6
+ } from '../../shared/client';
2
7
 
3
8
  export type RequestId = string;
4
9
  export type Timestamp = number;
@@ -43,9 +43,7 @@ export const CookiesTab = ({ selectedRequest }: CookiesTabProps) => {
43
43
  <div className="p-4">
44
44
  <div className="space-y-6">
45
45
  {hasRequestCookies && (
46
- <Section
47
- title={`Request Cookies (${requestCookies.length})`}
48
- >
46
+ <Section title={`Request Cookies (${requestCookies.length})`}>
49
47
  <div className="space-y-2">
50
48
  {requestCookies.map((cookie, index) => (
51
49
  <CookieCard
@@ -59,9 +57,7 @@ export const CookiesTab = ({ selectedRequest }: CookiesTabProps) => {
59
57
  )}
60
58
 
61
59
  {hasResponseCookies && (
62
- <Section
63
- title={`Response Cookies (${responseCookies.length})`}
64
- >
60
+ <Section title={`Response Cookies (${responseCookies.length})`}>
65
61
  <div className="space-y-2">
66
62
  {responseCookies.map((cookie, index) => (
67
63
  <CookieCard
@@ -4,7 +4,7 @@ import { Section } from '../components/Section';
4
4
  import { KeyValueGrid, KeyValueItem } from '../components/KeyValueGrid';
5
5
  import { HttpNetworkEntry, SSENetworkEntry } from '../state/model';
6
6
  import { getStatusColor } from '../utils/getStatusColor';
7
- import { CopyAsCurlButton } from '../components/CopyAsCurlButton';
7
+ import { CopyRequestDropdown } from '../components/CopyRequestDropdown';
8
8
  import { HttpHeaders } from '../../shared/client';
9
9
 
10
10
  export type HeadersTabProps = {
@@ -69,15 +69,10 @@ export const HeadersTab = ({ selectedRequest }: HeadersTabProps) => {
69
69
  [selectedRequest]
70
70
  );
71
71
 
72
- const isCopyAsCurlEnabled =
73
- selectedRequest.request.body?.data.type !== 'binary';
74
-
75
72
  return (
76
73
  <ScrollArea className="h-full w-full">
77
74
  <div className="p-4 space-y-4">
78
- {isCopyAsCurlEnabled && (
79
- <CopyAsCurlButton selectedRequest={selectedRequest} />
80
- )}
75
+ <CopyRequestDropdown selectedRequest={selectedRequest} />
81
76
 
82
77
  <Section title="General">
83
78
  <KeyValueGrid items={generalItems} />
@@ -1,41 +1,56 @@
1
- import * as React from 'react';
2
1
  import { ScrollArea } from '../components/ScrollArea';
3
- import { JsonTree } from '../components/JsonTree';
4
- import { HttpNetworkEntry, SSENetworkEntry } from '../state/model';
2
+ import {
3
+ HttpNetworkEntry,
4
+ HttpRequestData,
5
+ SSENetworkEntry,
6
+ } from '../state/model';
5
7
  import { KeyValueGrid } from '../components/KeyValueGrid';
6
8
  import { Section } from '../components/Section';
7
- import { CodeBlock } from '../components/CodeBlock';
8
- import { ReactNode, useMemo } from 'react';
9
+ import { useMemo } from 'react';
10
+ import { RequestBody } from '../components/RequestBody';
9
11
 
10
12
  export type RequestTabProps = {
11
13
  selectedRequest: HttpNetworkEntry | SSENetworkEntry;
12
14
  };
13
15
 
16
+ const getRequestBodySectionTitle = (body: HttpRequestData) => {
17
+ const baseTitle = 'Request Body';
18
+
19
+ switch (body.data.type) {
20
+ case 'form-data':
21
+ return `${baseTitle} (FormData)`;
22
+
23
+ case 'binary':
24
+ return `${baseTitle} (Binary)`;
25
+
26
+ default:
27
+ return baseTitle;
28
+ }
29
+ };
30
+
14
31
  export const RequestTab = ({ selectedRequest }: RequestTabProps) => {
15
32
  const queryParams = useMemo(() => {
16
33
  const { searchParams } = new URL(selectedRequest.request.url);
17
34
 
18
- return Array.from(searchParams.entries());
35
+ return Array.from(searchParams.entries()).map(([key, value]) => ({
36
+ key,
37
+ value,
38
+ }));
19
39
  }, [selectedRequest.request.url]);
20
40
 
21
41
  const requestBody = selectedRequest.request.body;
22
42
  const hasQueryParams = queryParams.length > 0;
23
43
 
24
44
  const renderQueryParams = () => {
25
- if (hasQueryParams) {
26
- return (
27
- <Section title={`Query Parameters (${queryParams.length})`}>
28
- <KeyValueGrid
29
- items={queryParams.map(([key, value]) => ({
30
- key,
31
- value,
32
- }))}
33
- />
34
- </Section>
35
- );
45
+ if (!hasQueryParams) {
46
+ return null;
36
47
  }
37
48
 
38
- return null;
49
+ return (
50
+ <Section title={`Query Parameters (${queryParams.length})`}>
51
+ <KeyValueGrid items={queryParams} />
52
+ </Section>
53
+ );
39
54
  };
40
55
 
41
56
  const renderRequestBody = () => {
@@ -43,40 +58,9 @@ export const RequestTab = ({ selectedRequest }: RequestTabProps) => {
43
58
  return null;
44
59
  }
45
60
 
46
- const { type, data } = requestBody;
47
- const { type: dataType, value } = data;
48
-
49
- let bodyContent: ReactNode = null;
50
-
51
- if (dataType === 'text') {
52
- try {
53
- const jsonData = JSON.parse(value);
54
-
55
- bodyContent = <JsonTree data={jsonData} />;
56
- } catch {
57
- bodyContent = value;
58
- }
59
- }
60
-
61
- // Show JSON tree as a temporary solution for form-data and binary types
62
- if (dataType === 'form-data' || dataType === 'binary') {
63
- bodyContent = <JsonTree data={value} />
64
- }
65
-
66
61
  return (
67
- <Section title="Request Body">
68
- <div className="space-y-4">
69
- <KeyValueGrid
70
- items={[
71
- {
72
- key: 'Content-Type',
73
- value: type,
74
- valueClassName: 'text-blue-400',
75
- },
76
- ]}
77
- />
78
- {bodyContent && <CodeBlock>{bodyContent}</CodeBlock>}
79
- </div>
62
+ <Section title={getRequestBodySectionTitle(requestBody)}>
63
+ <RequestBody data={requestBody.data} />
80
64
  </Section>
81
65
  );
82
66
  };
@@ -0,0 +1,7 @@
1
+ import { NetworkEntry } from '../state/model';
2
+
3
+ export const checkRequestBodyBinary = (request: NetworkEntry) => {
4
+ return (
5
+ request.type === 'http' && request.request.body?.data.type === 'binary'
6
+ );
7
+ };
@@ -1,4 +1,5 @@
1
- import { RequestPostData } from '../../shared/client';
1
+ import { HttpHeaders, RequestPostData } from '../../shared/client';
2
+ import { HttpNetworkEntry, SSENetworkEntry } from '../state/model';
2
3
  import { escapeShellArg } from './escapeShellArg';
3
4
 
4
5
  const BASE_TAB_INDENT = 2; // Number of spaces for indentation
@@ -28,10 +29,7 @@ function addHttpMethodToCurl(curlParts: string[], method: string): void {
28
29
  }
29
30
  }
30
31
 
31
- function addHeadersToCurl(
32
- curlParts: string[],
33
- headers: Record<string, string>
34
- ): void {
32
+ function addHeadersToCurl(curlParts: string[], headers: HttpHeaders): void {
35
33
  Object.entries(headers).forEach(([key, value]) => {
36
34
  addCurlParam(curlParts, '-H', escapeShellArg(`${key}: ${value}`));
37
35
  });
@@ -65,13 +63,12 @@ function addBodyToCurl(curlParts: string[], postData: RequestPostData): void {
65
63
  addCurlParam(curlParts, '--data-raw', escapeShellArg(stringifyData(value)));
66
64
  }
67
65
 
68
- export function generateCurlCommand(request: {
69
- method: string;
70
- url: string;
71
- headers?: Record<string, string>;
72
- postData?: RequestPostData;
73
- }): string {
74
- const { method, url, headers = {}, postData } = request;
66
+ export function generateCurlCommand(
67
+ request: HttpNetworkEntry | SSENetworkEntry
68
+ ) {
69
+ const { method, url, headers = {}, body } = request.request;
70
+
71
+ const postData = body?.data;
75
72
 
76
73
  const curlParts: string[] = [`curl ${escapeShellArg(url)}`];
77
74
 
@@ -0,0 +1,64 @@
1
+ import { generateMultipartBody } from './generateMultipartBody';
2
+ import {
3
+ HttpNetworkEntry,
4
+ HttpRequestData,
5
+ SSENetworkEntry,
6
+ } from '../state/model';
7
+ import { getHttpHeaderValueAsString } from '../../utils/getHttpHeaderValueAsString';
8
+ import { HttpHeaders, XHRHeaders } from '../../shared/client';
9
+
10
+ const processHeaders = (requestHeaders: HttpHeaders | undefined) => {
11
+ const headers: XHRHeaders = {};
12
+
13
+ if (!requestHeaders) {
14
+ return headers;
15
+ }
16
+
17
+ Object.entries(requestHeaders).forEach(([name, value]) => {
18
+ // Filter out HTTP/2 pseudo-headers
19
+ if (!name.startsWith(':')) {
20
+ headers[name] = getHttpHeaderValueAsString(value);
21
+ }
22
+ });
23
+
24
+ return headers;
25
+ };
26
+
27
+ const processRequestBody = (body: HttpRequestData, headers: XHRHeaders) => {
28
+ const { type, value } = body.data;
29
+
30
+ switch (type) {
31
+ case 'text':
32
+ return value;
33
+
34
+ case 'form-data': {
35
+ const { body, contentType } = generateMultipartBody(value);
36
+
37
+ headers['Content-Type'] = contentType;
38
+
39
+ return body;
40
+ }
41
+
42
+ default:
43
+ return undefined;
44
+ }
45
+ };
46
+
47
+ export const generateFetchCall = (
48
+ request: HttpNetworkEntry | SSENetworkEntry
49
+ ) => {
50
+ const { url, headers: requestHeaders, method, body } = request.request;
51
+
52
+ const headers = processHeaders(requestHeaders);
53
+ const requestBody = body ? processRequestBody(body, headers) : undefined;
54
+
55
+ const fetchOptions: RequestInit = {
56
+ headers: Object.keys(headers).length ? headers : undefined,
57
+ body: requestBody,
58
+ method,
59
+ };
60
+
61
+ const options = JSON.stringify(fetchOptions, null, 2);
62
+
63
+ return `fetch(${JSON.stringify(url)}, ${options});`;
64
+ };
@@ -0,0 +1,19 @@
1
+ export const generateMultipartBody = (formData: Record<string, unknown>) => {
2
+ const boundary = 'FormBoundary' + Math.random().toString(36).substr(2, 16);
3
+
4
+ const parts: string[] = [];
5
+
6
+ Object.entries(formData).forEach(([key, value]) => {
7
+ parts.push(`--${boundary}`);
8
+ parts.push(`Content-Disposition: form-data; name="${key}"`);
9
+ parts.push('');
10
+ parts.push(String(value));
11
+ });
12
+
13
+ parts.push(`--${boundary}--`);
14
+
15
+ return {
16
+ body: parts.join('\r\n'),
17
+ contentType: `multipart/form-data; boundary=${boundary}`,
18
+ };
19
+ };
@@ -5,15 +5,14 @@ import { inferContentTypeFromPostData } from './inferContentTypeFromPostData';
5
5
  /**
6
6
  * Partially emulates React Native's behavior for setting HTTP headers.
7
7
  *
8
- * See:
9
- * https://github.com/facebook/react-native/blob/de5093c88771977b58f7bec3f3ffa64a9595334e/packages/react-native/Libraries/Network/RCTNetworking.mm#L345-L349
8
+ * @see https://github.com/facebook/react-native/blob/de5093c88771977b58f7bec3f3ffa64a9595334e/packages/react-native/Libraries/Network/RCTNetworking.mm#L345-L349
10
9
  */
11
10
  export function applyReactNativeRequestHeadersLogic(
12
11
  headers: HttpHeaders,
13
12
  postData?: RequestPostData
14
13
  ): HttpHeaders {
15
14
  const existingContentType = getHttpHeader(headers, 'content-type');
16
-
15
+
17
16
  if (existingContentType) {
18
17
  return headers;
19
18
  }
@@ -6,9 +6,8 @@ import { getHttpHeader } from './getHttpHeader';
6
6
  * Applies React Native specific logic to response headers.
7
7
  * React Native concatenates multiple header values into single strings,
8
8
  * this function parses them back into arrays where appropriate.
9
- *
10
- * See:
11
- * https://github.com/facebook/react-native/blob/588f0c5ce6c283f116228456da2170d2adc3cbf4/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java#L637
9
+ *
10
+ * @see https://github.com/facebook/react-native/blob/588f0c5ce6c283f116228456da2170d2adc3cbf4/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java#L637
12
11
  */
13
12
  export const applyReactNativeResponseHeadersLogic = (
14
13
  headers: XHRHeaders
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Combines multiple HTTP header values according to RFC 7230 Section 3.2.2
3
+ *
4
+ * Per RFC 7230 Section 3.2.2: "A recipient MAY combine multiple header fields
5
+ * with the same field name into one 'field-name: field-value' pair, without
6
+ * changing the semantics of the message, by appending each subsequent field
7
+ * value to the combined field value in order, separated by a comma."
8
+ *
9
+ * @see https://tools.ietf.org/html/rfc7230#section-3.2.2
10
+ */
11
+ export function getHttpHeaderValueAsString(value: string | string[]): string {
12
+ return Array.isArray(value) ? value.join(', ') : value;
13
+ }
@@ -0,0 +1,27 @@
1
+ export const isBlob = (value: unknown): value is Blob => value instanceof Blob;
2
+
3
+ export const isArrayBuffer = (
4
+ value: unknown
5
+ ): value is ArrayBuffer | ArrayBufferView =>
6
+ value instanceof ArrayBuffer || ArrayBuffer.isView(value);
7
+
8
+ export const isFormData = (value: unknown): value is FormData =>
9
+ value instanceof FormData;
10
+
11
+ export const isNullOrUndefined = (value: unknown): value is null | undefined =>
12
+ value === null || value === undefined;
13
+
14
+ export const isString = (value: unknown): value is string =>
15
+ typeof value === 'string';
16
+
17
+ export const isNumber = (value: unknown): value is number =>
18
+ typeof value === 'number' && !isNaN(value);
19
+
20
+ export const isBoolean = (value: unknown): value is boolean =>
21
+ typeof value === 'boolean';
22
+
23
+ export const isObject = (value: unknown): value is object =>
24
+ typeof value === 'object' && value !== null;
25
+
26
+ export const isArray = (value: unknown): value is unknown[] =>
27
+ Array.isArray(value);
@@ -1,5 +0,0 @@
1
- import { HttpNetworkEntry, SSENetworkEntry } from '../state/model';
2
- export type CopyAsCurlButtonProps = {
3
- selectedRequest?: HttpNetworkEntry | SSENetworkEntry;
4
- };
5
- export declare const CopyAsCurlButton: ({ selectedRequest, }: CopyAsCurlButtonProps) => import("react/jsx-runtime").JSX.Element;
@@ -1 +0,0 @@
1
- export declare const isNumber: (value: unknown) => value is number;
@@ -1,45 +0,0 @@
1
- import { useCopyToClipboard } from '../hooks/useCopyToClipboard';
2
- import { Copy, Check } from 'lucide-react';
3
- import { Button } from './Button';
4
- import { generateCurlCommand } from '../utils/generateCurlCommand';
5
- import { HttpNetworkEntry, SSENetworkEntry } from '../state/model';
6
-
7
- export type CopyAsCurlButtonProps = {
8
- selectedRequest?: HttpNetworkEntry | SSENetworkEntry;
9
- };
10
-
11
- export const CopyAsCurlButton = ({
12
- selectedRequest,
13
- }: CopyAsCurlButtonProps) => {
14
- const { isCopied, copy } = useCopyToClipboard();
15
-
16
- const handleCopyCurl = () => {
17
- if (!selectedRequest) return;
18
-
19
- const { method, url, headers, body } = selectedRequest.request;
20
-
21
- const curlCommand = generateCurlCommand({
22
- method,
23
- url,
24
- headers,
25
- postData: body?.data,
26
- });
27
-
28
- copy(curlCommand);
29
- };
30
-
31
- const Icon = isCopied ? Check : Copy;
32
-
33
- return (
34
- <Button
35
- variant="ghost"
36
- size="xs"
37
- onClick={handleCopyCurl}
38
- disabled={!selectedRequest}
39
- className="border border-gray-700"
40
- >
41
- <Icon className="w-2 h-2" />
42
- Copy as cURL
43
- </Button>
44
- );
45
- };
@@ -1,3 +0,0 @@
1
- export const isNumber = (value: unknown): value is number => {
2
- return typeof value === 'number';
3
- };