url handler downloads
Summary: Adding support for downloading archived Flipper data using a URL handler. A URL looks like `flipper://import/?url=` and will download the file specified in the url param. While downloading the file, a spinner is shown in the app's titlebar. Reviewed By: jknoxville Differential Revision: D14262763 fbshipit-source-id: 6538fc78c07a48cef7b71b3f7bdbcb712d054593
This commit is contained in:
committed by
Facebook Github Bot
parent
3e336d2349
commit
79124891a9
33
flow-typed/npm/query-string_v6.x.x.js
vendored
Normal file
33
flow-typed/npm/query-string_v6.x.x.js
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// flow-typed signature: 9096718d5c8abc7d9ae9aadbe0ce565a
|
||||
// flow-typed version: 6d8676cf5a/query-string_v6.x.x/flow_>=v0.32.x
|
||||
|
||||
declare module 'query-string' {
|
||||
declare type ArrayFormat = 'none' | 'bracket' | 'index'
|
||||
declare type ParseOptions = {|
|
||||
arrayFormat?: ArrayFormat,
|
||||
|}
|
||||
|
||||
declare type StringifyOptions = {|
|
||||
arrayFormat?: ArrayFormat,
|
||||
encode?: boolean,
|
||||
strict?: boolean,
|
||||
sort?: false | <A, B>(A, B) => number,
|
||||
|}
|
||||
|
||||
declare type ObjectParameter = string | number | boolean | null | void;
|
||||
|
||||
declare type ObjectParameters = {
|
||||
[string]: ObjectParameter | $ReadOnlyArray<ObjectParameter>
|
||||
}
|
||||
|
||||
declare type QueryParameters = {
|
||||
[string]: string | Array<string> | null
|
||||
}
|
||||
|
||||
declare module.exports: {
|
||||
extract(str: string): string,
|
||||
parse(str: string, opts?: ParseOptions): QueryParameters,
|
||||
parseUrl(str: string, opts?: ParseOptions): { url: string, query: QueryParameters },
|
||||
stringify(obj: ObjectParameters, opts?: StringifyOptions): string,
|
||||
}
|
||||
}
|
||||
@@ -92,6 +92,7 @@
|
||||
"pkg": "^4.3.7",
|
||||
"promise-retry": "^1.1.1",
|
||||
"prop-types": "^15.6.0",
|
||||
"query-string": "^6.2.0",
|
||||
"react": "16",
|
||||
"react-color": "^2.11.7",
|
||||
"react-debounce-render": "^4.0.3",
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
Spacer,
|
||||
styled,
|
||||
Text,
|
||||
LoadingIndicator,
|
||||
} from 'flipper';
|
||||
import {connect} from 'react-redux';
|
||||
import {
|
||||
@@ -61,6 +62,7 @@ type Props = {|
|
||||
leftSidebarVisible: boolean,
|
||||
rightSidebarVisible: boolean,
|
||||
rightSidebarAvailable: boolean,
|
||||
downloadingImportData: boolean,
|
||||
toggleLeftSidebarVisible: (visible?: boolean) => void,
|
||||
toggleRightSidebarVisible: (visible?: boolean) => void,
|
||||
setActiveSheet: (sheet: ActiveSheet) => void,
|
||||
@@ -72,17 +74,29 @@ const VersionText = styled(Text)({
|
||||
marginTop: 2,
|
||||
});
|
||||
|
||||
const Importing = styled(FlexRow)({
|
||||
color: colors.light50,
|
||||
alignItems: 'center',
|
||||
marginLeft: 10,
|
||||
});
|
||||
|
||||
class TitleBar extends Component<Props> {
|
||||
render() {
|
||||
return (
|
||||
<AppTitleBar focused={this.props.windowIsFocused} className="toolbar">
|
||||
<DevicesButton />
|
||||
<ScreenCaptureButtons />
|
||||
{this.props.downloadingImportData && (
|
||||
<Importing>
|
||||
<LoadingIndicator size={16} /> Importing data...
|
||||
</Importing>
|
||||
)}
|
||||
<Spacer />
|
||||
<VersionText>
|
||||
{this.props.version}
|
||||
{isProduction() ? '' : '-dev'}
|
||||
</VersionText>
|
||||
|
||||
{isAutoUpdaterEnabled() ? (
|
||||
<AutoUpdateVersion version={this.props.version} />
|
||||
) : null}
|
||||
@@ -125,12 +139,14 @@ export default connect<Props, OwnProps, _, _, _, _>(
|
||||
leftSidebarVisible,
|
||||
rightSidebarVisible,
|
||||
rightSidebarAvailable,
|
||||
downloadingImportData,
|
||||
},
|
||||
}) => ({
|
||||
windowIsFocused,
|
||||
leftSidebarVisible,
|
||||
rightSidebarVisible,
|
||||
rightSidebarAvailable,
|
||||
downloadingImportData,
|
||||
}),
|
||||
{
|
||||
setActiveSheet,
|
||||
|
||||
@@ -21,6 +21,7 @@ test('TitleBar is rendered', () => {
|
||||
leftSidebarVisible={false}
|
||||
rightSidebarVisible={false}
|
||||
rightSidebarAvailable={false}
|
||||
downloadingImportData={false}
|
||||
toggleLeftSidebarVisible={() => {}}
|
||||
toggleRightSidebarVisible={() => {}}
|
||||
setActiveSheet={_sheet => {}}
|
||||
|
||||
@@ -8,9 +8,12 @@
|
||||
import {remote, ipcRenderer} from 'electron';
|
||||
import type {Store} from '../reducers/index.js';
|
||||
import type {Logger} from '../fb-interfaces/Logger.js';
|
||||
import {toggleAction} from '../reducers/application';
|
||||
import {parseFlipperPorts} from '../utils/environmentVariables';
|
||||
import {importFileToStore} from '../utils/exportData';
|
||||
import {selectPlugin, userPreferredPlugin} from '../reducers/connections';
|
||||
import {importDataToStore, importFileToStore} from '../utils/exportData';
|
||||
import {selectPlugin} from '../reducers/connections';
|
||||
import qs from 'query-string';
|
||||
|
||||
export const uriComponents = (url: string) => {
|
||||
if (!url) {
|
||||
return [];
|
||||
@@ -42,11 +45,29 @@ export default (store: Store, logger: Logger) => {
|
||||
});
|
||||
});
|
||||
|
||||
ipcRenderer.on('flipper-deeplink', (event, url) => {
|
||||
// flipper://<client>/<pluginId>/<payload>
|
||||
ipcRenderer.on('flipper-protocol-handler', (event, url) => {
|
||||
if (url.startsWith('flipper://import')) {
|
||||
const {search} = new URL(url);
|
||||
const download = qs.parse(search)?.url;
|
||||
store.dispatch(toggleAction('downloadingImportData', true));
|
||||
return (
|
||||
download &&
|
||||
fetch(String(download))
|
||||
.then(res => res.text())
|
||||
.then(data => importDataToStore(data, store))
|
||||
.then(() => {
|
||||
store.dispatch(toggleAction('downloadingImportData', false));
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
console.error(e);
|
||||
store.dispatch(toggleAction('downloadingImportData', false));
|
||||
})
|
||||
);
|
||||
}
|
||||
const match = uriComponents(url);
|
||||
if (match.length > 1) {
|
||||
store.dispatch(
|
||||
// flipper://<client>/<pluginId>/<payload>
|
||||
return store.dispatch(
|
||||
selectPlugin({
|
||||
selectedApp: match[0],
|
||||
selectedPlugin: match[1],
|
||||
@@ -60,14 +81,6 @@ export default (store: Store, logger: Logger) => {
|
||||
importFileToStore(url, store);
|
||||
});
|
||||
|
||||
ipcRenderer.on('flipper-deeplink-preferred-plugin', (event, url) => {
|
||||
// flipper://<client>/<pluginId>/<payload>
|
||||
const match = uriComponents(url);
|
||||
if (match.length > 1) {
|
||||
store.dispatch(userPreferredPlugin(match[1]));
|
||||
}
|
||||
});
|
||||
|
||||
if (process.env.FLIPPER_PORTS) {
|
||||
const portOverrides = parseFlipperPorts(process.env.FLIPPER_PORTS);
|
||||
if (portOverrides) {
|
||||
|
||||
@@ -316,7 +316,6 @@ export function processEntry(
|
||||
entry: DeviceLogEntry,
|
||||
} {
|
||||
const {icon, style} = LOG_TYPES[(entry.type: string)] || LOG_TYPES.debug;
|
||||
|
||||
// build the item, it will either be batched or added straight away
|
||||
return {
|
||||
entry,
|
||||
|
||||
@@ -30,13 +30,15 @@ export type State = {
|
||||
insecure: number,
|
||||
secure: number,
|
||||
},
|
||||
downloadingImportData: boolean,
|
||||
};
|
||||
|
||||
type BooleanActionType =
|
||||
| 'leftSidebarVisible'
|
||||
| 'rightSidebarVisible'
|
||||
| 'rightSidebarAvailable'
|
||||
| 'windowIsFocused';
|
||||
| 'windowIsFocused'
|
||||
| 'downloadingImportData';
|
||||
|
||||
export type Action =
|
||||
| {
|
||||
@@ -66,6 +68,7 @@ const initialState: () => State = () => ({
|
||||
insecure: 8089,
|
||||
secure: 8088,
|
||||
},
|
||||
downloadingImportData: false,
|
||||
});
|
||||
|
||||
export default function reducer(state: State, action: Action): State {
|
||||
@@ -74,7 +77,8 @@ export default function reducer(state: State, action: Action): State {
|
||||
action.type === 'leftSidebarVisible' ||
|
||||
action.type === 'rightSidebarVisible' ||
|
||||
action.type === 'rightSidebarAvailable' ||
|
||||
action.type === 'windowIsFocused'
|
||||
action.type === 'windowIsFocused' ||
|
||||
action.type === 'downloadingImportData'
|
||||
) {
|
||||
const newValue =
|
||||
typeof action.payload === 'undefined'
|
||||
|
||||
@@ -244,12 +244,7 @@ export const exportStoreToFile = (
|
||||
});
|
||||
};
|
||||
|
||||
export const importFileToStore = (file: string, store: Store) => {
|
||||
fs.readFile(file, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
export function importDataToStore(data: string, store: Store) {
|
||||
const json = deserialize(data);
|
||||
const {device, clients} = json;
|
||||
const {serial, deviceType, title, os, logs} = device;
|
||||
@@ -312,5 +307,14 @@ export const importFileToStore = (file: string, store: Store) => {
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export const importFileToStore = (file: string, store: Store) => {
|
||||
fs.readFile(file, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
importDataToStore(data, store);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -131,7 +131,7 @@ app.on('will-finish-launching', () => {
|
||||
event.preventDefault();
|
||||
deeplinkURL = url;
|
||||
if (win) {
|
||||
win.webContents.send('flipper-deeplink', deeplinkURL);
|
||||
win.webContents.send('flipper-protocol-handler', deeplinkURL);
|
||||
}
|
||||
});
|
||||
app.on('open-file', (event, path) => {
|
||||
@@ -163,7 +163,7 @@ app.on('ready', function() {
|
||||
|
||||
ipcMain.on('componentDidMount', event => {
|
||||
if (deeplinkURL) {
|
||||
win.webContents.send('flipper-deeplink-preferred-plugin', deeplinkURL);
|
||||
win.webContents.send('flipper-protocol-handler', deeplinkURL);
|
||||
deeplinkURL = null;
|
||||
}
|
||||
if (filePath) {
|
||||
|
||||
13
yarn.lock
13
yarn.lock
@@ -5785,6 +5785,14 @@ qs@~6.5.1, qs@~6.5.2:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
|
||||
query-string@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.2.0.tgz#468edeb542b7e0538f9f9b1aeb26f034f19c86e1"
|
||||
integrity sha512-5wupExkIt8RYL4h/FE+WTg3JHk62e6fFPWtAZA9J5IWK1PfTfKkMS93HBUHcFpeYi9KsY5pFbh+ldvEyaz5MyA==
|
||||
dependencies:
|
||||
decode-uri-component "^0.2.0"
|
||||
strict-uri-encode "^2.0.0"
|
||||
|
||||
querystring@0.2.0, querystring@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
|
||||
@@ -6863,6 +6871,11 @@ stream-meter@1.0.4:
|
||||
dependencies:
|
||||
readable-stream "^2.1.4"
|
||||
|
||||
strict-uri-encode@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
|
||||
integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY=
|
||||
|
||||
string-length@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
|
||||
|
||||
Reference in New Issue
Block a user