Skip to content

Commit a710a53

Browse files
authored
feat: copy content on insecure connections (HTTP) (#251)
Closes #250
1 parent 9914980 commit a710a53

File tree

4 files changed

+61
-2
lines changed

4 files changed

+61
-2
lines changed

src/i18n/en/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,9 @@ const en = {
153153
version: 'Version',
154154
vocabOnly: 'Vocab only',
155155
writePromptToStart: 'Write a prompt to start a new session',
156-
you: 'You'
156+
you: 'You',
157+
copiedNotPrivate: 'Content copied, but your connection is not private',
158+
notCopiedNotPrivate: "Couldn't copy content. Connection is not private"
157159
} satisfies BaseTranslation;
158160

159161
export default en;

src/i18n/i18n-types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,14 @@ type RootTranslation = {
620620
* Y​o​u
621621
*/
622622
you: string
623+
/**
624+
* C​o​n​t​e​n​t​ ​c​o​p​i​e​d​,​ ​b​u​t​ ​y​o​u​r​ ​c​o​n​n​e​c​t​i​o​n​ ​i​s​ ​n​o​t​ ​p​r​i​v​a​t​e
625+
*/
626+
copiedNotPrivate: string
627+
/**
628+
* C​o​u​l​d​n​'​t​ ​c​o​p​y​ ​c​o​n​t​e​n​t​.​ ​C​o​n​n​e​c​t​i​o​n​ ​i​s​ ​n​o​t​ ​p​r​i​v​a​t​e
629+
*/
630+
notCopiedNotPrivate: string
623631
}
624632

625633
export type TranslationFunctions = {
@@ -1220,6 +1228,14 @@ The completion in progress will stop
12201228
* You
12211229
*/
12221230
you: () => LocalizedString
1231+
/**
1232+
* Content copied, but your connection is not private
1233+
*/
1234+
copiedNotPrivate: () => LocalizedString
1235+
/**
1236+
* Couldn't copy content. Connection is not private
1237+
*/
1238+
notCopiedNotPrivate: () => LocalizedString
12231239
}
12241240

12251241
export type Formatters = {}

src/lib/components/ButtonCopy.svelte

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import { Files } from 'lucide-svelte';
3+
import { toast } from 'svelte-sonner';
34
45
import LL from '$i18n/i18n-svelte';
56
@@ -8,7 +9,25 @@
89
export let content: string;
910
1011
function copyContent() {
11-
navigator.clipboard.writeText(content);
12+
if (navigator.clipboard && window.isSecureContext) {
13+
navigator.clipboard.writeText(content);
14+
} else {
15+
// HACK
16+
// This is a workaround to copy text content on HTTP connections.
17+
// https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem
18+
const textArea = document.createElement('textarea');
19+
textArea.value = content;
20+
document.body.appendChild(textArea);
21+
textArea.select();
22+
try {
23+
document.execCommand('copy');
24+
toast.warning($LL.copiedNotPrivate());
25+
} catch (e) {
26+
console.error(e);
27+
toast.error($LL.notCopiedNotPrivate());
28+
}
29+
document.body.removeChild(textArea);
30+
}
1231
}
1332
</script>
1433

tests/session-interaction.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,28 @@ test.describe('Session interaction', () => {
131131
);
132132
});
133133

134+
test('can copy text on an insecure connection', async ({ page }) => {
135+
// Mock insecure context before navigating
136+
await page.addInitScript(() => {
137+
Object.defineProperty(window, 'isSecureContext', { value: false });
138+
});
139+
140+
await page.goto('/');
141+
await mockCompletionResponse(page, MOCK_SESSION_1_RESPONSE_1);
142+
await page.getByText('Sessions', { exact: true }).click();
143+
await page.getByTestId('new-session').click();
144+
await chooseModel(page, MOCK_API_TAGS_RESPONSE.models[0].name);
145+
await promptTextarea.fill('Who would win in a fight between Emma Watson and Jessica Alba?');
146+
await page.getByText('Run').click();
147+
await expect(page.locator('.session__history').getByTitle('Copy')).toHaveCount(2);
148+
149+
const toastWarning = page.getByText('Content copied, but your connection is not private');
150+
await expect(toastWarning).not.toBeVisible();
151+
152+
await page.locator('.session__history').getByTitle('Copy').first().click();
153+
await expect(toastWarning).toBeVisible();
154+
});
155+
134156
test('can copy the whole session content to clipboard', async ({ page }) => {
135157
await page.goto('/');
136158
await page.evaluate(() => navigator.clipboard.writeText(''));

0 commit comments

Comments
 (0)