-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathPluginUtils.tsx
More file actions
164 lines (146 loc) · 5.01 KB
/
PluginUtils.tsx
File metadata and controls
164 lines (146 loc) · 5.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import Log from '@deephaven/log';
import {
type PluginModuleMap,
type AuthPlugin,
type AuthPluginComponent,
isAuthPlugin,
type LegacyAuthPlugin,
type LegacyPlugin,
type Plugin,
PluginType,
isLegacyAuthPlugin,
processLoadedModule,
sortPluginsByDependency,
type PluginManifest,
} from '@deephaven/plugin';
import loadRemoteModule from './loadRemoteModule';
import { resolve } from './remote-component.config';
const log = Log.module('@deephaven/app-utils.PluginUtils');
/**
* Imports a commonjs plugin module from the provided URL
* @param pluginUrl The URL of the plugin to load
* @returns The loaded module
*/
export async function loadModulePlugin(
pluginUrl: string
): Promise<LegacyPlugin | { default: Plugin }> {
const myModule = await loadRemoteModule(pluginUrl);
return myModule;
}
/**
* Loads a JSON file and returns the JSON object
* @param jsonUrl The URL of the JSON file to load
* @returns The JSON object of the manifest file
*/
export async function loadJson(jsonUrl: string): Promise<PluginManifest> {
const res = await fetch(jsonUrl);
if (!res.ok) {
throw new Error(res.statusText);
}
try {
return await res.json();
} catch {
throw new Error('Could not be parsed as JSON');
}
}
/**
* Load all plugin modules available based on the manifest file at the provided base URL.
* Plugins are loaded sequentially so that each plugin's exports are registered
* in the module resolve map before subsequent plugins load. This enables
* cross-plugin imports via standard import statements.
* @param modulePluginsUrl The base URL of the module plugins to load
* @returns A map from the name of the plugin to the plugin module that was loaded
*/
export async function loadModulePlugins(
modulePluginsUrl: string
): Promise<PluginModuleMap> {
log.debug('Loading plugins...');
try {
const manifest = await loadJson(`${modulePluginsUrl}/manifest.json`);
if (!Array.isArray(manifest.plugins)) {
throw new Error('Plugin manifest JSON does not contain plugins array');
}
log.debug('Plugin manifest loaded:', manifest);
const sortedPlugins = sortPluginsByDependency(manifest.plugins);
const pluginMap: PluginModuleMap = new Map();
// Load plugins sequentially so each plugin's exports are available
// to subsequently loaded plugins via import
for (let i = 0; i < sortedPlugins.length; i += 1) {
const { name, main, version, package: packageName } = sortedPlugins[i];
const pluginMainUrl = `${modulePluginsUrl}/${name}/${main}`;
try {
// eslint-disable-next-line no-await-in-loop
const pluginExports = await loadModulePlugin(pluginMainUrl);
processLoadedModule(
pluginMap,
resolve,
pluginExports,
name,
packageName,
version
);
} catch (e) {
log.error(`Unable to load plugin '${name}'`, e);
}
}
log.info('Plugins loaded:', pluginMap);
return pluginMap;
} catch (e) {
log.error('Unable to load plugins:', e);
return new Map();
}
}
export function getAuthHandlers(
authConfigValues: Map<string, string>
): string[] {
return authConfigValues.get('AuthHandlers')?.split(',') ?? [];
}
/**
* Get the auth plugin component from the plugin map and current configuration
* Throws if no auth plugin is available
*
* @param pluginMap Map of plugins loaded from the server
* @param authConfigValues Auth config values from the server
* @param corePlugins Map of core auth plugins to include in the list. They are added after the loaded plugins
* @returns The auth plugin component to render
*/
export function getAuthPluginComponent(
pluginMap: PluginModuleMap,
authConfigValues: Map<string, string>,
corePlugins = new Map<string, AuthPlugin | LegacyAuthPlugin>()
): AuthPluginComponent {
const authHandlers = getAuthHandlers(authConfigValues);
// User plugins take priority over core plugins
const authPlugins = (
[...pluginMap.entries(), ...corePlugins.entries()].filter(
([, plugin]) => isAuthPlugin(plugin) || isLegacyAuthPlugin(plugin)
) as [string, AuthPlugin | LegacyAuthPlugin][]
).map(([name, plugin]) => {
if (isLegacyAuthPlugin(plugin)) {
return {
type: PluginType.AUTH_PLUGIN,
name,
component: plugin.AuthPlugin.Component,
isAvailable: plugin.AuthPlugin.isAvailable,
};
}
return plugin;
});
// Filter the available auth plugins
const availableAuthPlugins = authPlugins.filter(({ isAvailable }) =>
isAvailable(authHandlers, authConfigValues)
);
if (availableAuthPlugins.length === 0) {
throw new Error(
`No login plugins found, please register a login plugin for auth handlers: ${authHandlers}`
);
} else if (availableAuthPlugins.length > 1) {
log.warn(
'More than one login plugin available, will use the first one: ',
availableAuthPlugins.map(({ name }) => name).join(', ')
);
}
const { name, component } = availableAuthPlugins[0];
log.info('Using LoginPlugin', name);
return component;
}