@adobe/uix-host-react 0.10.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ExtensibleWrapper/ExtensibleWrapper.d.ts +33 -0
- package/dist/components/ExtensibleWrapper/ExtensibleWrapper.d.ts.map +1 -0
- package/dist/components/ExtensibleWrapper/ExtensionManagerProvider.d.ts +129 -0
- package/dist/components/ExtensibleWrapper/ExtensionManagerProvider.d.ts.map +1 -0
- package/dist/components/ExtensibleWrapper/ExtensionManagerProvider.test.d.ts +2 -0
- package/dist/components/ExtensibleWrapper/ExtensionManagerProvider.test.d.ts.map +1 -0
- package/dist/components/ExtensibleWrapper/UrlExtensionProvider.d.ts +37 -0
- package/dist/components/ExtensibleWrapper/UrlExtensionProvider.d.ts.map +1 -0
- package/dist/components/ExtensibleWrapper/UrlExtensionProvider.test.d.ts +2 -0
- package/dist/components/ExtensibleWrapper/UrlExtensionProvider.test.d.ts.map +1 -0
- package/dist/components/ExtensibleWrapper/index.d.ts +4 -0
- package/dist/components/ExtensibleWrapper/index.d.ts.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/hooks/useExtensions.d.ts.map +1 -1
- package/dist/index.js +258 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/ExtensibleWrapper/ExtensibleWrapper.tsx +133 -0
- package/src/components/ExtensibleWrapper/ExtensionManagerProvider.test.ts +214 -0
- package/src/components/ExtensibleWrapper/ExtensionManagerProvider.ts +324 -0
- package/src/components/ExtensibleWrapper/UrlExtensionProvider.test.ts +119 -0
- package/src/components/ExtensibleWrapper/UrlExtensionProvider.ts +93 -0
- package/src/components/ExtensibleWrapper/index.ts +15 -0
- package/src/components/index.ts +1 -0
- package/src/hooks/useExtensions.ts +9 -3
@@ -0,0 +1,324 @@
|
|
1
|
+
/*************************************************************************
|
2
|
+
* ADOBE CONFIDENTIAL
|
3
|
+
* ___________________
|
4
|
+
*
|
5
|
+
* Copyright 2024 Adobe
|
6
|
+
* All Rights Reserved.
|
7
|
+
*
|
8
|
+
* NOTICE: All information contained herein is, and remains
|
9
|
+
* the property of Adobe and its suppliers, if any. The intellectual
|
10
|
+
* and technical concepts contained herein are proprietary to Adobe
|
11
|
+
* and its suppliers and are protected by all applicable intellectual
|
12
|
+
* property laws, including trade secret and copyright laws.
|
13
|
+
* Dissemination of this information or reproduction of this material
|
14
|
+
* is strictly forbidden unless prior written permission is obtained
|
15
|
+
* from Adobe.
|
16
|
+
**************************************************************************/
|
17
|
+
|
18
|
+
import {
|
19
|
+
createExtensionRegistryAsObjectsProvider,
|
20
|
+
ExtensionsProvider,
|
21
|
+
InstalledExtensions,
|
22
|
+
} from "@adobe/uix-host";
|
23
|
+
|
24
|
+
const EXTENSION_MANAGER_URL_PROD = "https://aemx-mngr.adobe.io";
|
25
|
+
const EXTENSION_MANAGER_URL_STAGE = "https://aemx-mngr-stage.adobe.io";
|
26
|
+
|
27
|
+
const APP_REGISTRY_URL_PROD = "https://appregistry.adobe.io";
|
28
|
+
const APP_REGISTRY_URL_STAGE = "https://appregistry-stage.adobe.io";
|
29
|
+
|
30
|
+
// Extension Manager stores information about extension points that a particular extension implements
|
31
|
+
// in the "extensionPoints" array of objects of the following "ExtensionPoint" type
|
32
|
+
// where "extensionPoint" is the name of the extension point, for example, "aem/assets/details/1"
|
33
|
+
// "url" is the extension url for the specified extension point
|
34
|
+
type ExtensionPoint = {
|
35
|
+
extensionPoint: string;
|
36
|
+
url: string;
|
37
|
+
};
|
38
|
+
export type ExtensionManagerExtension = {
|
39
|
+
id: string;
|
40
|
+
name: string;
|
41
|
+
title: string;
|
42
|
+
description: string;
|
43
|
+
status: string;
|
44
|
+
supportEmail: string;
|
45
|
+
extId: string;
|
46
|
+
disabled: boolean;
|
47
|
+
extensionPoints: ExtensionPoint[];
|
48
|
+
scope: Record<string, unknown>;
|
49
|
+
configuration?: Record<string, unknown>;
|
50
|
+
};
|
51
|
+
|
52
|
+
type AuthEMConfig = {
|
53
|
+
schema: "Bearer" | "Basic";
|
54
|
+
imsToken: string;
|
55
|
+
};
|
56
|
+
export interface ExtensionManagerConfig {
|
57
|
+
apiKey: string;
|
58
|
+
auth: AuthEMConfig;
|
59
|
+
service: string;
|
60
|
+
extensionPoint: string;
|
61
|
+
version: string;
|
62
|
+
imsOrg: string;
|
63
|
+
baseUrl: string;
|
64
|
+
scope?: Record<string, string>;
|
65
|
+
}
|
66
|
+
|
67
|
+
/** Authentication configuration, including IMS Org ID, access token, and API key */
|
68
|
+
export interface AuthConfig {
|
69
|
+
/** IMS Org ID */
|
70
|
+
imsOrg: string;
|
71
|
+
/** Access token for the user */
|
72
|
+
imsToken: string;
|
73
|
+
/** API key */
|
74
|
+
apiKey: string;
|
75
|
+
}
|
76
|
+
|
77
|
+
/** Discovery configuration, including environment and repo Id */
|
78
|
+
export interface DiscoveryConfig {
|
79
|
+
/** Environment level for backend Extension resolution services */
|
80
|
+
experienceShellEnvironment?: "prod" | "stage";
|
81
|
+
scope?: Record<string, string>;
|
82
|
+
}
|
83
|
+
|
84
|
+
/** Extension point ID */
|
85
|
+
export interface ExtensionPointId {
|
86
|
+
/** Service name */
|
87
|
+
service: string;
|
88
|
+
/** Extension point name */
|
89
|
+
name: string;
|
90
|
+
/** Extension point version */
|
91
|
+
version: string;
|
92
|
+
}
|
93
|
+
|
94
|
+
/**
|
95
|
+
* Sets up new ExtensionsProvider with authentication and discovery information needed to fetch the list of
|
96
|
+
* Extensions from AppRegistry and Extension Manager service, along with the query string portion of URL
|
97
|
+
* to extract the information about development Extensions
|
98
|
+
*/
|
99
|
+
export interface ExtensionsProviderConfig {
|
100
|
+
/** Discovery configuration */
|
101
|
+
discoveryConfig: DiscoveryConfig;
|
102
|
+
/** Authentication configuration */
|
103
|
+
authConfig: AuthConfig;
|
104
|
+
/** Extension point ID */
|
105
|
+
extensionPointId: ExtensionPointId;
|
106
|
+
providerConfig: ExtensionProviderConfig;
|
107
|
+
}
|
108
|
+
|
109
|
+
export interface ExtensionProviderConfig {
|
110
|
+
extensionManagerUrl?: string;
|
111
|
+
appRegistryUrl?: string;
|
112
|
+
disableExtensionManager?: boolean;
|
113
|
+
}
|
114
|
+
export const getExtensionRegistryBaseUrl = (
|
115
|
+
environment: "prod" | "stage" | undefined,
|
116
|
+
registry: string | null
|
117
|
+
): string =>
|
118
|
+
environment === "prod"
|
119
|
+
? APP_REGISTRY_URL_PROD
|
120
|
+
: registry ?? APP_REGISTRY_URL_STAGE;
|
121
|
+
|
122
|
+
export const getExtensionManagerBaseUrl = (
|
123
|
+
environment: "prod" | "stage" | undefined,
|
124
|
+
extensionManager: string | null
|
125
|
+
): string =>
|
126
|
+
environment === "prod"
|
127
|
+
? EXTENSION_MANAGER_URL_PROD
|
128
|
+
: extensionManager ?? EXTENSION_MANAGER_URL_STAGE;
|
129
|
+
|
130
|
+
/**
|
131
|
+
* Extracts programId and envId from the repo value
|
132
|
+
* @param repo - the repo value
|
133
|
+
* @returns object with programId and envId
|
134
|
+
* @ignore
|
135
|
+
*/
|
136
|
+
export function extractProgramIdEnvId(repo: string): {
|
137
|
+
programId: string;
|
138
|
+
envId: string;
|
139
|
+
} {
|
140
|
+
const regex: RegExp = /p(\d+)-e(\d+)/;
|
141
|
+
const match: RegExpMatchArray | null = regex.exec(repo);
|
142
|
+
if (!match) {
|
143
|
+
throw new Error("Error parsing a repo value");
|
144
|
+
}
|
145
|
+
|
146
|
+
return {
|
147
|
+
programId: match[1],
|
148
|
+
envId: match[2],
|
149
|
+
};
|
150
|
+
}
|
151
|
+
|
152
|
+
/**
|
153
|
+
* Builds the URL for fetching extensions from the Extension Manager service
|
154
|
+
* @param config - the Extension Manager configuration
|
155
|
+
* @returns the URL for fetching extensions
|
156
|
+
* @ignore
|
157
|
+
*/
|
158
|
+
export function buildExtensionManagerUrl(
|
159
|
+
config: ExtensionManagerConfig
|
160
|
+
): string {
|
161
|
+
const scope = config.scope
|
162
|
+
? Object.fromEntries(
|
163
|
+
Object.entries(config.scope).map(([k, v]) => [`scope.${k}`, v])
|
164
|
+
)
|
165
|
+
: {};
|
166
|
+
const extensionPoints: string = `${config.service}/${config.extensionPoint}/${config.version}`;
|
167
|
+
const queryParams = new URLSearchParams({
|
168
|
+
...scope,
|
169
|
+
extensionPoints,
|
170
|
+
});
|
171
|
+
|
172
|
+
return `${config.baseUrl}/v2/extensions?${queryParams.toString()}`;
|
173
|
+
}
|
174
|
+
|
175
|
+
/**
|
176
|
+
* @ignore
|
177
|
+
*/
|
178
|
+
export async function fetchExtensionsFromExtensionManager(
|
179
|
+
config: ExtensionManagerConfig
|
180
|
+
): Promise<ExtensionManagerExtension[]> {
|
181
|
+
const resp: Response = await fetch(buildExtensionManagerUrl(config), {
|
182
|
+
headers: {
|
183
|
+
Authorization: `Bearer ${config.auth.imsToken}`,
|
184
|
+
"x-api-key": config.apiKey,
|
185
|
+
"x-org-id": config.imsOrg,
|
186
|
+
},
|
187
|
+
});
|
188
|
+
|
189
|
+
if (resp.status !== 200) {
|
190
|
+
throw new Error(
|
191
|
+
`Extension Manager returned non-200 response (${
|
192
|
+
resp.status
|
193
|
+
}): ${await resp.text()}`
|
194
|
+
);
|
195
|
+
}
|
196
|
+
|
197
|
+
return resp.json();
|
198
|
+
}
|
199
|
+
|
200
|
+
/**
|
201
|
+
* Takes an array of extensions from the App Registry, an array of extensions from the Extension Manager, and
|
202
|
+
* merges them into a list of Extensions. If an extension is disabled in the Extension Manager, it is removed from
|
203
|
+
* the list.
|
204
|
+
* Extension list from the App Registry is used as a base.
|
205
|
+
* @ignore
|
206
|
+
*/
|
207
|
+
export function mergeExtensions(
|
208
|
+
appRegistryExtensions: InstalledExtensions,
|
209
|
+
extensionManagerExtensions: ExtensionManagerExtension[],
|
210
|
+
extensionPointId: ExtensionPointId
|
211
|
+
): InstalledExtensions {
|
212
|
+
const mergedExtensions: InstalledExtensions = Object.assign(
|
213
|
+
appRegistryExtensions,
|
214
|
+
{}
|
215
|
+
);
|
216
|
+
extensionManagerExtensions.forEach((extension: ExtensionManagerExtension) => {
|
217
|
+
if (extension.disabled) {
|
218
|
+
// remove disabled extensions
|
219
|
+
delete mergedExtensions[extension.name];
|
220
|
+
} else {
|
221
|
+
const extPoint: ExtensionPoint | undefined =
|
222
|
+
extension.extensionPoints.find(
|
223
|
+
(_extensionPoint: ExtensionPoint) =>
|
224
|
+
_extensionPoint.extensionPoint ===
|
225
|
+
`${extensionPointId.service}/${extensionPointId.name}/${extensionPointId.version}`
|
226
|
+
);
|
227
|
+
if (extPoint) {
|
228
|
+
// add a new extension record or replace the existing one by an extension record from Extension Manager
|
229
|
+
// extension points are useful for filtering out extensions
|
230
|
+
mergedExtensions[extension.name] = {
|
231
|
+
id: extension.name,
|
232
|
+
url: extPoint.url,
|
233
|
+
configuration: extension.configuration,
|
234
|
+
extensionPoints: extension.extensionPoints.map(
|
235
|
+
(point) => point.extensionPoint
|
236
|
+
),
|
237
|
+
};
|
238
|
+
} else {
|
239
|
+
//this should never happen because we query Extension Manager service for our specific extension point
|
240
|
+
console.warn(
|
241
|
+
`Extension point ${extensionPointId.service}/${extensionPointId.name}/${extensionPointId.version} not found for extension ${extension.name}`
|
242
|
+
);
|
243
|
+
}
|
244
|
+
}
|
245
|
+
});
|
246
|
+
|
247
|
+
return mergedExtensions;
|
248
|
+
}
|
249
|
+
|
250
|
+
async function getExtensionManagerExtensions(
|
251
|
+
discoveryConfig: DiscoveryConfig,
|
252
|
+
authConfig: AuthConfig,
|
253
|
+
providerConfig: ExtensionProviderConfig,
|
254
|
+
extensionPointId: ExtensionPointId
|
255
|
+
): Promise<InstalledExtensions> {
|
256
|
+
const config = {
|
257
|
+
apiKey: authConfig.apiKey,
|
258
|
+
auth: {
|
259
|
+
schema: "Bearer",
|
260
|
+
imsToken: authConfig.imsToken,
|
261
|
+
},
|
262
|
+
service: extensionPointId.service,
|
263
|
+
extensionPoint: extensionPointId.name,
|
264
|
+
version: extensionPointId.version,
|
265
|
+
imsOrg: authConfig.imsOrg,
|
266
|
+
scope: discoveryConfig.scope,
|
267
|
+
};
|
268
|
+
|
269
|
+
const appRegistryConfig = {
|
270
|
+
...config,
|
271
|
+
baseUrl: getExtensionRegistryBaseUrl(
|
272
|
+
discoveryConfig.experienceShellEnvironment,
|
273
|
+
providerConfig.appRegistryUrl
|
274
|
+
),
|
275
|
+
} as ExtensionManagerConfig;
|
276
|
+
const appRegistryExtensionsProvider: ExtensionsProvider =
|
277
|
+
createExtensionRegistryAsObjectsProvider(appRegistryConfig);
|
278
|
+
|
279
|
+
const extensionManagerConfiguration = {
|
280
|
+
...config,
|
281
|
+
baseUrl: getExtensionManagerBaseUrl(
|
282
|
+
discoveryConfig.experienceShellEnvironment,
|
283
|
+
providerConfig.extensionManagerUrl
|
284
|
+
),
|
285
|
+
} as ExtensionManagerConfig;
|
286
|
+
const [appRegistryExtensions, extensionManagerExtensions] = await Promise.all(
|
287
|
+
[
|
288
|
+
appRegistryExtensionsProvider(),
|
289
|
+
providerConfig.disableExtensionManager
|
290
|
+
? []
|
291
|
+
: fetchExtensionsFromExtensionManager(extensionManagerConfiguration),
|
292
|
+
]
|
293
|
+
);
|
294
|
+
|
295
|
+
if (providerConfig.disableExtensionManager) {
|
296
|
+
return appRegistryExtensions;
|
297
|
+
} else {
|
298
|
+
return mergeExtensions(
|
299
|
+
appRegistryExtensions,
|
300
|
+
extensionManagerExtensions,
|
301
|
+
extensionPointId
|
302
|
+
);
|
303
|
+
}
|
304
|
+
}
|
305
|
+
|
306
|
+
/**
|
307
|
+
* Creates an extension manager extension provider
|
308
|
+
* @ignore
|
309
|
+
*/
|
310
|
+
export function createExtensionManagerExtensionsProvider(
|
311
|
+
discoveryConfig: DiscoveryConfig,
|
312
|
+
authConfig: AuthConfig,
|
313
|
+
providerConfig: ExtensionProviderConfig,
|
314
|
+
extensionPointId: ExtensionPointId
|
315
|
+
): ExtensionsProvider {
|
316
|
+
return () => {
|
317
|
+
return getExtensionManagerExtensions(
|
318
|
+
discoveryConfig,
|
319
|
+
authConfig,
|
320
|
+
providerConfig,
|
321
|
+
extensionPointId
|
322
|
+
);
|
323
|
+
};
|
324
|
+
}
|
@@ -0,0 +1,119 @@
|
|
1
|
+
/*
|
2
|
+
Copyright 2022 Adobe. All rights reserved.
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
10
|
+
governing permissions and limitations under the License.
|
11
|
+
*/
|
12
|
+
|
13
|
+
import {
|
14
|
+
extractExtUrlParams,
|
15
|
+
generateExtensionId,
|
16
|
+
createUrlExtensionsProvider,
|
17
|
+
} from "./UrlExtensionProvider";
|
18
|
+
import { ExtensionPointId } from "./ExtensionManagerProvider";
|
19
|
+
describe("extractExtUrlParams", () => {
|
20
|
+
it("should return an empty object when no query string is provided", () => {
|
21
|
+
expect(extractExtUrlParams(undefined)).toEqual({});
|
22
|
+
});
|
23
|
+
|
24
|
+
it("should return an empty object when the query string does not contain any valid extension params", () => {
|
25
|
+
expect(extractExtUrlParams("foo=bar&baz=qux")).toEqual({});
|
26
|
+
});
|
27
|
+
|
28
|
+
it("should extract valid extension params", () => {
|
29
|
+
const queryString =
|
30
|
+
"ext=foo&ext.service1.name1.version1=http://example.com";
|
31
|
+
const expectedParams = {
|
32
|
+
ext: "foo",
|
33
|
+
"ext.service1.name1.version1": "http://example.com",
|
34
|
+
};
|
35
|
+
expect(extractExtUrlParams(queryString)).toEqual(expectedParams);
|
36
|
+
});
|
37
|
+
|
38
|
+
it('should only include params with the "ext" prefix', () => {
|
39
|
+
const queryString =
|
40
|
+
"ext=foo&other=bar&ext.service1.name1.version1=http://example.com";
|
41
|
+
const expectedParams = {
|
42
|
+
ext: "foo",
|
43
|
+
"ext.service1.name1.version1": "http://example.com",
|
44
|
+
};
|
45
|
+
expect(extractExtUrlParams(queryString)).toEqual(expectedParams);
|
46
|
+
});
|
47
|
+
});
|
48
|
+
|
49
|
+
describe("generateExtensionId", () => {
|
50
|
+
it("should replace non-word characters with underscores", () => {
|
51
|
+
const url = "http://example.com/some/path";
|
52
|
+
expect(generateExtensionId(url)).toBe("http___example_com_some_path");
|
53
|
+
});
|
54
|
+
|
55
|
+
it("should return the same ID when there are no non-word characters", () => {
|
56
|
+
const url = "extension_1";
|
57
|
+
expect(generateExtensionId(url)).toBe("extension_1");
|
58
|
+
});
|
59
|
+
});
|
60
|
+
|
61
|
+
describe("createUrlExtensionsProvider", () => {
|
62
|
+
const mockExtensionPointId: ExtensionPointId = {
|
63
|
+
service: "service1",
|
64
|
+
name: "name1",
|
65
|
+
version: "version1",
|
66
|
+
};
|
67
|
+
|
68
|
+
it("should return an ExtensionsProvider that provides installed extensions", async () => {
|
69
|
+
const queryString =
|
70
|
+
"ext=foo&ext.service1/name1/version1=http://example2.com";
|
71
|
+
const provider = createUrlExtensionsProvider(
|
72
|
+
mockExtensionPointId,
|
73
|
+
queryString
|
74
|
+
);
|
75
|
+
|
76
|
+
const extensions = await provider();
|
77
|
+
expect(Object.keys(extensions)).toHaveLength(2);
|
78
|
+
expect(extensions).toHaveProperty("foo");
|
79
|
+
expect(extensions).toHaveProperty("http___example2_com");
|
80
|
+
expect(extensions["http___example2_com"]).toHaveProperty(
|
81
|
+
"url",
|
82
|
+
"http://example2.com"
|
83
|
+
);
|
84
|
+
});
|
85
|
+
|
86
|
+
it("should return an empty object if no valid extensions are found in the query string", async () => {
|
87
|
+
const queryString = "foo=bar&baz=qux";
|
88
|
+
const provider = createUrlExtensionsProvider(
|
89
|
+
mockExtensionPointId,
|
90
|
+
queryString
|
91
|
+
);
|
92
|
+
|
93
|
+
const extensions = await provider();
|
94
|
+
expect(extensions).toEqual({});
|
95
|
+
});
|
96
|
+
|
97
|
+
it("should filter extensions by the correct extension point", async () => {
|
98
|
+
const queryString =
|
99
|
+
"ext.service1/name1/version1=http://example1.com&ext.service2/name2/version2=https://www.test.";
|
100
|
+
const provider = createUrlExtensionsProvider(
|
101
|
+
mockExtensionPointId,
|
102
|
+
queryString
|
103
|
+
);
|
104
|
+
|
105
|
+
const extensions = await provider();
|
106
|
+
expect(extensions).toHaveProperty("http___example1_com");
|
107
|
+
});
|
108
|
+
|
109
|
+
it("should return an empty object when the query string does not match the expected extension point", async () => {
|
110
|
+
const queryString = "ext.service2.name2.version2=http://example1.com";
|
111
|
+
const provider = createUrlExtensionsProvider(
|
112
|
+
mockExtensionPointId,
|
113
|
+
queryString
|
114
|
+
);
|
115
|
+
|
116
|
+
const extensions = await provider();
|
117
|
+
expect(extensions).toEqual({});
|
118
|
+
});
|
119
|
+
});
|
@@ -0,0 +1,93 @@
|
|
1
|
+
/*************************************************************************
|
2
|
+
* ADOBE CONFIDENTIAL
|
3
|
+
* ___________________
|
4
|
+
*
|
5
|
+
* Copyright 2024 Adobe
|
6
|
+
* All Rights Reserved.
|
7
|
+
*
|
8
|
+
* NOTICE: All information contained herein is, and remains
|
9
|
+
* the property of Adobe and its suppliers, if any. The intellectual
|
10
|
+
* and technical concepts contained herein are proprietary to Adobe
|
11
|
+
* and its suppliers and are protected by all applicable intellectual
|
12
|
+
* property laws, including trade secret and copyright laws.
|
13
|
+
* Dissemination of this information or reproduction of this material
|
14
|
+
* is strictly forbidden unless prior written permission is obtained
|
15
|
+
* from Adobe.
|
16
|
+
**************************************************************************/
|
17
|
+
import { ExtensionsProvider, InstalledExtensions } from "@adobe/uix-host";
|
18
|
+
import { Extension } from "@adobe/uix-core";
|
19
|
+
import { ExtensionPointId } from "./ExtensionManagerProvider";
|
20
|
+
|
21
|
+
const EXT_PARAM_PREFIX = "ext";
|
22
|
+
|
23
|
+
export interface ExtUrlParams {
|
24
|
+
[key: string]: string;
|
25
|
+
}
|
26
|
+
|
27
|
+
/**
|
28
|
+
* Extracts extension URLs from the query string
|
29
|
+
* @ignore
|
30
|
+
*/
|
31
|
+
export function extractExtUrlParams(
|
32
|
+
queryString: string | undefined
|
33
|
+
): ExtUrlParams {
|
34
|
+
if (!queryString) {
|
35
|
+
return {};
|
36
|
+
}
|
37
|
+
const params: URLSearchParams = new URLSearchParams(queryString);
|
38
|
+
return Array.from(params.entries()).reduce((extParams, [key, value]) => {
|
39
|
+
if (key === EXT_PARAM_PREFIX || key.startsWith(`${EXT_PARAM_PREFIX}.`)) {
|
40
|
+
extParams[key] = value;
|
41
|
+
}
|
42
|
+
return extParams;
|
43
|
+
}, {} as ExtUrlParams);
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
* Generates an extension ID from the extension URL
|
48
|
+
* @ignore
|
49
|
+
*/
|
50
|
+
export function generateExtensionId(extensionUrl: string): string {
|
51
|
+
return extensionUrl.replace(/\W/g, "_");
|
52
|
+
}
|
53
|
+
|
54
|
+
/**
|
55
|
+
* Creates an ExtensionsProvider that provides extensions from the URL
|
56
|
+
* @ignore
|
57
|
+
*/
|
58
|
+
export function createUrlExtensionsProvider(
|
59
|
+
extensionPointId: ExtensionPointId,
|
60
|
+
queryString: string | undefined
|
61
|
+
): ExtensionsProvider {
|
62
|
+
const extUrlParams: ExtUrlParams = extractExtUrlParams(queryString);
|
63
|
+
|
64
|
+
const extensionUrls: string[] = Object.keys(extUrlParams)
|
65
|
+
.filter(
|
66
|
+
(extParam) =>
|
67
|
+
extParam === EXT_PARAM_PREFIX ||
|
68
|
+
extParam ===
|
69
|
+
`${EXT_PARAM_PREFIX}.${extensionPointId.service}/${extensionPointId.name}/${extensionPointId.version}`
|
70
|
+
)
|
71
|
+
.flatMap((extParam) => {
|
72
|
+
const paramValue = extUrlParams[extParam];
|
73
|
+
// If it's a single value, return it in an array. If it's already an array, return it as is.
|
74
|
+
return Array.isArray(paramValue) ? paramValue : [paramValue];
|
75
|
+
});
|
76
|
+
|
77
|
+
const installedExtensions: InstalledExtensions = extensionUrls
|
78
|
+
.map((extensionUrl: string) => {
|
79
|
+
return {
|
80
|
+
id: generateExtensionId(extensionUrl),
|
81
|
+
url: extensionUrl,
|
82
|
+
extensionPoints: [
|
83
|
+
`${extensionPointId.service}/${extensionPointId.name}/${extensionPointId.version}`,
|
84
|
+
],
|
85
|
+
} as Extension;
|
86
|
+
})
|
87
|
+
.reduce((acc: InstalledExtensions, extension: Extension) => {
|
88
|
+
acc[extension.id] = extension;
|
89
|
+
return acc;
|
90
|
+
}, {} as InstalledExtensions);
|
91
|
+
|
92
|
+
return async () => installedExtensions;
|
93
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
Copyright 2022 Adobe. All rights reserved.
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
10
|
+
governing permissions and limitations under the License.
|
11
|
+
*/
|
12
|
+
|
13
|
+
export * from "./ExtensibleWrapper";
|
14
|
+
export * from "./UrlExtensionProvider";
|
15
|
+
export * from "./ExtensionManagerProvider";
|
package/src/components/index.ts
CHANGED
@@ -157,9 +157,6 @@ export function useExtensions<
|
|
157
157
|
allExtensionPoints
|
158
158
|
)
|
159
159
|
) {
|
160
|
-
if (provides) {
|
161
|
-
guest.provide(provides);
|
162
|
-
}
|
163
160
|
newExtensions.push(guest as unknown as TypedGuestConnection<Incoming>);
|
164
161
|
}
|
165
162
|
}
|
@@ -176,6 +173,15 @@ export function useExtensions<
|
|
176
173
|
);
|
177
174
|
|
178
175
|
const [extensions, setExtensions] = useState(() => getExtensions());
|
176
|
+
|
177
|
+
useEffect(() => {
|
178
|
+
for (const guest of extensions) {
|
179
|
+
if (provides) {
|
180
|
+
guest.provide(provides);
|
181
|
+
}
|
182
|
+
}
|
183
|
+
}, [provides, extensions]);
|
184
|
+
|
179
185
|
useEffect(() => {
|
180
186
|
return subscribe(() => {
|
181
187
|
setExtensions(getExtensions());
|