fix: get hostUrl from form submission values itself in options page

This commit is contained in:
abhijithvijayan
2020-03-08 22:30:40 +05:30
parent b685899e7b
commit c3b6cf6450
7 changed files with 106 additions and 53 deletions

View File

@@ -7,7 +7,11 @@ import { browser } from 'webextension-polyfill-ts';
import axios, { AxiosPromise } from 'axios';
import * as constants from './constants';
import { Kutt } from '../Popup/Popup';
export enum Kutt {
hostDomain = 'kutt.it',
hostUrl = 'https://kutt.it',
}
type ShortenUrlBodyProperties = {
target: string;
@@ -43,7 +47,7 @@ export type SuccessfulShortenStatusProperties = {
data: ShortenLinkResponseProperties;
};
type HostUrlProperties = typeof Kutt.hostUrl;
type HostUrlProperties = string;
export type DomainEntryProperties = {
address: string;

View File

@@ -2,12 +2,21 @@ import React, { useEffect, useState } from 'react';
import { getExtensionSettings } from '../util/settings';
import BodyWrapper from '../components/BodyWrapper';
import { isValidUrl } from '../util/tabs';
import Loader from '../components/Loader';
import OptionsForm, { OptionsFormValuesProperties } from './OptionsForm';
import OptionsForm from './OptionsForm';
export type ExtensionConfigProperties = {
apikey: string;
autocopy: boolean;
history: boolean;
advanced: boolean;
customhost: string; // for form values
};
const Options: React.FC = () => {
const [loading, setLoading] = useState(true);
const [defaultValues, setDefaultValues] = useState<OptionsFormValuesProperties>({
const [extensionConfig, setExtensionConfig] = useState<ExtensionConfigProperties>({
apikey: '',
autocopy: true,
history: false,
@@ -18,38 +27,49 @@ const Options: React.FC = () => {
useEffect(() => {
async function getSavedSettings(): Promise<void> {
const { settings = {} } = await getExtensionSettings();
const customHost: string = settings.customhost || defaultValues.customhost;
const advancedSettings: boolean = settings.advanced || defaultValues.advanced;
// eslint-disable-next-line no-nested-ternary
const customHost: string = settings.customhost
? isValidUrl(settings.customurl)
? settings.customhost
: extensionConfig.customhost
: extensionConfig.customhost;
const advancedSettings: boolean = settings.advanced || extensionConfig.advanced;
// inject existing keys (if field doesn't exist, use default)
const defaultFormValues: OptionsFormValuesProperties = {
apikey: settings.apikey || defaultValues.apikey,
const defaultExtensionConfig: ExtensionConfigProperties = {
apikey: settings.apikey || extensionConfig.apikey,
autocopy: Object.prototype.hasOwnProperty.call(settings, 'autocopy')
? settings.autocopy
: defaultValues.autocopy,
: extensionConfig.autocopy,
history: Object.prototype.hasOwnProperty.call(settings, 'history')
? settings.history
: defaultValues.history,
advanced: customHost.trim().length > 0 ? advancedSettings : defaultValues.advanced, // disable `advance` if customhost is not set
customhost: advancedSettings === true ? customHost : defaultValues.customhost, // drop customhost value if `advanced` is false
: extensionConfig.history,
advanced: customHost.trim().length > 0 ? advancedSettings : extensionConfig.advanced, // disable `advance` if customhost is not set
customhost:
// eslint-disable-next-line no-nested-ternary
advancedSettings === true
? customHost.endsWith('/')
? customHost.slice(0, -1)
: customHost
: extensionConfig.customhost, // drop customhost value if `advanced` is false
};
setDefaultValues(defaultFormValues);
setExtensionConfig(defaultExtensionConfig);
setLoading(false);
}
getSavedSettings();
}, [
defaultValues.apikey,
defaultValues.autocopy,
defaultValues.history,
defaultValues.advanced,
defaultValues.customhost,
extensionConfig.apikey,
extensionConfig.autocopy,
extensionConfig.history,
extensionConfig.advanced,
extensionConfig.customhost,
]); // dependencies
return (
<BodyWrapper>
<div id="options">{!loading ? <OptionsForm defaultValues={defaultValues} /> : <Loader />}</div>
<div id="options">{!loading ? <OptionsForm extensionConfig={extensionConfig} /> : <Loader />}</div>
</BodyWrapper>
);
};

View File

@@ -2,11 +2,18 @@ import React, { useEffect } from 'react';
import { withFormik, Field, Form, FormikBag, FormikProps, FormikErrors } from 'formik';
import Icon from '../components/Icon';
import {
Kutt,
SuccessfulApiKeyCheckProperties,
ApiErroredProperties,
GetUserSettingsBodyProperties,
} from '../Background';
import { isValidUrl } from '../util/tabs';
import messageUtil from '../util/mesageUtil';
import { ExtensionConfigProperties } from './Options';
import { CHECK_API_KEY } from '../Background/constants';
import { TextField, CheckBox } from '../components/Input';
import { updateExtensionSettings } from '../util/settings';
import { SuccessfulApiKeyCheckProperties, ApiErroredProperties } from '../Background';
export type OptionsFormValuesProperties = {
apikey: string;
@@ -24,12 +31,17 @@ const onSave = (values: OptionsFormValuesProperties): Promise<any> => {
// Note: The default key-value pairs are not saved to storage without any first interaction
const InnerForm: React.FC<FormikProps<OptionsFormValuesProperties>> = props => {
const { isSubmitting, handleSubmit, setStatus, status, values } = props;
const { isSubmitting, handleSubmit, setFieldValue, setStatus, status, values } = props;
// on component mount -> set `settings` object
useEffect(() => {
// Reset `customhost` field on `advanced` untick
if (values.advanced === false) {
setFieldValue('customhost', '');
}
onSave({ ...values, ...(values.advanced === false && { customhost: '' }) });
}, [values]);
}, [values, setFieldValue]);
// run on component update
useEffect(() => {
@@ -72,15 +84,15 @@ const InnerForm: React.FC<FormikProps<OptionsFormValuesProperties>> = props => {
// The type of props `OptionsForm` receives
type OptionsFormProperties = {
defaultValues: OptionsFormValuesProperties;
extensionConfig: ExtensionConfigProperties;
};
// Wrap our form with the withFormik HoC
const OptionsForm = withFormik<OptionsFormProperties, OptionsFormValuesProperties>({
// Transform outer props into form values
mapPropsToValues: ({
defaultValues: { apikey, autocopy, history, advanced, customhost },
}): OptionsFormValuesProperties => {
extensionConfig: { apikey, autocopy, history, advanced, customhost },
}: OptionsFormProperties): OptionsFormValuesProperties => {
return {
apikey,
autocopy,
@@ -96,30 +108,41 @@ const OptionsForm = withFormik<OptionsFormProperties, OptionsFormValuesPropertie
if (!values.apikey) {
errors.apikey = 'API key missing';
}
// ToDo: restore before on production
// else if (values.apikey && values.apikey.trim().length < 40) {
// errors.apikey = 'API key must be 40 characters';
// } else if (values.apikey && values.apikey.trim().length > 40) {
// errors.apikey = 'API key cannot exceed 40 characters';
// }
// ToDo: add custom domain validation
// should begin with `http://` or `https://`
// dont end with `/`
// no spaces(validate a url)
if (values.advanced && values.customhost.trim().length <= 0) {
errors.customhost = 'Custom URL cannot be empty';
}
if (values.customhost.trim().length > 0) {
if (!isValidUrl(values.customhost.trim()) || values.customhost.trim().length < 10) {
errors.customhost = 'Please enter a valid url';
}
}
return errors;
},
// for API Key validation only
handleSubmit: async (
values: OptionsFormValuesProperties,
{ apikey, customhost }: OptionsFormValuesProperties,
{ setSubmitting, setStatus }: FormikBag<OptionsFormProperties, OptionsFormValuesProperties>
) => {
// request API validation request
// ToDo: attach customdomain (if exist)
const response: SuccessfulApiKeyCheckProperties | ApiErroredProperties = await messageUtil.send(CHECK_API_KEY, {
apikey: values.apikey.trim(),
});
const apiKeyValidationBody: GetUserSettingsBodyProperties = {
apikey: apikey.trim(),
hostUrl: customhost.trim().length > 0 ? customhost.trim() : Kutt.hostUrl,
};
const response: SuccessfulApiKeyCheckProperties | ApiErroredProperties = await messageUtil.send(
CHECK_API_KEY,
apiKeyValidationBody
);
if (!response.error) {
// set top-level status

View File

@@ -2,11 +2,11 @@ import React, { useState } from 'react';
import Icon from '../components/Icon';
import messageUtil from '../util/mesageUtil';
import { UserConfigProperties, SetPageReloadFlagProperties } from './Popup';
import { openExtOptionsPage } from '../util/tabs';
import { CHECK_API_KEY } from '../Background/constants';
import { updateExtensionSettings } from '../util/settings';
import { SuccessfulApiKeyCheckProperties, ApiErroredProperties, GetUserSettingsBodyProperties } from '../Background';
import { UserConfigProperties, SetPageReloadFlagProperties } from './Popup';
type SetLoadingProperties = React.Dispatch<React.SetStateAction<boolean>>;

View File

@@ -1,20 +1,20 @@
import React, { useEffect, useState } from 'react';
import { UserSettingsResponseProperties } from '../Background';
import PopupBody, { ProcessedRequestProperties } from './PopupBody';
import { Kutt, UserSettingsResponseProperties } from '../Background';
import { getExtensionSettings } from '../util/settings';
import BodyWrapper from '../components/BodyWrapper';
import Loader from '../components/Loader';
import PopupForm from './PopupForm';
import PopupHeader from './Header';
import PopupBody, { ProcessedRequestProperties } from './PopupBody';
import './styles.scss';
import { openExtOptionsPage } from '../util/tabs';
import { openExtOptionsPage, isValidUrl } from '../util/tabs';
export enum Kutt {
hostDomain = 'kutt.it',
hostUrl = 'https://kutt.it',
}
type HostProperties = {
hostDomain: string;
hostUrl: string;
};
type DomainOptionsProperties = {
option: string;
@@ -33,7 +33,7 @@ export type ProcessRequestProperties = React.Dispatch<
export type UserConfigProperties = {
apikey: string;
domainOptions: DomainOptionsProperties[];
host: typeof Kutt;
host: HostProperties;
};
export type SetPageReloadFlagProperties = React.Dispatch<React.SetStateAction<boolean>>;
@@ -67,7 +67,7 @@ const Popup: React.FC = () => {
return;
}
let defaultHost = Kutt;
let defaultHost: HostProperties = Kutt;
// If `advanced` field is true
if (Object.prototype.hasOwnProperty.call(settings, 'advanced') && settings.advanced) {
@@ -75,7 +75,7 @@ const Popup: React.FC = () => {
if (
Object.prototype.hasOwnProperty.call(settings, 'customhost') &&
settings.customhost.trim().length > 0 &&
(settings.customhost.startsWith('http://') || settings.customhost.startsWith('https://'))
isValidUrl(settings.customurl)
) {
defaultHost = {
hostDomain: settings.customhost

View File

@@ -1,18 +1,17 @@
import React from 'react';
import { withFormik, Field, Form, FormikBag, FormikProps, FormikErrors } from 'formik';
import messageUtil from '../util/mesageUtil';
import { UserConfigProperties, ProcessRequestProperties } from './Popup';
import { getCurrentTab } from '../util/tabs';
import { SHORTEN_URL } from '../Background/constants';
import { SelectField, TextField } from '../components/Input';
import {
ApiBodyProperties,
SuccessfulShortenStatusProperties,
ApiErroredProperties,
ShortUrlActionBodyProperties,
} from '../Background';
import messageUtil from '../util/mesageUtil';
import { getCurrentTab, isValidUrl } from '../util/tabs';
import { SHORTEN_URL } from '../Background/constants';
import { SelectField, TextField } from '../components/Input';
import { ProcessRequestProperties, UserConfigProperties } from './Popup';
type PopupFormValuesProperties = {
password: string;
@@ -120,9 +119,9 @@ const PopupForm = withFormik<PopupFormProperties, PopupFormValuesProperties>({
const tabs = await getCurrentTab();
const target: string | null = (tabs.length > 0 && tabs[0].url) || null;
if (!target || !target.startsWith('http')) {
if (!target || !isValidUrl(target)) {
setLoading(false);
// No valid target
return setRequestProcessed({ error: true, message: 'Not a valid URL' });
}

View File

@@ -10,3 +10,10 @@ export function getCurrentTab(): Promise<Tabs.Tab[]> {
lastFocusedWindow: true,
});
}
export function isValidUrl(url: string): boolean {
// https://regex101.com/r/iXVlNL/1/
const re = /^(http[s]?:\/\/)(www\.){0,1}[a-zA-Z0-9.-]+\.[a-zA-Z]{2,5}[.]{0,1}/;
return re.test(url);
}