Skip to content

Commit ae10d6c

Browse files
committed
refactor: interactive message
1 parent 71243c0 commit ae10d6c

File tree

9 files changed

+103
-33
lines changed

9 files changed

+103
-33
lines changed

packages/insomnia/src/common/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,3 +550,5 @@ export const RESPONSE_CODE_REASONS: Record<number, string> = {
550550

551551
// (ms) curently server timeout is 30s
552552
export const INSOMNIA_FETCH_TIME_OUT = 30_000;
553+
554+
export const PREF_SECURITY = 'Insomnia’s Preferences → Security';
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { PREF_SECURITY } from '~/common/constants';
2+
import { showSettingsModal } from '~/ui/components/modals/settings-modal';
3+
4+
const interactives = [{ text: PREF_SECURITY, handler: () => showSettingsModal({ tab: 'general' }) }];
5+
6+
export function buildInteractiveMessage(message: string) {
7+
const parts = [];
8+
let prev = '';
9+
for (let i = 0; i < message.length; i++) {
10+
let matched = false;
11+
for (const { text, handler } of interactives) {
12+
matched = message.startsWith(text, i);
13+
if (matched) {
14+
if (prev) {
15+
parts.push({ text: prev });
16+
prev = '';
17+
}
18+
parts.push({ text, handler });
19+
i += text.length - 1;
20+
break;
21+
}
22+
}
23+
if (!matched) {
24+
prev += message[i];
25+
}
26+
}
27+
if (prev) {
28+
parts.push({ text: prev });
29+
}
30+
return parts;
31+
}

packages/insomnia/src/common/validators.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { PREF_SECURITY } from '~/common/constants';
2+
13
import type { CaCertificate } from '../models/ca-certificate';
24
import type { ClientCertificate } from '../models/client-certificate';
35
import type { Settings } from '../models/settings';
@@ -14,7 +16,7 @@ export function isFsAccessingAllowed(
1416
if (fromCli) {
1517
throw `Insomnia cannot access the file ‘${fileName}’. You can specify paths with one or more "--dataFolders <directory>" or "-f <directory>" to allow accessing.`;
1618
} else {
17-
throw `Insomnia cannot access the file ‘${fileName}’.`;
19+
throw `Insomnia cannot access the file ‘${fileName}’. You must specify which directories Insomnia can access in ${PREF_SECURITY}.`;
1820
}
1921
};
2022

packages/insomnia/src/templating/base-extension-worker.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { PREF_SECURITY } from '~/common/constants';
2+
13
import packageJson from '../../package.json';
24
import type { CloudProviderCredential } from '../models/cloud-credential';
35
import type { Request } from '../models/request';
@@ -188,7 +190,7 @@ export default class BaseExtension {
188190
?.getSettings()
189191
.dataFolders.some((folder: string) => folder !== '' && path.startsWith(folder));
190192
if (!allowed) {
191-
throw `Insomnia cannot access the file ‘${path}’.`;
193+
throw `Insomnia cannot access the file ‘${path}’. You must specify which directories Insomnia can access in ${PREF_SECURITY}.`;
192194
}
193195
return fetchFromTemplateWorkerDatabase('readFile', { path, encoding });
194196
},

packages/insomnia/src/templating/base-extension.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import os from 'node:os';
55

66
import iconv from 'iconv-lite';
77

8+
import { PREF_SECURITY } from '~/common/constants';
9+
810
import { database as db } from '../common/database';
911
import * as models from '../models/index';
1012
import type { Request } from '../models/request';
@@ -124,7 +126,7 @@ export default class BaseExtension {
124126
?.getSettings()
125127
.dataFolders.some((folder: string) => folder !== '' && path.startsWith(folder));
126128
if (!allowed) {
127-
throw `Insomnia cannot access the file ‘${path}’.`;
129+
throw `Insomnia cannot access the file ‘${path}’. You must specify which directories Insomnia can access in ${PREF_SECURITY}.`;
128130
}
129131

130132
const content = await fs.promises.readFile(path);

packages/insomnia/src/ui/components/modals/nunjucks-modal.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export const NunjucksModal = forwardRef<NunjucksModalHandle, ModalProps & Props>
7575
defaultValue={template}
7676
workspace={workspace}
7777
editorId={state.editorId}
78+
close={() => modalRef.current?.hide()}
7879
/>
7980
);
8081
} else {

packages/insomnia/src/ui/components/rendered-query-string.tsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import classNames from 'classnames';
22
import React, { type FC, useCallback, useEffect, useState } from 'react';
33
import { Link } from 'react-aria-components';
44

5-
import { showSettingsModal } from '~/ui/components/modals/settings-modal';
5+
import { buildInteractiveMessage } from '~/common/interactive-messages';
66

77
import { database as db } from '../../common/database';
88
import * as models from '../../models';
@@ -140,27 +140,28 @@ export const RenderedQueryString: FC<Props> = ({ request }) => {
140140
}
141141
}, [tooLong]);
142142

143-
const showPreferneces = useCallback(() => {
144-
showSettingsModal({ tab: 'general' });
145-
}, []);
146-
147143
const className = previewString === defaultPreview ? 'super-duper-faint' : 'selectable force-wrap';
148144
// naive way to detect the access file error
149-
const settingTip = previewString.includes('Insomnia cannot access');
145+
const messages = previewString.includes('Insomnia cannot access')
146+
? buildInteractiveMessage(previewString)
147+
: [{ text: previewString }];
150148

151149
return (
152150
<div className="relative flex h-full w-full justify-between gap-[var(--padding-sm)] overflow-auto">
153151
<span className={classNames('my-auto', className)}>
154-
{previewString}
155-
{settingTip && (
156-
<span>
157-
{' '}
158-
You must specify which directories Insomnia can access in{' '}
159-
<Link className="cursor-pointer text-[--color-surprise]" onPress={showPreferneces}>
160-
Insomnia’s Preferences → Security
152+
{messages.map(({ text, handler }, index) =>
153+
handler ? (
154+
<Link
155+
className="cursor-pointer text-[--color-surprise]"
156+
// eslint-disable-next-line react/no-array-index-key
157+
key={index}
158+
onPress={handler}
159+
>
160+
{text}
161161
</Link>
162-
.
163-
</span>
162+
) : (
163+
text
164+
),
164165
)}
165166
</span>
166167

packages/insomnia/src/ui/components/request-url-bar.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Button, Link } from 'react-aria-components';
33
import { useParams, useSearchParams } from 'react-router';
44
import * as reactUse from 'react-use';
55

6+
import { buildInteractiveMessage } from '~/common/interactive-messages';
67
import { useRootLoaderData } from '~/root';
78
import {
89
type ConnectActionParams,
@@ -13,7 +14,6 @@ import {
1314
useDebugRequestSendActionFetcher,
1415
} from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.send';
1516
import { OneLineEditor, type OneLineEditorHandle } from '~/ui/components/.client/codemirror/one-line-editor';
16-
import { showSettingsModal } from '~/ui/components/modals/settings-modal';
1717

1818
import { database as db } from '../../common/database';
1919
import * as models from '../../models';
@@ -73,32 +73,34 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(
7373
} else {
7474
// only for request render error
7575
const errorMessage = searchParams.get('error') || '';
76-
const settingTip = errorMessage.includes('Insomnia cannot access');
76+
const messages = errorMessage.includes('Insomnia cannot access')
77+
? buildInteractiveMessage(errorMessage)
78+
: [{ text: errorMessage }];
7779
const close = showModal(AlertModal, {
7880
title: 'Unexpected Request Failure',
7981
message: (
8082
<div>
8183
<p>The request failed due to an unhandled error:</p>
8284
<code className="wide selectable">
83-
<p className="w-full overflow-y-auto text-wrap">
84-
{searchParams.get('error')}
85-
{settingTip && (
86-
<span>
87-
{' '}
88-
You must specify which directories Insomnia can access in{' '}
85+
<div className="w-full overflow-y-auto text-wrap">
86+
{messages.map(({ text, handler }, index) =>
87+
handler ? (
8988
<Link
9089
className="cursor-pointer text-[--color-surprise]"
90+
// eslint-disable-next-line react/no-array-index-key
91+
key={index}
9192
onPress={() => {
9293
close();
93-
showSettingsModal({ tab: 'general' });
94+
handler();
9495
}}
9596
>
96-
Insomnia’s Preferences → Security
97+
{text}
9798
</Link>
98-
.
99-
</span>
99+
) : (
100+
text
101+
),
100102
)}
101-
</p>
103+
</div>
102104
</code>
103105
</div>
104106
),

packages/insomnia/src/ui/components/templating/tag-editor.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import classnames from 'classnames';
22
import clone from 'clone';
33
import { localTemplateTags } from 'insomnia/src/templating/local-template-tags';
44
import React, { type FC, useCallback, useEffect, useState } from 'react';
5-
import { Button } from 'react-aria-components';
5+
import { Button, Link } from 'react-aria-components';
66
import * as reactUse from 'react-use';
77

8+
import { buildInteractiveMessage } from '~/common/interactive-messages';
9+
810
import { database as db } from '../../../common/database';
911
import { docsAfterResponseScript } from '../../../common/documentation';
1012
import { delay, fnOrString } from '../../../common/misc';
@@ -33,6 +35,7 @@ interface Props {
3335
onChange: (...args: any[]) => any;
3436
workspace: Workspace;
3537
editorId?: string;
38+
close: () => void;
3639
}
3740

3841
interface State {
@@ -251,7 +254,31 @@ export const TagEditor: FC<Props> = props => {
251254
}
252255
let previewElement;
253256
if (error) {
254-
previewElement = <textarea className="danger" value={error || 'Error'} readOnly rows={5} />;
257+
if (error.startsWith('Insomnia cannot access')) {
258+
previewElement = (
259+
<div className="danger min-h-[115px] rounded-md border border-solid border-[var(--hl-md)] bg-[var(--hl-xxs)] p-[var(--padding-sm)]">
260+
{buildInteractiveMessage(error).map(({ text, handler }, index) =>
261+
handler ? (
262+
<Link
263+
className="cursor-pointer text-[--color-surprise]"
264+
// eslint-disable-next-line react/no-array-index-key
265+
key={index}
266+
onPress={() => {
267+
props.close();
268+
handler();
269+
}}
270+
>
271+
{text}
272+
</Link>
273+
) : (
274+
text
275+
),
276+
)}
277+
</div>
278+
);
279+
} else {
280+
previewElement = <textarea className="danger" value={error || 'Error'} readOnly rows={5} />;
281+
}
255282
} else if (rendering) {
256283
previewElement = <textarea value="rendering..." readOnly rows={5} />;
257284
} else {

0 commit comments

Comments
 (0)