Fix clipboard handling in safari (#6917)

* fix clipboard handling in safari

closes #6911, #6909

Co-authored-by: Can Yesilyurt <36952967+canyesilyurt@users.noreply.github.com>

* [autofix.ci] apply automated fixes

* update dependencies

---------

Co-authored-by: Can Yesilyurt <36952967+canyesilyurt@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Maximilian Hils 2024-06-12 14:59:40 +02:00 committed by GitHub
parent 40ae9ff05b
commit 832e735b0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 2229 additions and 2815 deletions

View File

@ -21,6 +21,8 @@
([#6876](https://github.com/mitmproxy/mitmproxy/pull/6876), @errorxyz) ([#6876](https://github.com/mitmproxy/mitmproxy/pull/6876), @errorxyz)
* Add an API to parse HTTPS records from DNS RDATA. * Add an API to parse HTTPS records from DNS RDATA.
([#6884](https://github.com/mitmproxy/mitmproxy/pull/6884), @errorxyz) ([#6884](https://github.com/mitmproxy/mitmproxy/pull/6884), @errorxyz)
* Fix flow export in mitmweb for Safari
([#6917](https://github.com/mitmproxy/mitmproxy/pull/6917), @mhils, @canyesilyurt)
* Releases now come with a Sigstore attestations file to demonstrate build provenance. * Releases now come with a Sigstore attestations file to demonstrate build provenance.
([f05c050](https://github.com/mitmproxy/mitmproxy/commit/f05c050f615b9ab9963707944c893bc94e738525), @mhils) ([f05c050](https://github.com/mitmproxy/mitmproxy/commit/f05c050f615b9ab9963707944c893bc94e738525), @mhils)

4911
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@
"prettier": "prettier --write ." "prettier": "prettier --write ."
}, },
"dependencies": { "dependencies": {
"@popperjs/core": "^2.9.3", "@popperjs/core": "^2.11.8",
"bootstrap": "^3.4.1", "bootstrap": "^3.4.1",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"codemirror": "^5.62.3", "codemirror": "^5.62.3",
@ -29,7 +29,7 @@
"@testing-library/jest-dom": "^5.14.1", "@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0", "@testing-library/react": "^12.0.0",
"@testing-library/user-event": "^13.2.1", "@testing-library/user-event": "^13.2.1",
"@types/jest": "^27.0.1", "@types/jest": "^29.5.12",
"@types/redux-mock-store": "^1.0.3", "@types/redux-mock-store": "^1.0.3",
"esbuild": "^0.12.21", "esbuild": "^0.12.21",
"esbuild-jest": "^0.5.0", "esbuild-jest": "^0.5.0",
@ -43,7 +43,8 @@
"gulp-plumber": "^1.2.1", "gulp-plumber": "^1.2.1",
"gulp-replace": "^1.1.3", "gulp-replace": "^1.1.3",
"gulp-sourcemaps": "^3.0.0", "gulp-sourcemaps": "^3.0.0",
"jest": "^27.0.6", "jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-fetch-mock": "^3.0.3", "jest-fetch-mock": "^3.0.3",
"prettier": "2.8.4", "prettier": "2.8.4",
"react-test-renderer": "^17.0.2", "react-test-renderer": "^17.0.2",

View File

@ -1,8 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EventList Component should render correctly 1`] = ` exports[`EventList Component should render correctly 1`] = `
Object { {
"vScroll": Object { "vScroll": {
"end": 1, "end": 1,
"paddingBottom": 18, "paddingBottom": 18,
"paddingTop": 0, "paddingTop": 0,
@ -12,14 +12,14 @@ Object {
`; `;
exports[`EventList Component should render correctly 2`] = ` exports[`EventList Component should render correctly 2`] = `
Object { {
"events": Array [ "events": [
Object { {
"id": 1, "id": 1,
"level": "info", "level": "info",
"message": "foo", "message": "foo",
}, },
Object { {
"id": 2, "id": 2,
"level": "error", "level": "error",
"message": "bar", "message": "bar",

View File

@ -10,7 +10,7 @@ exports[`FilterInput Component should render correctly 1`] = `
<i <i
className="fa fa-fw fa-foo" className="fa fa-fw fa-foo"
style={ style={
Object { {
"color": "red", "color": "red",
} }
} }

View File

@ -4,7 +4,7 @@ exports[`EventLog Component should connect to state and render correctly 1`] = `
<div <div
className="eventlog" className="eventlog"
style={ style={
Object { {
"height": 200, "height": 200,
} }
} }

View File

@ -8,7 +8,7 @@ exports[`FlowTable Component should render correctly 1`] = `
<table> <table>
<thead <thead
style={ style={
Object { {
"transform": "translateY(0px)", "transform": "translateY(0px)",
} }
} }
@ -67,7 +67,7 @@ exports[`FlowTable Component should render correctly 1`] = `
<tbody> <tbody>
<tr <tr
style={ style={
Object { {
"height": 0, "height": 0,
} }
} }
@ -107,7 +107,7 @@ exports[`FlowTable Component should render correctly 1`] = `
<td <td
className="col-status" className="col-status"
style={ style={
Object { {
"color": "darkgreen", "color": "darkgreen",
} }
} }
@ -131,7 +131,7 @@ exports[`FlowTable Component should render correctly 1`] = `
</tr> </tr>
<tr <tr
style={ style={
Object { {
"height": 0, "height": 0,
} }
} }

View File

@ -1,27 +0,0 @@
// Adapted from https://stackoverflow.com/a/65996386/934719
/**
* `navigator.clipboard.writeText()`, but with an additional fallback for non-secure contexts.
*/
export function copyToClipboard(text: string): Promise<void> {
// navigator clipboard requires a security context such as https
if (navigator.clipboard && window.isSecureContext) {
return navigator.clipboard.writeText(text);
} else {
let t = document.createElement("textarea");
t.value = text;
t.style.position = "absolute";
t.style.opacity = "0";
document.body.appendChild(t);
try {
t.focus();
t.select();
document.execCommand("copy");
return Promise.resolve();
} catch (err) {
alert(text);
return Promise.reject(err);
} finally {
t.remove();
}
}
}

View File

@ -1,14 +1,22 @@
import { runCommand } from "../utils"; import { copyToClipboard, runCommand } from "../utils";
import { Flow } from "../flow"; import { Flow } from "../flow";
import { copyToClipboard } from "../contrib/clipboard";
export const copy = async (flow: Flow, format: string): Promise<void> => { export const copy = async (flow: Flow, format: string): Promise<void> => {
let ret = await runCommand("export", format, `@${flow.id}`); // Safari: We need to call copyToClipboard _right away_ with a promise,
if (ret.value) { // otherwise we're loosing user intent and can't copy anymore.
await copyToClipboard(ret.value); let formatted = (async () => {
} else if (ret.error) { let ret = await runCommand("export", format, `@${flow.id}`);
alert(ret.error); if (ret.value) {
} else { return ret.value;
console.error(ret); } else if (ret.error) {
throw ret.error;
} else {
throw ret;
}
})();
try {
await copyToClipboard(formatted);
} catch (err) {
alert(err);
} }
}; };

View File

@ -135,3 +135,50 @@ export function getDiff(obj1, obj2) {
} }
return result; return result;
} }
/**
* `navigator.clipboard.writeText()`, but with an additional fallback for non-secure contexts.
*
* Never throws unless textPromise is rejected.
*/
export async function copyToClipboard(
textPromise: Promise<string>
): Promise<void> {
// Try the new clipboard APIs first. If that fails, use textarea fallback.
try {
await navigator.clipboard.write([
new ClipboardItem({
"text/plain": textPromise,
}),
]);
return;
} catch (err) {
console.warn(err);
}
let text = await textPromise;
try {
await navigator.clipboard.writeText(text);
return;
} catch (err) {
console.warn(err);
}
let t = document.createElement("textarea");
t.value = text;
t.style.position = "absolute";
t.style.opacity = "0";
document.body.appendChild(t);
try {
t.focus();
t.select();
if (!document.execCommand("copy")) {
throw "failed to copy";
}
} catch {
alert(text);
} finally {
t.remove();
}
}