@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.
Files changed (66) hide show
  1. package/dist/common.d.ts +10 -0
  2. package/dist/common.d.ts.map +1 -1
  3. package/dist/common.js +1 -0
  4. package/dist/common.js.map +1 -1
  5. package/dist/get-value-for-environment.d.ts +4 -0
  6. package/dist/get-value-for-environment.d.ts.map +1 -0
  7. package/dist/get-value-for-environment.js +26 -0
  8. package/dist/get-value-for-environment.js.map +1 -0
  9. package/dist/globals.d.ts +4 -3
  10. package/dist/globals.d.ts.map +1 -1
  11. package/dist/globals.js +4 -2
  12. package/dist/globals.js.map +1 -1
  13. package/dist/index.d.ts +3 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +3 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/loader.d.ts.map +1 -1
  18. package/dist/loader.js +39 -8
  19. package/dist/loader.js.map +1 -1
  20. package/dist/register.d.ts.map +1 -1
  21. package/dist/register.js +21 -4
  22. package/dist/register.js.map +1 -1
  23. package/dist/utils/get-bundle-info.d.ts +15 -3
  24. package/dist/utils/get-bundle-info.d.ts.map +1 -1
  25. package/dist/utils/get-bundle-info.js +4 -1
  26. package/dist/utils/get-bundle-info.js.map +1 -1
  27. package/dist/utils/get-element-name.d.ts +1 -1
  28. package/dist/utils/get-element-name.d.ts.map +1 -1
  29. package/dist/utils/get-ld-service-token.d.ts +4 -0
  30. package/dist/utils/get-ld-service-token.d.ts.map +1 -0
  31. package/dist/utils/get-ld-service-token.js +11 -0
  32. package/dist/utils/get-ld-service-token.js.map +1 -0
  33. package/dist/utils/get-mfe-has-compatible-launchdarkly-version.d.ts +11 -0
  34. package/dist/utils/get-mfe-has-compatible-launchdarkly-version.d.ts.map +1 -0
  35. package/dist/utils/get-mfe-has-compatible-launchdarkly-version.js +16 -0
  36. package/dist/utils/get-mfe-has-compatible-launchdarkly-version.js.map +1 -0
  37. package/dist/utils/index.d.ts +1 -0
  38. package/dist/utils/index.d.ts.map +1 -1
  39. package/dist/utils/index.js +1 -0
  40. package/dist/utils/index.js.map +1 -1
  41. package/dist/utils/validate-bundle-info.d.ts +7 -4
  42. package/dist/utils/validate-bundle-info.d.ts.map +1 -1
  43. package/dist/utils/validate-bundle-info.js +30 -12
  44. package/dist/utils/validate-bundle-info.js.map +1 -1
  45. package/dist/utils/web-component.d.ts +4 -1
  46. package/dist/utils/web-component.d.ts.map +1 -1
  47. package/dist/utils/web-component.js +15 -2
  48. package/dist/utils/web-component.js.map +1 -1
  49. package/package.json +17 -12
  50. package/src/__tests__/get-value-for-environment.test.ts +77 -0
  51. package/src/__tests__/globals.test.ts +4 -8
  52. package/src/__tests__/loader.test.tsx +152 -13
  53. package/src/__tests__/register-legacy-root.test.tsx +2 -5
  54. package/src/common.ts +10 -0
  55. package/src/get-value-for-environment.ts +42 -0
  56. package/src/globals.ts +12 -3
  57. package/src/index.ts +3 -2
  58. package/src/loader.tsx +46 -9
  59. package/src/register.tsx +21 -8
  60. package/src/utils/__tests__/get-mfe-has-compatible-launchdarkly-version.test.ts +54 -0
  61. package/src/utils/get-bundle-info.ts +27 -10
  62. package/src/utils/get-ld-service-token.ts +13 -0
  63. package/src/utils/get-mfe-has-compatible-launchdarkly-version.ts +20 -0
  64. package/src/utils/index.ts +1 -0
  65. package/src/utils/validate-bundle-info.ts +40 -19
  66. 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
- export { _WEB_COMPONENT_NAME as WEB_COMPONENT_NAME };
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<(props: LoaderDataProps) => JSX.Element | null>(async () => {
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
- cancelation.signal,
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, { exposedDependencies, logService, packageName: src });
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
- { provide: EVENT_BUS_TOKEN, useValue: this.eventBus },
254
+ {
255
+ provide: EXPOSED_INSTANCE_DEPENDENCIES_TOKEN,
256
+ useValue: this.exposedInstanceDependencies,
257
+ },
250
258
  { provide: BASENAME_TOKEN, useValue: this.basename },
251
- ].filter(({ useValue }) => useValue !== undefined)}
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
- export async function getBundleInfo(
22
- mainPackageUrl: string,
23
- fallbackPackageUrl: string | undefined,
24
- exposedDependencies: ExposedDependencies = {},
25
- signal?: AbortSignal,
26
- retries?: number,
27
- onRequestError?: (error: GetJsonError) => void
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
+ }
@@ -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 (!mismatch || !logService) {
15
+ if (!logService) {
14
16
  return;
15
17
  }
16
18
 
17
- const data = { package: packageName, dependencies: JSON.stringify(mismatch) };
18
- if (Object.keys(exposedDependencies).length === 0) {
19
- logService.warning({
20
- category: 'MicroFrontends.NoExposedDependencies',
21
- message:
22
- 'Cannot use light bundle because the host is not exposing dependencies or the list of shared dependencies is empty.',
23
- data,
24
- });
25
- } else {
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.DependenciesMismatch',
28
- message: 'Some of the package dependencies have incompatible versions.',
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));