feat: 增加手动安装插件功能

pull/13/head
moonrailgun 4 years ago
parent ca8df00861
commit 7929ef1ecf

@ -146,6 +146,7 @@ export {
version,
} from './utils/environment';
export { getTextColorHex, isValidStr } from './utils/string-helper';
export { isValidJson } from './utils/json-helper';
export { MessageHelper } from './utils/message-helper';
export { uploadFile } from './utils/upload-helper';
export type { UploadFileResult } from './utils/upload-helper';

@ -12,6 +12,7 @@ import { useAppDispatch, useAppSelector } from './useAppSelector';
import _isNil from 'lodash/isNil';
import { useChatBoxContext } from '../..';
import { MessageHelper } from '../../utils/message-helper';
import { ChatConverseType } from '../../model/converse';
function useHandleSendMessage(context: ConverseContext) {
const { converseId } = context;
@ -91,7 +92,7 @@ export function useConverseMessage(context: ConverseContext) {
chatActions.setConverseInfo({
_id: converseId,
name: '',
type: 'Group',
type: ChatConverseType.Group,
members: [],
})
);

@ -0,0 +1,18 @@
import { isValidJson } from '../json-helper';
describe('isValidJson', () => {
test.each([
['foo', false],
['[]', true],
['{}', true],
['{"foo": []}', true],
['{"foo": [}', false],
['{foo: bar}', false],
['{"foo": "bar"}', true],
[[], false],
[null, false],
[undefined, false],
])('%s => %s', (input: any, should) => {
expect(isValidJson(input)).toBe(should);
});
});

@ -0,0 +1,16 @@
/**
* json
* @param jsonStr json
*/
export function isValidJson(jsonStr: string): boolean {
if (typeof jsonStr !== 'string') {
return false;
}
try {
JSON.parse(jsonStr);
return true;
} catch (e) {
return false;
}
}

@ -1,11 +1,17 @@
// mock
jest.mock('tailchat-shared/i18n');
const ignoreErroMessages = [
/Warning.*not wrapped in act/,
/PluginManifest validation/,
];
// https://github.com/testing-library/react-testing-library#suppressing-unnecessary-warnings-on-react-dom-168
const originalError = console.error;
console.error = (...args) => {
if (/Warning.*not wrapped in act/.test(args[0])) {
if (ignoreErroMessages.some((re) => re.test(args[0]))) {
return;
}
originalError.call(console, ...args);
};

@ -21,6 +21,7 @@
"antd": "^4.16.6",
"clsx": "^1.1.1",
"is-hotkey": "^0.2.0",
"jsonschema": "^1.4.0",
"jwt-decode": "^3.1.2",
"mini-star": "^1.0.0",
"p-min-delay": "^4.0.0",

@ -0,0 +1,41 @@
import { Button, Input } from 'antd';
import React, { useMemo, useState } from 'react';
import { isValidJson, t, useAsyncRequest } from 'tailchat-shared';
import { pluginManager } from '../manager';
import { parsePluginManifest } from '../utils';
export const ManualInstall: React.FC = React.memo(() => {
const [json, setJson] = useState('');
const [{ loading }, handleInstallPlugin] = useAsyncRequest(async () => {
pluginManager.installPlugin(parsePluginManifest(json));
}, [json]);
const invalid = useMemo(() => !isValidJson(json), [json]);
return (
<div>
<Input.TextArea
placeholder={t(
'请手动输入JSON信息如果你不明确你在做什么请不要使用该功能'
)}
disabled={loading}
value={json}
onChange={(e) => setJson(e.target.value)}
/>
<div className="text-red-500">
{invalid && json !== '' && t('不是一个合法的JSON字符串')}&nbsp;
</div>
<Button
loading={loading}
disabled={invalid}
onClick={handleInstallPlugin}
>
{t('确认')}
</Button>
</div>
);
});
ManualInstall.displayName = 'ManualInstall';

@ -10,6 +10,7 @@ import { t, useAsync } from 'tailchat-shared';
import { builtinPlugins } from '../builtin';
import { pluginManager } from '../manager';
import { PluginStoreItem } from './Item';
import { ManualInstall } from './ManualInstall';
function usePluginStoreData() {
const { loading: loading1, value: installedPluginList = [] } = useAsync(
@ -86,6 +87,13 @@ export const PluginStore: React.FC = React.memo(() => {
))}
</div>
</PillTabPane>
<PillTabPane
tab={<span className="text-green-400">{t('手动安装')}</span>}
key="3"
>
<ManualInstall />
</PillTabPane>
</PillTabs>
</div>
);

@ -0,0 +1,55 @@
import { parsePluginManifest } from '../utils';
describe('parsePluginManifest', () => {
test.each([
[
'correct',
JSON.stringify({
label: '网页面板插件',
name: 'com.msgbyte.webview',
url: '/plugins/com.msgbyte.webview/index.js',
version: '0.0.0',
author: 'msgbyte',
description: '为群组提供创建网页面板的功能',
requireRestart: false,
}),
true,
],
['string', 'foo.bar', false],
[
'no used properties',
JSON.stringify({
label: '网页面板插件',
foo: 'bar',
}),
false,
],
[
'more properties',
JSON.stringify({
label: '网页面板插件',
name: 'com.msgbyte.webview',
url: '/plugins/com.msgbyte.webview/index.js',
version: '0.0.0',
author: 'msgbyte',
description: '为群组提供创建网页面板的功能',
requireRestart: false,
foo: 'bar',
}),
true,
],
[
'missed properties',
JSON.stringify({
label: '网页面板插件',
}),
false,
],
])('case: %# %s', (title, input, valid) => {
if (valid === true) {
expect(parsePluginManifest(input)).toEqual(JSON.parse(input));
} else {
expect(() => parsePluginManifest(input)).toThrowError();
}
});
});

@ -0,0 +1,39 @@
import { isValidJson, PluginManifest, t } from 'tailchat-shared';
import { Validator } from 'jsonschema';
/**
* json
*
*/
export function parsePluginManifest(json: string): PluginManifest {
if (!isValidJson(json)) {
throw new Error(t('不是一个合法的JSON字符串'));
}
const obj = JSON.parse(json);
const { valid, errors } = new Validator().validate(obj, {
type: 'object',
properties: {
label: { type: 'string' },
name: { type: 'string' },
url: { type: 'string' },
version: { type: 'string' },
author: { type: 'string' },
description: { type: 'string' },
requireRestart: { type: 'boolean' },
},
required: ['label', 'name', 'url', 'version', 'author', 'description'],
additionalProperties: false,
});
if (!valid) {
console.error(
'[PluginManifest validation]:',
errors.map((e) => e.message).join(', ')
);
throw new Error(t('不是一个合法的插件配置'));
}
return obj;
}

@ -7195,6 +7195,11 @@ jsonpointer@^4.1.0:
resolved "https://registry.npm.taobao.org/jsonpointer/download/jsonpointer-4.1.0.tgz#501fb89986a2389765ba09e6053299ceb4f2c2cc"
integrity sha1-UB+4mYaiOJdlugnmBTKZzrTywsw=
jsonschema@^1.4.0:
version "1.4.0"
resolved "https://registry.npm.taobao.org/jsonschema/download/jsonschema-1.4.0.tgz#1afa34c4bc22190d8e42271ec17ac8b3404f87b2"
integrity sha1-Gvo0xLwiGQ2OQicewXrIs0BPh7I=
"jsx-ast-utils@^2.4.1 || ^3.0.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82"

Loading…
Cancel
Save