@servicetitan/web-components 28.1.1 → 28.3.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/common.d.ts +10 -0
- package/dist/common.d.ts.map +1 -1
- package/dist/common.js +1 -0
- package/dist/common.js.map +1 -1
- package/dist/get-value-for-environment.d.ts +4 -0
- package/dist/get-value-for-environment.d.ts.map +1 -0
- package/dist/get-value-for-environment.js +26 -0
- package/dist/get-value-for-environment.js.map +1 -0
- package/dist/globals.d.ts +4 -3
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js +4 -2
- package/dist/globals.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +39 -8
- package/dist/loader.js.map +1 -1
- package/dist/register.d.ts.map +1 -1
- package/dist/register.js +21 -4
- package/dist/register.js.map +1 -1
- package/dist/utils/get-bundle-info.d.ts +15 -3
- package/dist/utils/get-bundle-info.d.ts.map +1 -1
- package/dist/utils/get-bundle-info.js +4 -1
- package/dist/utils/get-bundle-info.js.map +1 -1
- package/dist/utils/get-element-name.d.ts +1 -1
- package/dist/utils/get-element-name.d.ts.map +1 -1
- package/dist/utils/get-ld-service-token.d.ts +4 -0
- package/dist/utils/get-ld-service-token.d.ts.map +1 -0
- package/dist/utils/get-ld-service-token.js +11 -0
- package/dist/utils/get-ld-service-token.js.map +1 -0
- package/dist/utils/get-mfe-has-compatible-launchdarkly-version.d.ts +11 -0
- package/dist/utils/get-mfe-has-compatible-launchdarkly-version.d.ts.map +1 -0
- package/dist/utils/get-mfe-has-compatible-launchdarkly-version.js +16 -0
- package/dist/utils/get-mfe-has-compatible-launchdarkly-version.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/validate-bundle-info.d.ts +7 -4
- package/dist/utils/validate-bundle-info.d.ts.map +1 -1
- package/dist/utils/validate-bundle-info.js +30 -12
- package/dist/utils/validate-bundle-info.js.map +1 -1
- package/dist/utils/web-component.d.ts +4 -1
- package/dist/utils/web-component.d.ts.map +1 -1
- package/dist/utils/web-component.js +15 -2
- package/dist/utils/web-component.js.map +1 -1
- package/package.json +17 -12
- package/src/__tests__/get-value-for-environment.test.ts +77 -0
- package/src/__tests__/globals.test.ts +4 -8
- package/src/__tests__/loader.test.tsx +152 -13
- package/src/__tests__/register-legacy-root.test.tsx +2 -5
- package/src/common.ts +10 -0
- package/src/get-value-for-environment.ts +42 -0
- package/src/globals.ts +12 -3
- package/src/index.ts +3 -2
- package/src/loader.tsx +46 -9
- package/src/register.tsx +21 -8
- package/src/utils/__tests__/get-mfe-has-compatible-launchdarkly-version.test.ts +54 -0
- package/src/utils/get-bundle-info.ts +27 -10
- package/src/utils/get-ld-service-token.ts +13 -0
- package/src/utils/get-mfe-has-compatible-launchdarkly-version.ts +20 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/validate-bundle-info.ts +40 -19
- package/src/utils/web-component.tsx +23 -2
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type Environment = 'dev' | 'go' | 'next' | 'qa' | 'stage' | 'test';
|
|
2
|
+
export type EnvironmentValues = Partial<Record<Environment, string>>;
|
|
3
|
+
|
|
4
|
+
export function getValueForEnvironment(
|
|
5
|
+
values: EnvironmentValues,
|
|
6
|
+
defaultEnvironment: Environment = 'qa',
|
|
7
|
+
hostname: string = window.location.hostname
|
|
8
|
+
) {
|
|
9
|
+
const keys = Object.keys(values) as (keyof typeof values)[];
|
|
10
|
+
const key = detectEnvironment(keys, defaultEnvironment, hostname);
|
|
11
|
+
return values[key];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function detectEnvironment(
|
|
15
|
+
keys: Environment[],
|
|
16
|
+
defaultEnvironment: Environment,
|
|
17
|
+
hostname: string
|
|
18
|
+
): Environment {
|
|
19
|
+
if (isTestEnvironment()) {
|
|
20
|
+
return 'test';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (isLocalHost(hostname)) {
|
|
24
|
+
return 'dev';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for (const key of keys) {
|
|
28
|
+
if (hostname.indexOf(`${key}.servicetitan.com`) >= 0) {
|
|
29
|
+
return key;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return defaultEnvironment;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isLocalHost(hostname: string) {
|
|
37
|
+
return ['localhost', '127.0.0.1'].some(host => hostname.indexOf(host) >= 0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isTestEnvironment() {
|
|
41
|
+
return process.env.NODE_ENV === 'test';
|
|
42
|
+
}
|
package/src/globals.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/naming-convention */
|
|
2
|
-
import { ExposedDependencies } from './common';
|
|
2
|
+
import { ExposedDependencies, ExposedInstanceDependencies } from './common';
|
|
3
3
|
|
|
4
4
|
declare global {
|
|
5
5
|
export const EXPOSED_DEPENDENCIES: ExposedDependencies | undefined;
|
|
6
|
+
export const EXPOSED_INSTANCE_DEPENDENCIES: ExposedInstanceDependencies | undefined;
|
|
6
7
|
export const WEB_COMPONENT_NAME: string | undefined;
|
|
7
8
|
}
|
|
8
9
|
|
|
@@ -17,9 +18,17 @@ function get<T>(fetch: () => T) {
|
|
|
17
18
|
const _EXPOSED_DEPENDENCIES = get(() => {
|
|
18
19
|
return EXPOSED_DEPENDENCIES;
|
|
19
20
|
});
|
|
20
|
-
export { _EXPOSED_DEPENDENCIES as EXPOSED_DEPENDENCIES };
|
|
21
21
|
|
|
22
22
|
const _WEB_COMPONENT_NAME = get(() => {
|
|
23
23
|
return WEB_COMPONENT_NAME;
|
|
24
24
|
});
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
const _EXPOSED_INSTANCE_DEPENDENCIES = get(() => {
|
|
27
|
+
return EXPOSED_INSTANCE_DEPENDENCIES;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
_EXPOSED_DEPENDENCIES as EXPOSED_DEPENDENCIES,
|
|
32
|
+
_EXPOSED_INSTANCE_DEPENDENCIES as EXPOSED_INSTANCE_DEPENDENCIES,
|
|
33
|
+
_WEB_COMPONENT_NAME as WEB_COMPONENT_NAME,
|
|
34
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './common';
|
|
2
|
+
export * from './contexts';
|
|
2
3
|
export * from './event-bus';
|
|
4
|
+
export * from './get-value-for-environment';
|
|
3
5
|
export * from './history-manager';
|
|
4
|
-
export * from './contexts';
|
|
5
|
-
export * from './register';
|
|
6
6
|
export * from './loader';
|
|
7
|
+
export * from './register';
|
package/src/loader.tsx
CHANGED
|
@@ -9,23 +9,27 @@ import {
|
|
|
9
9
|
useMemo,
|
|
10
10
|
} from 'react';
|
|
11
11
|
import { ErrorBoundary } from '@servicetitan/error-boundary';
|
|
12
|
+
import { type LDService } from '@servicetitan/launchdarkly-service';
|
|
12
13
|
import { Log } from '@servicetitan/log-service';
|
|
13
14
|
import { useOptionalDependencies } from '@servicetitan/react-ioc';
|
|
14
15
|
|
|
15
16
|
import {
|
|
16
17
|
EXPOSED_DEPENDENCIES_TOKEN,
|
|
18
|
+
EXPOSED_INSTANCE_DEPENDENCIES_TOKEN,
|
|
17
19
|
ExposedDependencies,
|
|
20
|
+
ExposedInstanceDependencies,
|
|
18
21
|
GetJsonError,
|
|
19
22
|
useCancelation,
|
|
20
23
|
} from './common';
|
|
21
24
|
import { MFEContextData } from './contexts/mfe-data-context';
|
|
22
25
|
import { Delayed } from './delayed';
|
|
23
26
|
import { EventBus, EVENT_BUS_TOKEN, useEventBus } from './event-bus';
|
|
24
|
-
import { EXPOSED_DEPENDENCIES } from './globals';
|
|
27
|
+
import { EXPOSED_DEPENDENCIES, EXPOSED_INSTANCE_DEPENDENCIES } from './globals';
|
|
25
28
|
import {
|
|
26
29
|
BundleInfo,
|
|
27
30
|
TtlCache,
|
|
28
31
|
getBundleInfo,
|
|
32
|
+
getLDServiceToken,
|
|
29
33
|
validateBundleInfo,
|
|
30
34
|
loadScripts,
|
|
31
35
|
webComponent,
|
|
@@ -61,6 +65,10 @@ export const Loader = forwardRef<LoaderRef, LoaderProps<{}>>((props, ref) => {
|
|
|
61
65
|
const bus = useEventBus();
|
|
62
66
|
const [logService, exposedDependencies = EXPOSED_DEPENDENCIES, eventBus = bus] =
|
|
63
67
|
useOptionalDependencies(Log, EXPOSED_DEPENDENCIES_TOKEN, EVENT_BUS_TOKEN);
|
|
68
|
+
let [exposedInstanceDependencies = EXPOSED_INSTANCE_DEPENDENCIES] = useOptionalDependencies(
|
|
69
|
+
EXPOSED_INSTANCE_DEPENDENCIES_TOKEN
|
|
70
|
+
);
|
|
71
|
+
const [ldService] = useOptionalDependencies(getLDServiceToken() ?? Symbol());
|
|
64
72
|
const cancelation = useCancelation();
|
|
65
73
|
|
|
66
74
|
const LoadingFallback = useMemo(() => {
|
|
@@ -73,6 +81,8 @@ export const Loader = forwardRef<LoaderRef, LoaderProps<{}>>((props, ref) => {
|
|
|
73
81
|
return getFallback(props.errorFallback, message);
|
|
74
82
|
}, [props.errorFallback, src]);
|
|
75
83
|
|
|
84
|
+
exposedInstanceDependencies = ensureLDService(exposedInstanceDependencies, ldService);
|
|
85
|
+
|
|
76
86
|
const Loader = useMemo(
|
|
77
87
|
() =>
|
|
78
88
|
lazyLoader({
|
|
@@ -80,6 +90,7 @@ export const Loader = forwardRef<LoaderRef, LoaderProps<{}>>((props, ref) => {
|
|
|
80
90
|
cache,
|
|
81
91
|
cancelation,
|
|
82
92
|
exposedDependencies,
|
|
93
|
+
exposedInstanceDependencies,
|
|
83
94
|
eventBus,
|
|
84
95
|
fallbackSrc,
|
|
85
96
|
logService,
|
|
@@ -92,6 +103,7 @@ export const Loader = forwardRef<LoaderRef, LoaderProps<{}>>((props, ref) => {
|
|
|
92
103
|
cache,
|
|
93
104
|
cancelation,
|
|
94
105
|
exposedDependencies,
|
|
106
|
+
exposedInstanceDependencies,
|
|
95
107
|
eventBus,
|
|
96
108
|
logService,
|
|
97
109
|
onRequestError,
|
|
@@ -119,6 +131,7 @@ export const Loader = forwardRef<LoaderRef, LoaderProps<{}>>((props, ref) => {
|
|
|
119
131
|
type LazyLoaderProps = LoaderProps<any> & {
|
|
120
132
|
cancelation: AbortController;
|
|
121
133
|
exposedDependencies?: ExposedDependencies;
|
|
134
|
+
exposedInstanceDependencies?: ExposedInstanceDependencies;
|
|
122
135
|
eventBus: EventBus;
|
|
123
136
|
logService?: Log;
|
|
124
137
|
};
|
|
@@ -150,6 +163,7 @@ function lazyWebComponent(props: LazyLoaderProps) {
|
|
|
150
163
|
basename,
|
|
151
164
|
cancelation,
|
|
152
165
|
exposedDependencies,
|
|
166
|
+
exposedInstanceDependencies,
|
|
153
167
|
eventBus,
|
|
154
168
|
fallbackSrc,
|
|
155
169
|
logService,
|
|
@@ -160,18 +174,19 @@ function lazyWebComponent(props: LazyLoaderProps) {
|
|
|
160
174
|
|
|
161
175
|
let { cache } = props;
|
|
162
176
|
|
|
163
|
-
return lazy<(
|
|
177
|
+
return lazy<(_: LoaderDataProps) => JSX.Element | null>(async () => {
|
|
164
178
|
let bundleInfo: BundleInfo | undefined = cache ? bundleCache.get(src) : undefined;
|
|
165
179
|
if (!bundleInfo) {
|
|
166
180
|
try {
|
|
167
|
-
bundleInfo = await getBundleInfo(
|
|
168
|
-
src,
|
|
169
|
-
fallbackSrc,
|
|
181
|
+
bundleInfo = await getBundleInfo({
|
|
182
|
+
mainPackageUrl: src,
|
|
183
|
+
fallbackPackageUrl: fallbackSrc,
|
|
170
184
|
exposedDependencies,
|
|
171
|
-
|
|
185
|
+
exposedInstanceDependencies,
|
|
186
|
+
signal: cancelation.signal,
|
|
172
187
|
retries,
|
|
173
|
-
onRequestError
|
|
174
|
-
);
|
|
188
|
+
onRequestError,
|
|
189
|
+
});
|
|
175
190
|
} catch (e) {
|
|
176
191
|
if (cancelation.signal.aborted) {
|
|
177
192
|
return {
|
|
@@ -190,17 +205,28 @@ function lazyWebComponent(props: LazyLoaderProps) {
|
|
|
190
205
|
bundleCache.set(src, bundleInfo, typeof cache === 'number' ? cache : undefined);
|
|
191
206
|
}
|
|
192
207
|
|
|
193
|
-
validateBundleInfo(bundleInfo, {
|
|
208
|
+
validateBundleInfo(bundleInfo, {
|
|
209
|
+
exposedDependencies,
|
|
210
|
+
exposedInstanceDependencies,
|
|
211
|
+
logService,
|
|
212
|
+
packageUrl: src,
|
|
213
|
+
});
|
|
194
214
|
}
|
|
195
215
|
|
|
196
216
|
await loadScripts(bundleInfo);
|
|
197
217
|
|
|
218
|
+
const ldService = bundleInfo.mfeHasCompatibleLaunchDarklyVersion
|
|
219
|
+
? exposedInstanceDependencies?.launchDarkly?.ldService
|
|
220
|
+
: undefined;
|
|
221
|
+
|
|
198
222
|
return {
|
|
199
223
|
default: webComponent({
|
|
200
224
|
basename,
|
|
201
225
|
bundleInfo,
|
|
202
226
|
eventBus,
|
|
203
227
|
exposedDependencies,
|
|
228
|
+
exposedInstanceDependencies,
|
|
229
|
+
ldService,
|
|
204
230
|
logService,
|
|
205
231
|
}),
|
|
206
232
|
};
|
|
@@ -220,5 +246,16 @@ function isLocalHost(source: string) {
|
|
|
220
246
|
return ['localhost', '127.0.0.1'].some(host => source.indexOf(host) >= 0);
|
|
221
247
|
}
|
|
222
248
|
|
|
249
|
+
function ensureLDService(
|
|
250
|
+
exposedInstanceDependencies?: ExposedInstanceDependencies,
|
|
251
|
+
ldService?: LDService
|
|
252
|
+
) {
|
|
253
|
+
const launchDarkly = exposedInstanceDependencies?.launchDarkly;
|
|
254
|
+
if (launchDarkly?.version && !launchDarkly.ldService && ldService) {
|
|
255
|
+
return { ...exposedInstanceDependencies, launchDarkly: { ...launchDarkly, ldService } };
|
|
256
|
+
}
|
|
257
|
+
return exposedInstanceDependencies;
|
|
258
|
+
}
|
|
259
|
+
|
|
223
260
|
const MINUTE = 1000 * 60;
|
|
224
261
|
const bundleCache = new TtlCache<BundleInfo>({ ttlMs: 15 * MINUTE });
|
package/src/register.tsx
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { FC, ComponentType, ReactElement, useEffect } from 'react';
|
|
2
|
+
import { type LDService } from '@servicetitan/launchdarkly-service';
|
|
2
3
|
import { Log } from '@servicetitan/log-service';
|
|
3
4
|
import { Provider } from '@servicetitan/react-ioc';
|
|
4
5
|
|
|
5
6
|
import {
|
|
6
|
-
ExposedDependencies,
|
|
7
|
-
EXPOSED_DEPENDENCIES_TOKEN,
|
|
8
|
-
Entries,
|
|
9
|
-
IWebComponent,
|
|
10
7
|
BASENAME_TOKEN,
|
|
8
|
+
EXPOSED_DEPENDENCIES_TOKEN,
|
|
9
|
+
EXPOSED_INSTANCE_DEPENDENCIES_TOKEN,
|
|
11
10
|
IS_WEB_COMPONENT_TOKEN,
|
|
12
11
|
RENDER_ROOT_TOKEN,
|
|
12
|
+
Entries,
|
|
13
|
+
ExposedDependencies,
|
|
14
|
+
ExposedInstanceDependencies,
|
|
15
|
+
IWebComponent,
|
|
13
16
|
} from './common';
|
|
14
17
|
import { MFEDataContext } from './contexts/mfe-data-context';
|
|
15
18
|
import { MFEMetadataContext } from './contexts/mfe-metadata-context';
|
|
@@ -17,7 +20,7 @@ import { EventBus, EVENT_BUS_TOKEN } from './event-bus';
|
|
|
17
20
|
import { WEB_COMPONENT_NAME } from './globals';
|
|
18
21
|
import { HistoryManager } from './history-manager';
|
|
19
22
|
import { render } from './render';
|
|
20
|
-
import { getElementName } from './utils';
|
|
23
|
+
import { getElementName, getLDServiceToken } from './utils';
|
|
21
24
|
|
|
22
25
|
function createLink(url: string, onLoad: () => void) {
|
|
23
26
|
const element = document.createElement('link');
|
|
@@ -50,9 +53,11 @@ export function register(Component: ComponentType, light: boolean, options?: Reg
|
|
|
50
53
|
elements: HTMLElement[];
|
|
51
54
|
} = { total: 0, loaded: 0, elements: [] };
|
|
52
55
|
|
|
56
|
+
private ldService?: LDService;
|
|
53
57
|
private logService?: Log;
|
|
54
58
|
private historyManager?: HistoryManager;
|
|
55
59
|
private exposedDependencies?: ExposedDependencies;
|
|
60
|
+
private exposedInstanceDependencies?: ExposedInstanceDependencies;
|
|
56
61
|
private eventBus?: EventBus;
|
|
57
62
|
private basename?: string;
|
|
58
63
|
private onReady?: () => void;
|
|
@@ -87,7 +92,7 @@ export function register(Component: ComponentType, light: boolean, options?: Reg
|
|
|
87
92
|
const portalElement = document.createElement('div');
|
|
88
93
|
portalElement.setAttribute(
|
|
89
94
|
'data-mfe-portal-name',
|
|
90
|
-
getElementName({ WebComponent: WEB_COMPONENT_NAME })
|
|
95
|
+
getElementName({ WebComponent: WEB_COMPONENT_NAME! })
|
|
91
96
|
);
|
|
92
97
|
this.portal = document.body.appendChild(portalElement).attachShadow({ mode: 'open' });
|
|
93
98
|
/*
|
|
@@ -246,9 +251,17 @@ export function register(Component: ComponentType, light: boolean, options?: Reg
|
|
|
246
251
|
provide: EXPOSED_DEPENDENCIES_TOKEN,
|
|
247
252
|
useValue: this.exposedDependencies,
|
|
248
253
|
},
|
|
249
|
-
{
|
|
254
|
+
{
|
|
255
|
+
provide: EXPOSED_INSTANCE_DEPENDENCIES_TOKEN,
|
|
256
|
+
useValue: this.exposedInstanceDependencies,
|
|
257
|
+
},
|
|
250
258
|
{ provide: BASENAME_TOKEN, useValue: this.basename },
|
|
251
|
-
|
|
259
|
+
{ provide: EVENT_BUS_TOKEN, useValue: this.eventBus },
|
|
260
|
+
{ provide: getLDServiceToken(), useValue: this.ldService },
|
|
261
|
+
].filter(
|
|
262
|
+
({ provide, useValue }) =>
|
|
263
|
+
useValue !== undefined && provide !== undefined
|
|
264
|
+
)}
|
|
252
265
|
>
|
|
253
266
|
<MFEDataContext.Provider value={{ ...mfeData }}>
|
|
254
267
|
<MFEMetadataContext.Provider value={metadata}>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ExposedInstanceDependencies } from '../../common';
|
|
2
|
+
import { Metadata } from '../get-bundle-info';
|
|
3
|
+
import { getMFEHasCompatibleLaunchDarklyVersion } from '../get-mfe-has-compatible-launchdarkly-version';
|
|
4
|
+
|
|
5
|
+
describe(`[web-components] ${getMFEHasCompatibleLaunchDarklyVersion.name}`, () => {
|
|
6
|
+
const version = '1.0.0';
|
|
7
|
+
let metadata: Pick<Metadata, 'bundledWith'>;
|
|
8
|
+
let exposedInstanceDependencies: ExposedInstanceDependencies;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
metadata = {
|
|
12
|
+
bundledWith: { 'launchdarkly-js-client-sdk': version },
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function itReturns(value: boolean | undefined) {
|
|
17
|
+
test(`return ${value}`, () => {
|
|
18
|
+
expect(subject()).toBe(value);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const subject = () =>
|
|
23
|
+
getMFEHasCompatibleLaunchDarklyVersion(metadata.bundledWith, exposedInstanceDependencies);
|
|
24
|
+
|
|
25
|
+
describe('when host and MFE use same LaunchDarkly version', () => {
|
|
26
|
+
beforeEach(() => (exposedInstanceDependencies = { launchDarkly: { version } }));
|
|
27
|
+
|
|
28
|
+
itReturns(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('when host uses different LaunchDarkly version', () => {
|
|
32
|
+
beforeEach(() => (exposedInstanceDependencies = { launchDarkly: { version: '1.0.1' } }));
|
|
33
|
+
|
|
34
|
+
itReturns(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('when host does not use LaunchDarkly', () => {
|
|
38
|
+
beforeEach(() => (exposedInstanceDependencies = {}));
|
|
39
|
+
|
|
40
|
+
itReturns(undefined);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('when MFE does not use LaunchDarkly', () => {
|
|
44
|
+
beforeEach(() => (metadata.bundledWith = {}));
|
|
45
|
+
|
|
46
|
+
itReturns(undefined);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('when metadata omits "bundledWith"', () => {
|
|
50
|
+
beforeEach(() => delete metadata.bundledWith);
|
|
51
|
+
|
|
52
|
+
itReturns(undefined);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import semver from 'semver';
|
|
2
2
|
import get from 'lodash.get';
|
|
3
3
|
|
|
4
|
-
import { ExposedDependencies, getJson, GetJsonError } from '../common';
|
|
4
|
+
import { ExposedDependencies, ExposedInstanceDependencies, getJson, GetJsonError } from '../common';
|
|
5
5
|
import { EntryPoints, getEntrypoints } from './get-entry-points';
|
|
6
6
|
import { getMFESupportsRerendering } from './get-mfe-supports-rerendering';
|
|
7
7
|
import { getVersionMismatches } from './get-version-mismatches';
|
|
8
8
|
import { isVersionedUrl } from './is-versioned-url';
|
|
9
|
+
import { getMFEHasCompatibleLaunchDarklyVersion } from './get-mfe-has-compatible-launchdarkly-version';
|
|
9
10
|
import { getMFEHasUniqueChunkNamespace } from './get-mfe-has-unique-chunk-namespace';
|
|
10
11
|
|
|
11
12
|
export interface Metadata {
|
|
@@ -18,14 +19,25 @@ export interface Metadata {
|
|
|
18
19
|
|
|
19
20
|
export type BundleInfo = Awaited<ReturnType<typeof getBundleInfo>>;
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
interface GetBundleInfoProps {
|
|
23
|
+
exposedDependencies?: ExposedDependencies;
|
|
24
|
+
exposedInstanceDependencies?: ExposedInstanceDependencies;
|
|
25
|
+
fallbackPackageUrl?: string;
|
|
26
|
+
mainPackageUrl: string;
|
|
27
|
+
onRequestError?: (error: GetJsonError) => void;
|
|
28
|
+
retries?: number;
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function getBundleInfo({
|
|
33
|
+
mainPackageUrl,
|
|
34
|
+
fallbackPackageUrl,
|
|
35
|
+
exposedDependencies = {},
|
|
36
|
+
exposedInstanceDependencies = {},
|
|
37
|
+
signal,
|
|
38
|
+
retries,
|
|
39
|
+
onRequestError,
|
|
40
|
+
}: GetBundleInfoProps) {
|
|
29
41
|
const { data: metadata, url: packageUrl } = await getMetadata({
|
|
30
42
|
fallbackPackageUrl,
|
|
31
43
|
mainPackageUrl,
|
|
@@ -81,9 +93,14 @@ export async function getBundleInfo(
|
|
|
81
93
|
);
|
|
82
94
|
|
|
83
95
|
return {
|
|
96
|
+
bundledWith,
|
|
84
97
|
disposer,
|
|
85
98
|
mfeSupportsRerendering: getMFESupportsRerendering(bundledWith, dependencies),
|
|
86
99
|
mfeHasUniqueChunkNamespace: getMFEHasUniqueChunkNamespace(bundledWith),
|
|
100
|
+
mfeHasCompatibleLaunchDarklyVersion: getMFEHasCompatibleLaunchDarklyVersion(
|
|
101
|
+
bundledWith,
|
|
102
|
+
exposedInstanceDependencies
|
|
103
|
+
),
|
|
87
104
|
mismatch: hasMismatch ? mismatch : undefined,
|
|
88
105
|
urls,
|
|
89
106
|
WebComponent: name,
|
|
@@ -109,7 +126,7 @@ async function getMetadata(props: {
|
|
|
109
126
|
throw e;
|
|
110
127
|
}
|
|
111
128
|
|
|
112
|
-
return getJson(`${fallbackPackageUrl}/dist/metadata.json`, {
|
|
129
|
+
return getJson<Metadata>(`${fallbackPackageUrl}/dist/metadata.json`, {
|
|
113
130
|
retries,
|
|
114
131
|
onRequestError,
|
|
115
132
|
cache: getCacheSetting(fallbackPackageUrl),
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type LDService } from '@servicetitan/launchdarkly-service';
|
|
2
|
+
import { SymbolToken } from '@servicetitan/react-ioc';
|
|
3
|
+
|
|
4
|
+
let token: SymbolToken<LDService>;
|
|
5
|
+
try {
|
|
6
|
+
token = require('@servicetitan/launchdarkly-service').LD_SERVICE_TOKEN;
|
|
7
|
+
} catch (e) {
|
|
8
|
+
// ignore
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getLDServiceToken() {
|
|
12
|
+
return token;
|
|
13
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ExposedInstanceDependencies } from '../common';
|
|
2
|
+
import { Metadata } from './get-bundle-info';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param bundledWith THe MFE's metadata.bundledWith.
|
|
6
|
+
* @param exposedInstanceDependencies The hosts's EXPOSED_INSTANCE_DEPENDENCIES, if any.
|
|
7
|
+
* @returns Returns true when host and MFE use compatible version of LaunchDarkly
|
|
8
|
+
* and returns false when host and MGE use incompatible version.
|
|
9
|
+
* Returns undefined when either the host or MFE doesn't expose LaunchDarkly.
|
|
10
|
+
*/
|
|
11
|
+
export function getMFEHasCompatibleLaunchDarklyVersion(
|
|
12
|
+
bundledWith: Metadata['bundledWith'],
|
|
13
|
+
exposedInstanceDependencies: ExposedInstanceDependencies
|
|
14
|
+
) {
|
|
15
|
+
const mfeVersion = bundledWith?.['launchdarkly-js-client-sdk'];
|
|
16
|
+
const hostVersion = exposedInstanceDependencies.launchDarkly?.version;
|
|
17
|
+
if (hostVersion && mfeVersion) {
|
|
18
|
+
return hostVersion === mfeVersion;
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './get-bundle-info';
|
|
2
2
|
export * from './get-element-name';
|
|
3
3
|
export * from './get-entry-points';
|
|
4
|
+
export * from './get-ld-service-token';
|
|
4
5
|
export * from './get-mfe-supports-rerendering';
|
|
5
6
|
export * from './get-version-mismatches';
|
|
6
7
|
export * from './is-versioned-url';
|
|
@@ -1,31 +1,52 @@
|
|
|
1
1
|
import { Log } from '@servicetitan/log-service';
|
|
2
|
-
import { ExposedDependencies } from '../common';
|
|
2
|
+
import { ExposedDependencies, ExposedInstanceDependencies } from '../common';
|
|
3
3
|
import { BundleInfo } from './get-bundle-info';
|
|
4
4
|
|
|
5
|
+
interface Context {
|
|
6
|
+
exposedDependencies?: ExposedDependencies;
|
|
7
|
+
exposedInstanceDependencies?: ExposedInstanceDependencies;
|
|
8
|
+
logService?: Log;
|
|
9
|
+
packageUrl: string;
|
|
10
|
+
}
|
|
5
11
|
export function validateBundleInfo(
|
|
6
|
-
{ mismatch }: BundleInfo,
|
|
7
|
-
{
|
|
8
|
-
exposedDependencies = {},
|
|
9
|
-
logService,
|
|
10
|
-
packageName,
|
|
11
|
-
}: { exposedDependencies?: ExposedDependencies; logService?: Log; packageName: string }
|
|
12
|
+
{ mismatch, mfeHasCompatibleLaunchDarklyVersion }: BundleInfo,
|
|
13
|
+
{ exposedDependencies = {}, exposedInstanceDependencies, logService, packageUrl }: Context
|
|
12
14
|
) {
|
|
13
|
-
if (!
|
|
15
|
+
if (!logService) {
|
|
14
16
|
return;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
if (mismatch) {
|
|
20
|
+
const data = { package: packageUrl, dependencies: JSON.stringify(mismatch) };
|
|
21
|
+
if (Object.keys(exposedDependencies).length === 0) {
|
|
22
|
+
logService.warning({
|
|
23
|
+
category: 'MicroFrontends.NoExposedDependencies',
|
|
24
|
+
message:
|
|
25
|
+
'Cannot use light bundle because the host is not exposing dependencies or the list of shared dependencies is empty.',
|
|
26
|
+
data,
|
|
27
|
+
});
|
|
28
|
+
} else {
|
|
29
|
+
logService.warning({
|
|
30
|
+
category: 'MicroFrontends.DependenciesMismatch',
|
|
31
|
+
message: 'Some of the package dependencies have incompatible versions.',
|
|
32
|
+
data,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Note, only checking false here because the value is undefined when the host
|
|
39
|
+
* or the MFE isn't using LaunchDarkly at all.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
if (mfeHasCompatibleLaunchDarklyVersion === false) {
|
|
43
|
+
const data = {
|
|
44
|
+
package: packageUrl,
|
|
45
|
+
hostVersion: exposedInstanceDependencies?.launchDarkly?.version ?? '',
|
|
46
|
+
};
|
|
26
47
|
logService.warning({
|
|
27
|
-
category: 'MicroFrontends.
|
|
28
|
-
message: '
|
|
48
|
+
category: 'MicroFrontends.LaunchDarklyMismatch',
|
|
49
|
+
message: 'LaunchDarkly versions are incompatible',
|
|
29
50
|
data,
|
|
30
51
|
});
|
|
31
52
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Fragment, useMemo, useState } from 'react';
|
|
2
2
|
import { Log } from '@servicetitan/log-service';
|
|
3
|
+
import { type LDService } from '@servicetitan/launchdarkly-service';
|
|
3
4
|
import { useOptionalDependencies } from '@servicetitan/react-ioc';
|
|
4
5
|
|
|
5
|
-
import { ExposedDependencies, IWebComponent } from '../common';
|
|
6
|
+
import { ExposedDependencies, ExposedInstanceDependencies, IWebComponent } from '../common';
|
|
6
7
|
import { MFEContextData } from '../contexts/mfe-data-context';
|
|
7
8
|
import { EventBus } from '../event-bus';
|
|
8
9
|
import { BundleInfo } from './get-bundle-info';
|
|
@@ -15,23 +16,32 @@ interface WebComponentProps {
|
|
|
15
16
|
bundleInfo: BundleInfo;
|
|
16
17
|
eventBus: EventBus;
|
|
17
18
|
exposedDependencies?: ExposedDependencies;
|
|
19
|
+
exposedInstanceDependencies?: ExposedInstanceDependencies;
|
|
20
|
+
ldService?: LDService;
|
|
18
21
|
logService?: Log;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
export function webComponent(props: WebComponentProps) {
|
|
22
|
-
const { basename, bundleInfo, eventBus, exposedDependencies, logService } = props;
|
|
25
|
+
const { basename, bundleInfo, eventBus, exposedDependencies, ldService, logService } = props;
|
|
23
26
|
const { WebComponent, disposer, mfeSupportsRerendering } = bundleInfo;
|
|
24
27
|
const elementName = getElementName(bundleInfo);
|
|
25
28
|
|
|
29
|
+
const exposedInstanceDependencies = ensureLDVersion(
|
|
30
|
+
props.exposedInstanceDependencies,
|
|
31
|
+
bundleInfo.bundledWith?.['launchdarkly-js-client-sdk']
|
|
32
|
+
);
|
|
33
|
+
|
|
26
34
|
return ({ LoadingFallback, ...props }: LoaderDataProps) => {
|
|
27
35
|
const [isLoading, setIsLoading] = useState(true);
|
|
28
36
|
const [historyManager] = useOptionalDependencies(HistoryManager);
|
|
29
37
|
|
|
30
38
|
const handleRef = (webComponent: IWebComponent | null) => {
|
|
31
39
|
webComponent?.provide({
|
|
40
|
+
ldService,
|
|
32
41
|
logService,
|
|
33
42
|
historyManager,
|
|
34
43
|
exposedDependencies,
|
|
44
|
+
exposedInstanceDependencies,
|
|
35
45
|
eventBus,
|
|
36
46
|
basename,
|
|
37
47
|
onReady: () => {
|
|
@@ -61,6 +71,7 @@ export function webComponent(props: WebComponentProps) {
|
|
|
61
71
|
{isLoading && <LoadingFallback />}
|
|
62
72
|
<WebComponent
|
|
63
73
|
key={key}
|
|
74
|
+
// @ts-expect-error because property ref does not exist on IntrinsicAttributes
|
|
64
75
|
ref={handleRef}
|
|
65
76
|
class={props.className}
|
|
66
77
|
{...(styleUrls && { 'style-urls': styleUrls })}
|
|
@@ -90,6 +101,16 @@ function composeDataAttributes(data: MFEContextData<{}> | undefined = {}) {
|
|
|
90
101
|
);
|
|
91
102
|
}
|
|
92
103
|
|
|
104
|
+
function ensureLDVersion(
|
|
105
|
+
exposedInstanceDependencies: ExposedInstanceDependencies = {},
|
|
106
|
+
version?: string
|
|
107
|
+
) {
|
|
108
|
+
if (version && !exposedInstanceDependencies.launchDarkly?.ldService) {
|
|
109
|
+
return { ...exposedInstanceDependencies, launchDarkly: { version } };
|
|
110
|
+
}
|
|
111
|
+
return exposedInstanceDependencies;
|
|
112
|
+
}
|
|
113
|
+
|
|
93
114
|
function getStyleUrls({ urls }: Pick<BundleInfo, 'urls'>) {
|
|
94
115
|
if (urls.css.length) {
|
|
95
116
|
return encodeURIComponent(JSON.stringify(urls.css));
|