diff --git a/packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.tsx b/packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.tsx
index 2dcfe1e18..af91f2c07 100644
--- a/packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.tsx
+++ b/packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.tsx
@@ -9,6 +9,7 @@ import PluginBox, { InstallState } from './PluginBox';
import PluginService, { PluginSettings } from '@joplin/lib/services/plugins/PluginService';
import { _ } from '@joplin/lib/locale';
import useOnInstallHandler from './useOnInstallHandler';
+import { themeStyle } from '@joplin/lib/theme';
const Root = styled.div`
`;
@@ -100,6 +101,13 @@ export default function(props: Props) {
}
}
+ const renderContentSourceInfo = () => {
+ if (props.repoApi().isUsingDefaultContentUrl) return null;
+ const theme = themeStyle(props.themeId);
+ const url = new URL(props.repoApi().contentBaseUrl);
+ return
{_('Content provided by %s', url.hostname)}
;
+ };
+
return (
@@ -112,6 +120,7 @@ export default function(props: Props) {
placeholder={props.disabled ? _('Please wait...') : _('Search for plugins...')}
disabled={props.disabled}
/>
+ {renderContentSourceInfo()}
diff --git a/packages/lib/services/plugins/RepositoryApi.ts b/packages/lib/services/plugins/RepositoryApi.ts
index c30210049..49051a844 100644
--- a/packages/lib/services/plugins/RepositoryApi.ts
+++ b/packages/lib/services/plugins/RepositoryApi.ts
@@ -16,6 +16,36 @@ interface Release {
assets: ReleaseAsset[];
}
+const findWorkingGitHubUrl = async (defaultContentUrl: string): Promise => {
+ // From: https://github.com/laurent22/joplin/issues/5161#issuecomment-921642721
+
+ const mirrorUrls = [
+ defaultContentUrl,
+ 'https://cdn.staticaly.com/gh/joplin/plugins/master',
+ 'https://ghproxy.com/https://raw.githubusercontent.com/joplin/plugins/master',
+ 'https://cdn.jsdelivr.net/gh/joplin/plugins@master',
+ 'https://raw.fastgit.org/joplin/plugins/master',
+ ];
+
+ for (const mirrorUrl of mirrorUrls) {
+ try {
+ // We try to fetch .gitignore, which is smaller than the whole manifest
+ await fetch(`${mirrorUrl}/.gitignore`);
+ } catch (error) {
+ logger.info(`findWorkingMirror: Could not connect to ${mirrorUrl}:`, error);
+ continue;
+ }
+
+ logger.info(`findWorkingMirror: Using: ${mirrorUrl}`);
+
+ return mirrorUrl;
+ }
+
+ logger.info('findWorkingMirror: Could not find any working GitHub URL');
+
+ return defaultContentUrl;
+};
+
export default class RepositoryApi {
// As a base URL, this class can support either a remote repository or a
@@ -29,6 +59,9 @@ export default class RepositoryApi {
private tempDir_: string;
private release_: Release = null;
private manifests_: PluginManifest[] = null;
+ private githubApiUrl_: string;
+ private contentBaseUrl_: string;
+ private isUsingDefaultContentUrl_: boolean = true;
public constructor(baseUrl: string, tempDir: string) {
this.baseUrl_ = baseUrl;
@@ -36,6 +69,14 @@ export default class RepositoryApi {
}
public async initialize() {
+ // https://github.com/joplin/plugins
+ // https://api.github.com/repos/joplin/plugins/releases
+ this.githubApiUrl_ = this.baseUrl_.replace(/^(https:\/\/)(github\.com\/)(.*)$/, '$1api.$2repos/$3');
+ const defaultContentBaseUrl = `${this.baseUrl_.replace(/github\.com/, 'raw.githubusercontent.com')}/master`;
+ this.contentBaseUrl_ = await findWorkingGitHubUrl(defaultContentBaseUrl);
+
+ this.isUsingDefaultContentUrl_ = this.contentBaseUrl_ === defaultContentBaseUrl;
+
await this.loadManifests();
await this.loadRelease();
}
@@ -45,18 +86,33 @@ export default class RepositoryApi {
try {
const manifests = JSON.parse(manifestsText);
if (!manifests) throw new Error('Invalid or missing JSON');
+
this.manifests_ = Object.keys(manifests).map(id => {
- return manifests[id];
+ const m: PluginManifest = manifests[id];
+ // If we don't control the repository, we can't recommend
+ // anything on it since it could have been modified.
+ if (!this.isUsingDefaultContentUrl) m._recommended = false;
+ return m;
});
} catch (error) {
throw new Error(`Could not parse JSON: ${error.message}`);
}
}
+ public get isUsingDefaultContentUrl() {
+ return this.isUsingDefaultContentUrl_;
+ }
+
private get githubApiUrl(): string {
- // https://github.com/joplin/plugins
- // https://api.github.com/repos/joplin/plugins/releases
- return this.baseUrl_.replace(/^(https:\/\/)(github\.com\/)(.*)$/, '$1api.$2repos/$3');
+ return this.githubApiUrl_;
+ }
+
+ public get contentBaseUrl(): string {
+ if (this.isLocalRepo) {
+ return this.baseUrl_;
+ } else {
+ return this.contentBaseUrl_;
+ }
}
private async loadRelease() {
@@ -78,14 +134,6 @@ export default class RepositoryApi {
return this.baseUrl_.indexOf('http') !== 0;
}
- private get contentBaseUrl(): string {
- if (this.isLocalRepo) {
- return this.baseUrl_;
- } else {
- return `${this.baseUrl_.replace(/github\.com/, 'raw.githubusercontent.com')}/master`;
- }
- }
-
private assetFileUrl(pluginId: string): string {
if (this.release_) {
const asset = this.release_.assets.find(asset => {