@servicetitan/launchdarkly-service 39.0.0 → 39.1.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.
@@ -1 +1 @@
1
- {"version":3,"file":"get-ld-client.d.ts","sourceRoot":"","sources":["../src/get-ld-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAc,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAIvC,wBAAgB,WAAW,CAAC,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,YAAY,GAAG,QAAQ,CAOtF"}
1
+ {"version":3,"file":"get-ld-client.d.ts","sourceRoot":"","sources":["../src/get-ld-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAyB,MAAM,4BAA4B,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAMvC,wBAAgB,WAAW,CAAC,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,YAAY,GAAG,QAAQ,CAOtF"}
@@ -1,9 +1,15 @@
1
1
  import { initialize } from 'launchdarkly-js-client-sdk';
2
2
  const ldClients = new Map();
3
+ const defaultOptions = {
4
+ sendEventsOnlyForVariation: true
5
+ };
3
6
  export function getLDClient({ clientSideID, context, options }) {
4
7
  let client = ldClients.get(clientSideID);
5
8
  if (!client) {
6
- client = initialize(clientSideID, context, options);
9
+ client = initialize(clientSideID, context, {
10
+ ...defaultOptions,
11
+ ...options
12
+ });
7
13
  ldClients.set(clientSideID, client);
8
14
  }
9
15
  return client;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/get-ld-client.ts"],"sourcesContent":["import { LDClient, initialize } from 'launchdarkly-js-client-sdk';\nimport { ClientConfig } from './types';\n\nconst ldClients: Map<string, LDClient> = new Map();\n\nexport function getLDClient({ clientSideID, context, options }: ClientConfig): LDClient {\n let client = ldClients.get(clientSideID);\n if (!client) {\n client = initialize(clientSideID, context, options);\n ldClients.set(clientSideID, client);\n }\n return client;\n}\n"],"names":["initialize","ldClients","Map","getLDClient","clientSideID","context","options","client","get","set"],"mappings":"AAAA,SAAmBA,UAAU,QAAQ,6BAA6B;AAGlE,MAAMC,YAAmC,IAAIC;AAE7C,OAAO,SAASC,YAAY,EAAEC,YAAY,EAAEC,OAAO,EAAEC,OAAO,EAAgB;IACxE,IAAIC,SAASN,UAAUO,GAAG,CAACJ;IAC3B,IAAI,CAACG,QAAQ;QACTA,SAASP,WAAWI,cAAcC,SAASC;QAC3CL,UAAUQ,GAAG,CAACL,cAAcG;IAChC;IACA,OAAOA;AACX"}
1
+ {"version":3,"sources":["../src/get-ld-client.ts"],"sourcesContent":["import { LDClient, LDOptions, initialize } from 'launchdarkly-js-client-sdk';\nimport { ClientConfig } from './types';\n\nconst ldClients: Map<string, LDClient> = new Map();\n\nconst defaultOptions: LDOptions = { sendEventsOnlyForVariation: true };\n\nexport function getLDClient({ clientSideID, context, options }: ClientConfig): LDClient {\n let client = ldClients.get(clientSideID);\n if (!client) {\n client = initialize(clientSideID, context, { ...defaultOptions, ...options });\n ldClients.set(clientSideID, client);\n }\n return client;\n}\n"],"names":["initialize","ldClients","Map","defaultOptions","sendEventsOnlyForVariation","getLDClient","clientSideID","context","options","client","get","set"],"mappings":"AAAA,SAA8BA,UAAU,QAAQ,6BAA6B;AAG7E,MAAMC,YAAmC,IAAIC;AAE7C,MAAMC,iBAA4B;IAAEC,4BAA4B;AAAK;AAErE,OAAO,SAASC,YAAY,EAAEC,YAAY,EAAEC,OAAO,EAAEC,OAAO,EAAgB;IACxE,IAAIC,SAASR,UAAUS,GAAG,CAACJ;IAC3B,IAAI,CAACG,QAAQ;QACTA,SAAST,WAAWM,cAAcC,SAAS;YAAE,GAAGJ,cAAc;YAAE,GAAGK,OAAO;QAAC;QAC3EP,UAAUU,GAAG,CAACL,cAAcG;IAChC;IACA,OAAOA;AACX"}
@@ -1,7 +1,7 @@
1
1
  import { LDContext } from 'launchdarkly-js-client-sdk';
2
2
  import { ProviderConfig as BaseProviderConfig } from 'launchdarkly-react-client-sdk';
3
3
  import { FC, PropsWithChildren } from 'react';
4
- export type ProviderConfig = Pick<BaseProviderConfig, 'clientSideID'> & {
4
+ export type ProviderConfig = Pick<BaseProviderConfig, 'clientSideID' | 'flags'> & {
5
5
  /** Pass `null` to defer client init until a real context is available. */
6
6
  context: LDContext | null;
7
7
  projectName: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ld-provider.d.ts","sourceRoot":"","sources":["../src/ld-provider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAEH,cAAc,IAAI,kBAAkB,EACvC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,iBAAiB,EAAmC,MAAM,OAAO,CAAC;AAM/E,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,kBAAkB,EAAE,cAAc,CAAC,GAAG;IACpE,0EAA0E;IAC1E,OAAO,EAAE,SAAS,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACnC,CAAC;AAaF,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,iBAAiB,CAAC,cAAc,CAAC,CA8D3D,CAAC"}
1
+ {"version":3,"file":"ld-provider.d.ts","sourceRoot":"","sources":["../src/ld-provider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAEH,cAAc,IAAI,kBAAkB,EACvC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,iBAAiB,EAAmC,MAAM,OAAO,CAAC;AAM/E,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,kBAAkB,EAAE,cAAc,GAAG,OAAO,CAAC,GAAG;IAC9E,0EAA0E;IAC1E,OAAO,EAAE,SAAS,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACnC,CAAC;AAaF,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,iBAAiB,CAAC,cAAc,CAAC,CA0E3D,CAAC"}
@@ -24,7 +24,7 @@ export const LDProvider = provide({
24
24
  FeatureFlagStore
25
25
  ]
26
26
  })((props)=>{
27
- const { children, clientSideID, context, projectName, waitForInitialization = true } = props;
27
+ const { children, clientSideID, context, flags, projectName, waitForInitialization = true } = props;
28
28
  const [ldClient, setLDClient] = useState();
29
29
  const [ldService, log] = useOptionalDependencies(LD_SERVICE_TOKEN, Log);
30
30
  const [counter, forceUpdate] = useReducer((x)=>x + 1, 0);
@@ -85,6 +85,9 @@ export const LDProvider = provide({
85
85
  return /*#__PURE__*/ _jsx(BaseLDProvider, {
86
86
  clientSideID: clientSideID,
87
87
  ldClient: ldClient,
88
+ ...flags && {
89
+ flags
90
+ },
88
91
  children: /*#__PURE__*/ _jsx(FeatureFlagProvider, {
89
92
  children: children
90
93
  })
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ld-provider.tsx"],"sourcesContent":["import { Log } from '@servicetitan/log-service';\nimport { provide, useOptionalDependencies } from '@servicetitan/react-ioc';\nimport { LDClient, LDContext } from 'launchdarkly-js-client-sdk';\nimport {\n LDProvider as BaseLDProvider,\n ProviderConfig as BaseProviderConfig,\n} from 'launchdarkly-react-client-sdk';\nimport { FC, PropsWithChildren, useEffect, useReducer, useState } from 'react';\nimport { FeatureFlagProvider } from './feature-flag-provider';\nimport { FeatureFlagStore } from './feature-flag-store';\nimport { getLDClient } from './get-ld-client';\nimport { ClientConfig, LDService, LD_SERVICE_TOKEN } from './types';\n\nexport type ProviderConfig = Pick<BaseProviderConfig, 'clientSideID'> & {\n /** Pass `null` to defer client init until a real context is available. */\n context: LDContext | null;\n projectName: string;\n waitForInitialization?: boolean;\n};\n\nfunction mergeOptions(ldService: LDService | undefined): BaseProviderConfig['options'] {\n const { application, options } = ldService ?? {};\n if (!options && application === undefined) {\n return undefined;\n }\n return {\n ...options,\n ...(application !== undefined ? { application } : {}),\n };\n}\n\nexport const LDProvider: FC<PropsWithChildren<ProviderConfig>> = provide({\n singletons: [FeatureFlagStore],\n})(props => {\n const { children, clientSideID, context, projectName, waitForInitialization = true } = props;\n const [ldClient, setLDClient] = useState<LDClient>();\n const [ldService, log] = useOptionalDependencies(LD_SERVICE_TOKEN, Log);\n const [counter, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n useEffect(() => {\n if (context === null) {\n setLDClient(undefined);\n return;\n }\n\n let cancelled = false;\n\n (async function initializeClient() {\n const options = mergeOptions(ldService);\n const config: ClientConfig = { clientSideID, context, options };\n const client = ldService?.getClient(config) ?? getLDClient(config);\n if (waitForInitialization) {\n try {\n await client.waitForInitialization(5);\n } catch (error) {\n log?.error({\n category: 'LaunchDarkly',\n code: 'InitializationFailed',\n error,\n message: `${projectName} client failed to initialize`,\n });\n }\n }\n if (!cancelled) {\n setLDClient(client);\n }\n })();\n\n return () => {\n cancelled = true;\n };\n // Presence only — avoid re-init when callers pass a new context object each render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [clientSideID, context === null]);\n\n useEffect(() => {\n if (!waitForInitialization && ldClient) {\n ldClient.on('initialized', forceUpdate);\n return () => {\n ldClient.off('initialized', forceUpdate);\n };\n }\n }, [ldClient, waitForInitialization]);\n\n if (context === null || !ldClient) {\n return null;\n }\n\n return (\n <BaseLDProvider key={counter.toString()} clientSideID={clientSideID} ldClient={ldClient}>\n <FeatureFlagProvider>{children}</FeatureFlagProvider>\n </BaseLDProvider>\n );\n});\n"],"names":["Log","provide","useOptionalDependencies","LDProvider","BaseLDProvider","useEffect","useReducer","useState","FeatureFlagProvider","FeatureFlagStore","getLDClient","LD_SERVICE_TOKEN","mergeOptions","ldService","application","options","undefined","singletons","props","children","clientSideID","context","projectName","waitForInitialization","ldClient","setLDClient","log","counter","forceUpdate","x","cancelled","initializeClient","config","client","getClient","error","category","code","message","on","off","toString"],"mappings":";AAAA,SAASA,GAAG,QAAQ,4BAA4B;AAChD,SAASC,OAAO,EAAEC,uBAAuB,QAAQ,0BAA0B;AAE3E,SACIC,cAAcC,cAAc,QAEzB,gCAAgC;AACvC,SAAgCC,SAAS,EAAEC,UAAU,EAAEC,QAAQ,QAAQ,QAAQ;AAC/E,SAASC,mBAAmB,QAAQ,0BAA0B;AAC9D,SAASC,gBAAgB,QAAQ,uBAAuB;AACxD,SAASC,WAAW,QAAQ,kBAAkB;AAC9C,SAAkCC,gBAAgB,QAAQ,UAAU;AASpE,SAASC,aAAaC,SAAgC;IAClD,MAAM,EAAEC,WAAW,EAAEC,OAAO,EAAE,GAAGF,sBAAAA,uBAAAA,YAAa,CAAC;IAC/C,IAAI,CAACE,WAAWD,gBAAgBE,WAAW;QACvC,OAAOA;IACX;IACA,OAAO;QACH,GAAGD,OAAO;QACV,GAAID,gBAAgBE,YAAY;YAAEF;QAAY,IAAI,CAAC,CAAC;IACxD;AACJ;AAEA,OAAO,MAAMX,aAAoDF,QAAQ;IACrEgB,YAAY;QAACR;KAAiB;AAClC,GAAGS,CAAAA;IACC,MAAM,EAAEC,QAAQ,EAAEC,YAAY,EAAEC,OAAO,EAAEC,WAAW,EAAEC,wBAAwB,IAAI,EAAE,GAAGL;IACvF,MAAM,CAACM,UAAUC,YAAY,GAAGlB;IAChC,MAAM,CAACM,WAAWa,IAAI,GAAGxB,wBAAwBS,kBAAkBX;IACnE,MAAM,CAAC2B,SAASC,YAAY,GAAGtB,WAAW,CAACuB,IAAcA,IAAI,GAAG;IAEhExB,UAAU;QACN,IAAIgB,YAAY,MAAM;YAClBI,YAAYT;YACZ;QACJ;QAEA,IAAIc,YAAY;QAEf,CAAA,eAAeC;;YACZ,MAAMhB,UAAUH,aAAaC;YAC7B,MAAMmB,SAAuB;gBAAEZ;gBAAcC;gBAASN;YAAQ;YAC9D,MAAMkB,iBAASpB,sBAAAA,gCAAAA,UAAWqB,SAAS,CAACF,8CAAWtB,YAAYsB;YAC3D,IAAIT,uBAAuB;gBACvB,IAAI;oBACA,MAAMU,OAAOV,qBAAqB,CAAC;gBACvC,EAAE,OAAOY,OAAO;oBACZT,gBAAAA,0BAAAA,IAAKS,KAAK,CAAC;wBACPC,UAAU;wBACVC,MAAM;wBACNF;wBACAG,SAAS,GAAGhB,YAAY,4BAA4B,CAAC;oBACzD;gBACJ;YACJ;YACA,IAAI,CAACQ,WAAW;gBACZL,YAAYQ;YAChB;QACJ,CAAA;QAEA,OAAO;YACHH,YAAY;QAChB;IACA,oFAAoF;IACpF,uDAAuD;IAC3D,GAAG;QAACV;QAAcC,YAAY;KAAK;IAEnChB,UAAU;QACN,IAAI,CAACkB,yBAAyBC,UAAU;YACpCA,SAASe,EAAE,CAAC,eAAeX;YAC3B,OAAO;gBACHJ,SAASgB,GAAG,CAAC,eAAeZ;YAChC;QACJ;IACJ,GAAG;QAACJ;QAAUD;KAAsB;IAEpC,IAAIF,YAAY,QAAQ,CAACG,UAAU;QAC/B,OAAO;IACX;IAEA,qBACI,KAACpB;QAAwCgB,cAAcA;QAAcI,UAAUA;kBAC3E,cAAA,KAAChB;sBAAqBW;;OADLQ,QAAQc,QAAQ;AAI7C,GAAG"}
1
+ {"version":3,"sources":["../src/ld-provider.tsx"],"sourcesContent":["import { Log } from '@servicetitan/log-service';\nimport { provide, useOptionalDependencies } from '@servicetitan/react-ioc';\nimport { LDClient, LDContext } from 'launchdarkly-js-client-sdk';\nimport {\n LDProvider as BaseLDProvider,\n ProviderConfig as BaseProviderConfig,\n} from 'launchdarkly-react-client-sdk';\nimport { FC, PropsWithChildren, useEffect, useReducer, useState } from 'react';\nimport { FeatureFlagProvider } from './feature-flag-provider';\nimport { FeatureFlagStore } from './feature-flag-store';\nimport { getLDClient } from './get-ld-client';\nimport { ClientConfig, LDService, LD_SERVICE_TOKEN } from './types';\n\nexport type ProviderConfig = Pick<BaseProviderConfig, 'clientSideID' | 'flags'> & {\n /** Pass `null` to defer client init until a real context is available. */\n context: LDContext | null;\n projectName: string;\n waitForInitialization?: boolean;\n};\n\nfunction mergeOptions(ldService: LDService | undefined): BaseProviderConfig['options'] {\n const { application, options } = ldService ?? {};\n if (!options && application === undefined) {\n return undefined;\n }\n return {\n ...options,\n ...(application !== undefined ? { application } : {}),\n };\n}\n\nexport const LDProvider: FC<PropsWithChildren<ProviderConfig>> = provide({\n singletons: [FeatureFlagStore],\n})(props => {\n const {\n children,\n clientSideID,\n context,\n flags,\n projectName,\n waitForInitialization = true,\n } = props;\n const [ldClient, setLDClient] = useState<LDClient>();\n const [ldService, log] = useOptionalDependencies(LD_SERVICE_TOKEN, Log);\n const [counter, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n useEffect(() => {\n if (context === null) {\n setLDClient(undefined);\n return;\n }\n\n let cancelled = false;\n\n (async function initializeClient() {\n const options = mergeOptions(ldService);\n const config: ClientConfig = { clientSideID, context, options };\n const client = ldService?.getClient(config) ?? getLDClient(config);\n if (waitForInitialization) {\n try {\n await client.waitForInitialization(5);\n } catch (error) {\n log?.error({\n category: 'LaunchDarkly',\n code: 'InitializationFailed',\n error,\n message: `${projectName} client failed to initialize`,\n });\n }\n }\n if (!cancelled) {\n setLDClient(client);\n }\n })();\n\n return () => {\n cancelled = true;\n };\n // Presence only — avoid re-init when callers pass a new context object each render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [clientSideID, context === null]);\n\n useEffect(() => {\n if (!waitForInitialization && ldClient) {\n ldClient.on('initialized', forceUpdate);\n return () => {\n ldClient.off('initialized', forceUpdate);\n };\n }\n }, [ldClient, waitForInitialization]);\n\n if (context === null || !ldClient) {\n return null;\n }\n\n return (\n <BaseLDProvider\n key={counter.toString()}\n clientSideID={clientSideID}\n ldClient={ldClient}\n {...(flags && { flags })}\n >\n <FeatureFlagProvider>{children}</FeatureFlagProvider>\n </BaseLDProvider>\n );\n});\n"],"names":["Log","provide","useOptionalDependencies","LDProvider","BaseLDProvider","useEffect","useReducer","useState","FeatureFlagProvider","FeatureFlagStore","getLDClient","LD_SERVICE_TOKEN","mergeOptions","ldService","application","options","undefined","singletons","props","children","clientSideID","context","flags","projectName","waitForInitialization","ldClient","setLDClient","log","counter","forceUpdate","x","cancelled","initializeClient","config","client","getClient","error","category","code","message","on","off","toString"],"mappings":";AAAA,SAASA,GAAG,QAAQ,4BAA4B;AAChD,SAASC,OAAO,EAAEC,uBAAuB,QAAQ,0BAA0B;AAE3E,SACIC,cAAcC,cAAc,QAEzB,gCAAgC;AACvC,SAAgCC,SAAS,EAAEC,UAAU,EAAEC,QAAQ,QAAQ,QAAQ;AAC/E,SAASC,mBAAmB,QAAQ,0BAA0B;AAC9D,SAASC,gBAAgB,QAAQ,uBAAuB;AACxD,SAASC,WAAW,QAAQ,kBAAkB;AAC9C,SAAkCC,gBAAgB,QAAQ,UAAU;AASpE,SAASC,aAAaC,SAAgC;IAClD,MAAM,EAAEC,WAAW,EAAEC,OAAO,EAAE,GAAGF,sBAAAA,uBAAAA,YAAa,CAAC;IAC/C,IAAI,CAACE,WAAWD,gBAAgBE,WAAW;QACvC,OAAOA;IACX;IACA,OAAO;QACH,GAAGD,OAAO;QACV,GAAID,gBAAgBE,YAAY;YAAEF;QAAY,IAAI,CAAC,CAAC;IACxD;AACJ;AAEA,OAAO,MAAMX,aAAoDF,QAAQ;IACrEgB,YAAY;QAACR;KAAiB;AAClC,GAAGS,CAAAA;IACC,MAAM,EACFC,QAAQ,EACRC,YAAY,EACZC,OAAO,EACPC,KAAK,EACLC,WAAW,EACXC,wBAAwB,IAAI,EAC/B,GAAGN;IACJ,MAAM,CAACO,UAAUC,YAAY,GAAGnB;IAChC,MAAM,CAACM,WAAWc,IAAI,GAAGzB,wBAAwBS,kBAAkBX;IACnE,MAAM,CAAC4B,SAASC,YAAY,GAAGvB,WAAW,CAACwB,IAAcA,IAAI,GAAG;IAEhEzB,UAAU;QACN,IAAIgB,YAAY,MAAM;YAClBK,YAAYV;YACZ;QACJ;QAEA,IAAIe,YAAY;QAEf,CAAA,eAAeC;;YACZ,MAAMjB,UAAUH,aAAaC;YAC7B,MAAMoB,SAAuB;gBAAEb;gBAAcC;gBAASN;YAAQ;YAC9D,MAAMmB,iBAASrB,sBAAAA,gCAAAA,UAAWsB,SAAS,CAACF,8CAAWvB,YAAYuB;YAC3D,IAAIT,uBAAuB;gBACvB,IAAI;oBACA,MAAMU,OAAOV,qBAAqB,CAAC;gBACvC,EAAE,OAAOY,OAAO;oBACZT,gBAAAA,0BAAAA,IAAKS,KAAK,CAAC;wBACPC,UAAU;wBACVC,MAAM;wBACNF;wBACAG,SAAS,GAAGhB,YAAY,4BAA4B,CAAC;oBACzD;gBACJ;YACJ;YACA,IAAI,CAACQ,WAAW;gBACZL,YAAYQ;YAChB;QACJ,CAAA;QAEA,OAAO;YACHH,YAAY;QAChB;IACA,oFAAoF;IACpF,uDAAuD;IAC3D,GAAG;QAACX;QAAcC,YAAY;KAAK;IAEnChB,UAAU;QACN,IAAI,CAACmB,yBAAyBC,UAAU;YACpCA,SAASe,EAAE,CAAC,eAAeX;YAC3B,OAAO;gBACHJ,SAASgB,GAAG,CAAC,eAAeZ;YAChC;QACJ;IACJ,GAAG;QAACJ;QAAUD;KAAsB;IAEpC,IAAIH,YAAY,QAAQ,CAACI,UAAU;QAC/B,OAAO;IACX;IAEA,qBACI,KAACrB;QAEGgB,cAAcA;QACdK,UAAUA;QACT,GAAIH,SAAS;YAAEA;QAAM,CAAC;kBAEvB,cAAA,KAACd;sBAAqBW;;OALjBS,QAAQc,QAAQ;AAQjC,GAAG"}
@@ -5,6 +5,9 @@ jest.mock('launchdarkly-js-client-sdk', ()=>({
5
5
  }));
6
6
  describe(`[launchdarkly-service] ${getLDClient.name}`, ()=>{
7
7
  const mockClient = Symbol('mockClient');
8
+ const defaultOptions = {
9
+ sendEventsOnlyForVariation: true
10
+ };
8
11
  let config;
9
12
  const subject = ()=>getLDClient(config);
10
13
  beforeEach(()=>{
@@ -17,17 +20,31 @@ describe(`[launchdarkly-service] ${getLDClient.name}`, ()=>{
17
20
  }
18
21
  };
19
22
  });
20
- test('initializes client with the given context', ()=>{
23
+ test('initializes client with the given context and default options', ()=>{
21
24
  expect(subject()).toBe(mockClient);
22
- expect(initialize).toHaveBeenCalledWith(config.clientSideID, config.context, config.options);
25
+ expect(initialize).toHaveBeenCalledWith(config.clientSideID, config.context, defaultOptions);
23
26
  });
24
27
  describe('when given options', ()=>{
25
28
  beforeEach(()=>config.options = {
26
29
  hash: 'foo'
27
30
  });
28
- test('passes options through unchanged', ()=>{
31
+ test('passes options through alongside the defaults', ()=>{
29
32
  subject();
30
- expect(initialize).toHaveBeenCalledWith(config.clientSideID, config.context, config.options);
33
+ expect(initialize).toHaveBeenCalledWith(config.clientSideID, config.context, {
34
+ ...defaultOptions,
35
+ ...config.options
36
+ });
37
+ });
38
+ });
39
+ describe('when given sendEventsOnlyForVariation', ()=>{
40
+ beforeEach(()=>config.options = {
41
+ sendEventsOnlyForVariation: false
42
+ });
43
+ test('lets the caller override the default', ()=>{
44
+ subject();
45
+ expect(initialize).toHaveBeenCalledWith(config.clientSideID, config.context, {
46
+ sendEventsOnlyForVariation: false
47
+ });
31
48
  });
32
49
  });
33
50
  describe('when client already exists', ()=>{
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/tests/get-ld-client.test.ts"],"sourcesContent":["import { initialize } from 'launchdarkly-js-client-sdk';\nimport { getLDClient } from '../get-ld-client';\n\njest.mock('launchdarkly-js-client-sdk', () => ({\n initialize: jest.fn(),\n}));\n\ndescribe(`[launchdarkly-service] ${getLDClient.name}`, () => {\n const mockClient: any = Symbol('mockClient');\n let config: Parameters<typeof getLDClient>[0];\n\n const subject = () => getLDClient(config);\n\n beforeEach(() => {\n jest.clearAllMocks();\n jest.mocked(initialize).mockImplementation(() => mockClient);\n config = { clientSideID: makeId(), context: { kind: 'multi' } };\n });\n\n test('initializes client with the given context', () => {\n expect(subject()).toBe(mockClient);\n expect(initialize).toHaveBeenCalledWith(\n config.clientSideID,\n config.context,\n config.options\n );\n });\n\n describe('when given options', () => {\n beforeEach(() => (config.options = { hash: 'foo' }));\n\n test('passes options through unchanged', () => {\n subject();\n\n expect(initialize).toHaveBeenCalledWith(\n config.clientSideID,\n config.context,\n config.options\n );\n });\n });\n\n describe('when client already exists', () => {\n const existingClient: any = Symbol('existingClient');\n\n beforeEach(() => {\n jest.mocked(initialize).mockImplementationOnce(() => existingClient);\n subject();\n });\n\n test('returns existing client', () => {\n expect(subject()).toBe(existingClient);\n expect(initialize).toHaveBeenCalledTimes(1);\n });\n });\n});\n\nfunction makeId() {\n return Math.round(1e6 * Math.random()).toString(36);\n}\n"],"names":["initialize","getLDClient","jest","mock","fn","describe","name","mockClient","Symbol","config","subject","beforeEach","clearAllMocks","mocked","mockImplementation","clientSideID","makeId","context","kind","test","expect","toBe","toHaveBeenCalledWith","options","hash","existingClient","mockImplementationOnce","toHaveBeenCalledTimes","Math","round","random","toString"],"mappings":"AAAA,SAASA,UAAU,QAAQ,6BAA6B;AACxD,SAASC,WAAW,QAAQ,mBAAmB;AAE/CC,KAAKC,IAAI,CAAC,8BAA8B,IAAO,CAAA;QAC3CH,YAAYE,KAAKE,EAAE;IACvB,CAAA;AAEAC,SAAS,CAAC,uBAAuB,EAAEJ,YAAYK,IAAI,EAAE,EAAE;IACnD,MAAMC,aAAkBC,OAAO;IAC/B,IAAIC;IAEJ,MAAMC,UAAU,IAAMT,YAAYQ;IAElCE,WAAW;QACPT,KAAKU,aAAa;QAClBV,KAAKW,MAAM,CAACb,YAAYc,kBAAkB,CAAC,IAAMP;QACjDE,SAAS;YAAEM,cAAcC;YAAUC,SAAS;gBAAEC,MAAM;YAAQ;QAAE;IAClE;IAEAC,KAAK,6CAA6C;QAC9CC,OAAOV,WAAWW,IAAI,CAACd;QACvBa,OAAOpB,YAAYsB,oBAAoB,CACnCb,OAAOM,YAAY,EACnBN,OAAOQ,OAAO,EACdR,OAAOc,OAAO;IAEtB;IAEAlB,SAAS,sBAAsB;QAC3BM,WAAW,IAAOF,OAAOc,OAAO,GAAG;gBAAEC,MAAM;YAAM;QAEjDL,KAAK,oCAAoC;YACrCT;YAEAU,OAAOpB,YAAYsB,oBAAoB,CACnCb,OAAOM,YAAY,EACnBN,OAAOQ,OAAO,EACdR,OAAOc,OAAO;QAEtB;IACJ;IAEAlB,SAAS,8BAA8B;QACnC,MAAMoB,iBAAsBjB,OAAO;QAEnCG,WAAW;YACPT,KAAKW,MAAM,CAACb,YAAY0B,sBAAsB,CAAC,IAAMD;YACrDf;QACJ;QAEAS,KAAK,2BAA2B;YAC5BC,OAAOV,WAAWW,IAAI,CAACI;YACvBL,OAAOpB,YAAY2B,qBAAqB,CAAC;QAC7C;IACJ;AACJ;AAEA,SAASX;IACL,OAAOY,KAAKC,KAAK,CAAC,MAAMD,KAAKE,MAAM,IAAIC,QAAQ,CAAC;AACpD"}
1
+ {"version":3,"sources":["../../src/tests/get-ld-client.test.ts"],"sourcesContent":["import { initialize } from 'launchdarkly-js-client-sdk';\nimport { getLDClient } from '../get-ld-client';\n\njest.mock('launchdarkly-js-client-sdk', () => ({\n initialize: jest.fn(),\n}));\n\ndescribe(`[launchdarkly-service] ${getLDClient.name}`, () => {\n const mockClient: any = Symbol('mockClient');\n const defaultOptions = { sendEventsOnlyForVariation: true };\n let config: Parameters<typeof getLDClient>[0];\n\n const subject = () => getLDClient(config);\n\n beforeEach(() => {\n jest.clearAllMocks();\n jest.mocked(initialize).mockImplementation(() => mockClient);\n config = { clientSideID: makeId(), context: { kind: 'multi' } };\n });\n\n test('initializes client with the given context and default options', () => {\n expect(subject()).toBe(mockClient);\n expect(initialize).toHaveBeenCalledWith(\n config.clientSideID,\n config.context,\n defaultOptions\n );\n });\n\n describe('when given options', () => {\n beforeEach(() => (config.options = { hash: 'foo' }));\n\n test('passes options through alongside the defaults', () => {\n subject();\n\n expect(initialize).toHaveBeenCalledWith(config.clientSideID, config.context, {\n ...defaultOptions,\n ...config.options,\n });\n });\n });\n\n describe('when given sendEventsOnlyForVariation', () => {\n beforeEach(() => (config.options = { sendEventsOnlyForVariation: false }));\n\n test('lets the caller override the default', () => {\n subject();\n\n expect(initialize).toHaveBeenCalledWith(config.clientSideID, config.context, {\n sendEventsOnlyForVariation: false,\n });\n });\n });\n\n describe('when client already exists', () => {\n const existingClient: any = Symbol('existingClient');\n\n beforeEach(() => {\n jest.mocked(initialize).mockImplementationOnce(() => existingClient);\n subject();\n });\n\n test('returns existing client', () => {\n expect(subject()).toBe(existingClient);\n expect(initialize).toHaveBeenCalledTimes(1);\n });\n });\n});\n\nfunction makeId() {\n return Math.round(1e6 * Math.random()).toString(36);\n}\n"],"names":["initialize","getLDClient","jest","mock","fn","describe","name","mockClient","Symbol","defaultOptions","sendEventsOnlyForVariation","config","subject","beforeEach","clearAllMocks","mocked","mockImplementation","clientSideID","makeId","context","kind","test","expect","toBe","toHaveBeenCalledWith","options","hash","existingClient","mockImplementationOnce","toHaveBeenCalledTimes","Math","round","random","toString"],"mappings":"AAAA,SAASA,UAAU,QAAQ,6BAA6B;AACxD,SAASC,WAAW,QAAQ,mBAAmB;AAE/CC,KAAKC,IAAI,CAAC,8BAA8B,IAAO,CAAA;QAC3CH,YAAYE,KAAKE,EAAE;IACvB,CAAA;AAEAC,SAAS,CAAC,uBAAuB,EAAEJ,YAAYK,IAAI,EAAE,EAAE;IACnD,MAAMC,aAAkBC,OAAO;IAC/B,MAAMC,iBAAiB;QAAEC,4BAA4B;IAAK;IAC1D,IAAIC;IAEJ,MAAMC,UAAU,IAAMX,YAAYU;IAElCE,WAAW;QACPX,KAAKY,aAAa;QAClBZ,KAAKa,MAAM,CAACf,YAAYgB,kBAAkB,CAAC,IAAMT;QACjDI,SAAS;YAAEM,cAAcC;YAAUC,SAAS;gBAAEC,MAAM;YAAQ;QAAE;IAClE;IAEAC,KAAK,iEAAiE;QAClEC,OAAOV,WAAWW,IAAI,CAAChB;QACvBe,OAAOtB,YAAYwB,oBAAoB,CACnCb,OAAOM,YAAY,EACnBN,OAAOQ,OAAO,EACdV;IAER;IAEAJ,SAAS,sBAAsB;QAC3BQ,WAAW,IAAOF,OAAOc,OAAO,GAAG;gBAAEC,MAAM;YAAM;QAEjDL,KAAK,iDAAiD;YAClDT;YAEAU,OAAOtB,YAAYwB,oBAAoB,CAACb,OAAOM,YAAY,EAAEN,OAAOQ,OAAO,EAAE;gBACzE,GAAGV,cAAc;gBACjB,GAAGE,OAAOc,OAAO;YACrB;QACJ;IACJ;IAEApB,SAAS,yCAAyC;QAC9CQ,WAAW,IAAOF,OAAOc,OAAO,GAAG;gBAAEf,4BAA4B;YAAM;QAEvEW,KAAK,wCAAwC;YACzCT;YAEAU,OAAOtB,YAAYwB,oBAAoB,CAACb,OAAOM,YAAY,EAAEN,OAAOQ,OAAO,EAAE;gBACzET,4BAA4B;YAChC;QACJ;IACJ;IAEAL,SAAS,8BAA8B;QACnC,MAAMsB,iBAAsBnB,OAAO;QAEnCK,WAAW;YACPX,KAAKa,MAAM,CAACf,YAAY4B,sBAAsB,CAAC,IAAMD;YACrDf;QACJ;QAEAS,KAAK,2BAA2B;YAC5BC,OAAOV,WAAWW,IAAI,CAACI;YACvBL,OAAOtB,YAAY6B,qBAAqB,CAAC;QAC7C;IACJ;AACJ;AAEA,SAASX;IACL,OAAOY,KAAKC,KAAK,CAAC,MAAMD,KAAKE,MAAM,IAAIC,QAAQ,CAAC;AACpD"}
@@ -95,6 +95,19 @@ describe(`[launchdarkly-service] ${LDProvider.name}`, ()=>{
95
95
  expect(screen.getByText('<Component />')).toBeInTheDocument();
96
96
  expect(ldClient.waitForInitialization).toHaveBeenCalled();
97
97
  });
98
+ describe('when given flags', ()=>{
99
+ beforeEach(()=>props.flags = {
100
+ EnableThing: false
101
+ });
102
+ test('forwards them to the base provider', async ()=>{
103
+ subject();
104
+ await waitFor(()=>expectLDProvider({
105
+ clientSideID: props.clientSideID,
106
+ ldClient,
107
+ flags: props.flags
108
+ }));
109
+ });
110
+ });
98
111
  test('provides FeatureFlagStore', async ()=>{
99
112
  subject();
100
113
  await waitFor(()=>expectLDProvider());
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/tests/ld-provider.test.tsx"],"sourcesContent":["import { Log } from '@servicetitan/log-service';\nimport { Provider, useDependencies } from '@servicetitan/react-ioc';\nimport '@testing-library/jest-dom';\nimport { render, screen, waitFor } from '@testing-library/react';\nimport { LDClient } from 'launchdarkly-js-client-sdk';\nimport { act, FC, Fragment, ReactNode } from 'react';\nimport { FeatureFlagStore } from '../feature-flag-store';\nimport { getLDClient } from '../get-ld-client';\nimport { LDProvider } from '../ld-provider';\nimport { LD_SERVICE_TOKEN, LDService } from '../types';\n\nfunction formatProps(props: Record<string, any>) {\n return Object.entries(props).map(([key, value]) => {\n return `${key}=${value.toString()}`;\n });\n}\n\nfunction mockComponent(name: string) {\n return {\n [name]: ({ children, ...props }: any) => (\n <Fragment>\n <div>{`<${name} ${formatProps(props)} />`}</div>\n {children}\n </Fragment>\n ),\n };\n}\n\njest.mock('launchdarkly-react-client-sdk', () => ({\n ...mockComponent('LDProvider'),\n}));\njest.mock('../feature-flag-provider', () => ({\n ...mockComponent('FeatureFlagProvider'),\n}));\njest.mock('../get-ld-client', () => ({\n getLDClient: jest.fn(),\n}));\n\ndescribe(`[launchdarkly-service] ${LDProvider.name}`, () => {\n const log: Partial<Log> = {\n error: jest.fn(),\n };\n const ldClient: Partial<LDClient> & { toString: () => string } = {\n on: jest.fn(),\n off: jest.fn(),\n waitUntilReady: jest.fn().mockResolvedValue(undefined),\n waitForInitialization: jest.fn().mockResolvedValue(undefined),\n toString: () => 'ldClient',\n };\n let singletons: Required<Parameters<typeof Provider>[0]>['singletons'];\n let props: Parameters<typeof LDProvider>[0];\n let providedFeatureFlagStore: FeatureFlagStore;\n let renderCount: number;\n\n const Component: FC = () => {\n [providedFeatureFlagStore] = useDependencies(FeatureFlagStore);\n renderCount += 1;\n return <div>{'<Component />'}</div>;\n };\n\n beforeEach(() => {\n jest.clearAllMocks();\n jest.mocked(getLDClient).mockReturnValue(ldClient as LDClient);\n renderCount = 0;\n singletons = [];\n props = {\n clientSideID: 'foo',\n projectName: 'bar',\n context: { kind: 'user', key: 'user-1' },\n };\n });\n\n const tree = () => (\n <Provider singletons={singletons}>\n <LDProvider {...props}>\n <Component />\n </LDProvider>\n </Provider>\n );\n\n const subject = () => render(tree());\n\n function expectLDProvider(props?: Record<string, any>) {\n if (props) {\n expect(screen.getByText(`<LDProvider ${formatProps(props)} />`)).toBeInTheDocument();\n } else {\n expect(screen.getByText(/<LDProvider .* \\/>/)).toBeInTheDocument();\n }\n }\n\n test('renders base LDProvider and FeatureFlagProvider after client is ready and initialized', async () => {\n subject();\n\n await waitFor(() => expectLDProvider({ clientSideID: props.clientSideID, ldClient }));\n\n expect(screen.getByText('<FeatureFlagProvider />')).toBeInTheDocument();\n expect(screen.getByText('<Component />')).toBeInTheDocument();\n expect(ldClient.waitForInitialization).toHaveBeenCalled();\n });\n\n test('provides FeatureFlagStore', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n\n expect(providedFeatureFlagStore).toBeDefined();\n });\n\n describe('when waitForInitialization is false', () => {\n let initializedCallback: Function;\n\n beforeEach(() => {\n props.waitForInitialization = false;\n jest.mocked(ldClient.on)?.mockImplementation((event, callback) => {\n if (event === 'initialized') {\n initializedCallback = callback;\n }\n });\n });\n\n test('does not wait for client to initialize', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n\n expect(ldClient.waitForInitialization).not.toHaveBeenCalled();\n });\n\n test('re-renders when client is initialized', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n expect(renderCount).toBe(1);\n\n act(() => initializedCallback?.());\n expect(renderCount).toBe(2);\n });\n\n test('deregisters listener when component is unmounted', async () => {\n const { unmount } = subject();\n\n await waitFor(() => expectLDProvider());\n\n unmount();\n\n expect(jest.mocked(ldClient.off)).toHaveBeenCalledWith(\n 'initialized',\n initializedCallback\n );\n });\n });\n\n describe('when initialization fails', () => {\n beforeEach(() => {\n jest.mocked(ldClient.waitForInitialization)?.mockRejectedValue('Oops!');\n });\n\n test('renders base LDProvider', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n });\n\n describe('when log service is provided', () => {\n beforeEach(() => singletons.push({ provide: Log, useValue: log }));\n\n test('logs error', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n\n expect(log.error).toHaveBeenCalledWith({\n category: 'LaunchDarkly',\n code: 'InitializationFailed',\n error: 'Oops!',\n message: `${props.projectName} client failed to initialize`,\n });\n });\n });\n });\n\n describe('when no context is provided', () => {\n beforeEach(() => {\n props.context = null;\n });\n\n test('does not initialize client', () => {\n subject();\n\n expect(getLDClient).not.toHaveBeenCalled();\n });\n\n test('does not render children', () => {\n subject();\n\n expect(screen.queryByText('<Component />')).not.toBeInTheDocument();\n });\n\n describe('when context becomes available after mount', () => {\n let rerender: (ui: ReactNode) => void;\n\n beforeEach(async () => {\n rerender = subject().rerender;\n props.context = { kind: 'user', key: 'user-1' };\n rerender(tree());\n\n await waitFor(() =>\n expectLDProvider({ clientSideID: props.clientSideID, ldClient })\n );\n });\n\n test('initializes client', () => {\n expect(getLDClient).toHaveBeenCalledWith(\n expect.objectContaining({ context: props.context })\n );\n });\n\n test('renders children', () => {\n expect(screen.queryByText('<Component />')).toBeInTheDocument();\n });\n\n describe('when context is removed', () => {\n beforeEach(() => {\n props.context = null;\n rerender(tree());\n });\n\n test('removes children', () => {\n expect(screen.queryByText('<Component />')).not.toBeInTheDocument();\n });\n });\n });\n });\n\n describe('when LDService is provided', () => {\n const ldServiceClient: Partial<LDClient> & { toString: () => string } = {\n waitUntilReady: jest.fn().mockResolvedValue(undefined),\n waitForInitialization: jest.fn().mockResolvedValue(undefined),\n toString: () => 'ldServiceClient',\n };\n let ldService: LDService;\n\n beforeEach(() => {\n ldService = { getClient: jest.fn().mockReturnValue(ldServiceClient) };\n singletons?.push({ provide: LD_SERVICE_TOKEN, useValue: ldService });\n });\n\n test('uses LDService client', async () => {\n subject();\n\n await waitFor(() =>\n expectLDProvider({ clientSideID: props.clientSideID, ldClient: ldServiceClient })\n );\n\n expect(ldServiceClient.waitForInitialization).toHaveBeenCalled();\n });\n\n describe('when LDService has options', () => {\n const options: LDService['options'] = { hash: 'abc123' };\n\n beforeEach(() => (ldService.options = options));\n\n test('forwards options to LDClient', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n\n expect(ldService.getClient).toHaveBeenCalledWith(\n expect.objectContaining({ options })\n );\n });\n });\n\n describe('when application is provided', () => {\n const application = { id: 'some-application', version: '1.2.3' };\n\n beforeEach(() => (ldService.application = application));\n\n test('passes application to LDClient', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n\n expect(ldService.getClient).toHaveBeenCalledWith(\n expect.objectContaining({\n options: { application },\n })\n );\n });\n });\n });\n});\n"],"names":["Log","Provider","useDependencies","render","screen","waitFor","act","Fragment","FeatureFlagStore","getLDClient","LDProvider","LD_SERVICE_TOKEN","formatProps","props","Object","entries","map","key","value","toString","mockComponent","name","children","div","jest","mock","fn","describe","log","error","ldClient","on","off","waitUntilReady","mockResolvedValue","undefined","waitForInitialization","singletons","providedFeatureFlagStore","renderCount","Component","beforeEach","clearAllMocks","mocked","mockReturnValue","clientSideID","projectName","context","kind","tree","subject","expectLDProvider","expect","getByText","toBeInTheDocument","test","toHaveBeenCalled","toBeDefined","initializedCallback","mockImplementation","event","callback","not","toBe","unmount","toHaveBeenCalledWith","mockRejectedValue","push","provide","useValue","category","code","message","queryByText","rerender","objectContaining","ldServiceClient","ldService","getClient","options","hash","application","id","version"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/tests/ld-provider.test.tsx"],"sourcesContent":["import { Log } from '@servicetitan/log-service';\nimport { Provider, useDependencies } from '@servicetitan/react-ioc';\nimport '@testing-library/jest-dom';\nimport { render, screen, waitFor } from '@testing-library/react';\nimport { LDClient } from 'launchdarkly-js-client-sdk';\nimport { act, FC, Fragment, ReactNode } from 'react';\nimport { FeatureFlagStore } from '../feature-flag-store';\nimport { getLDClient } from '../get-ld-client';\nimport { LDProvider } from '../ld-provider';\nimport { LD_SERVICE_TOKEN, LDService } from '../types';\n\nfunction formatProps(props: Record<string, any>) {\n return Object.entries(props).map(([key, value]) => {\n return `${key}=${value.toString()}`;\n });\n}\n\nfunction mockComponent(name: string) {\n return {\n [name]: ({ children, ...props }: any) => (\n <Fragment>\n <div>{`<${name} ${formatProps(props)} />`}</div>\n {children}\n </Fragment>\n ),\n };\n}\n\njest.mock('launchdarkly-react-client-sdk', () => ({\n ...mockComponent('LDProvider'),\n}));\njest.mock('../feature-flag-provider', () => ({\n ...mockComponent('FeatureFlagProvider'),\n}));\njest.mock('../get-ld-client', () => ({\n getLDClient: jest.fn(),\n}));\n\ndescribe(`[launchdarkly-service] ${LDProvider.name}`, () => {\n const log: Partial<Log> = {\n error: jest.fn(),\n };\n const ldClient: Partial<LDClient> & { toString: () => string } = {\n on: jest.fn(),\n off: jest.fn(),\n waitUntilReady: jest.fn().mockResolvedValue(undefined),\n waitForInitialization: jest.fn().mockResolvedValue(undefined),\n toString: () => 'ldClient',\n };\n let singletons: Required<Parameters<typeof Provider>[0]>['singletons'];\n let props: Parameters<typeof LDProvider>[0];\n let providedFeatureFlagStore: FeatureFlagStore;\n let renderCount: number;\n\n const Component: FC = () => {\n [providedFeatureFlagStore] = useDependencies(FeatureFlagStore);\n renderCount += 1;\n return <div>{'<Component />'}</div>;\n };\n\n beforeEach(() => {\n jest.clearAllMocks();\n jest.mocked(getLDClient).mockReturnValue(ldClient as LDClient);\n renderCount = 0;\n singletons = [];\n props = {\n clientSideID: 'foo',\n projectName: 'bar',\n context: { kind: 'user', key: 'user-1' },\n };\n });\n\n const tree = () => (\n <Provider singletons={singletons}>\n <LDProvider {...props}>\n <Component />\n </LDProvider>\n </Provider>\n );\n\n const subject = () => render(tree());\n\n function expectLDProvider(props?: Record<string, any>) {\n if (props) {\n expect(screen.getByText(`<LDProvider ${formatProps(props)} />`)).toBeInTheDocument();\n } else {\n expect(screen.getByText(/<LDProvider .* \\/>/)).toBeInTheDocument();\n }\n }\n\n test('renders base LDProvider and FeatureFlagProvider after client is ready and initialized', async () => {\n subject();\n\n await waitFor(() => expectLDProvider({ clientSideID: props.clientSideID, ldClient }));\n\n expect(screen.getByText('<FeatureFlagProvider />')).toBeInTheDocument();\n expect(screen.getByText('<Component />')).toBeInTheDocument();\n expect(ldClient.waitForInitialization).toHaveBeenCalled();\n });\n\n describe('when given flags', () => {\n beforeEach(() => (props.flags = { EnableThing: false }));\n\n test('forwards them to the base provider', async () => {\n subject();\n\n await waitFor(() =>\n expectLDProvider({ clientSideID: props.clientSideID, ldClient, flags: props.flags })\n );\n });\n });\n\n test('provides FeatureFlagStore', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n\n expect(providedFeatureFlagStore).toBeDefined();\n });\n\n describe('when waitForInitialization is false', () => {\n let initializedCallback: Function;\n\n beforeEach(() => {\n props.waitForInitialization = false;\n jest.mocked(ldClient.on)?.mockImplementation((event, callback) => {\n if (event === 'initialized') {\n initializedCallback = callback;\n }\n });\n });\n\n test('does not wait for client to initialize', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n\n expect(ldClient.waitForInitialization).not.toHaveBeenCalled();\n });\n\n test('re-renders when client is initialized', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n expect(renderCount).toBe(1);\n\n act(() => initializedCallback?.());\n expect(renderCount).toBe(2);\n });\n\n test('deregisters listener when component is unmounted', async () => {\n const { unmount } = subject();\n\n await waitFor(() => expectLDProvider());\n\n unmount();\n\n expect(jest.mocked(ldClient.off)).toHaveBeenCalledWith(\n 'initialized',\n initializedCallback\n );\n });\n });\n\n describe('when initialization fails', () => {\n beforeEach(() => {\n jest.mocked(ldClient.waitForInitialization)?.mockRejectedValue('Oops!');\n });\n\n test('renders base LDProvider', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n });\n\n describe('when log service is provided', () => {\n beforeEach(() => singletons.push({ provide: Log, useValue: log }));\n\n test('logs error', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n\n expect(log.error).toHaveBeenCalledWith({\n category: 'LaunchDarkly',\n code: 'InitializationFailed',\n error: 'Oops!',\n message: `${props.projectName} client failed to initialize`,\n });\n });\n });\n });\n\n describe('when no context is provided', () => {\n beforeEach(() => {\n props.context = null;\n });\n\n test('does not initialize client', () => {\n subject();\n\n expect(getLDClient).not.toHaveBeenCalled();\n });\n\n test('does not render children', () => {\n subject();\n\n expect(screen.queryByText('<Component />')).not.toBeInTheDocument();\n });\n\n describe('when context becomes available after mount', () => {\n let rerender: (ui: ReactNode) => void;\n\n beforeEach(async () => {\n rerender = subject().rerender;\n props.context = { kind: 'user', key: 'user-1' };\n rerender(tree());\n\n await waitFor(() =>\n expectLDProvider({ clientSideID: props.clientSideID, ldClient })\n );\n });\n\n test('initializes client', () => {\n expect(getLDClient).toHaveBeenCalledWith(\n expect.objectContaining({ context: props.context })\n );\n });\n\n test('renders children', () => {\n expect(screen.queryByText('<Component />')).toBeInTheDocument();\n });\n\n describe('when context is removed', () => {\n beforeEach(() => {\n props.context = null;\n rerender(tree());\n });\n\n test('removes children', () => {\n expect(screen.queryByText('<Component />')).not.toBeInTheDocument();\n });\n });\n });\n });\n\n describe('when LDService is provided', () => {\n const ldServiceClient: Partial<LDClient> & { toString: () => string } = {\n waitUntilReady: jest.fn().mockResolvedValue(undefined),\n waitForInitialization: jest.fn().mockResolvedValue(undefined),\n toString: () => 'ldServiceClient',\n };\n let ldService: LDService;\n\n beforeEach(() => {\n ldService = { getClient: jest.fn().mockReturnValue(ldServiceClient) };\n singletons?.push({ provide: LD_SERVICE_TOKEN, useValue: ldService });\n });\n\n test('uses LDService client', async () => {\n subject();\n\n await waitFor(() =>\n expectLDProvider({ clientSideID: props.clientSideID, ldClient: ldServiceClient })\n );\n\n expect(ldServiceClient.waitForInitialization).toHaveBeenCalled();\n });\n\n describe('when LDService has options', () => {\n const options: LDService['options'] = { hash: 'abc123' };\n\n beforeEach(() => (ldService.options = options));\n\n test('forwards options to LDClient', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n\n expect(ldService.getClient).toHaveBeenCalledWith(\n expect.objectContaining({ options })\n );\n });\n });\n\n describe('when application is provided', () => {\n const application = { id: 'some-application', version: '1.2.3' };\n\n beforeEach(() => (ldService.application = application));\n\n test('passes application to LDClient', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n\n expect(ldService.getClient).toHaveBeenCalledWith(\n expect.objectContaining({\n options: { application },\n })\n );\n });\n });\n });\n});\n"],"names":["Log","Provider","useDependencies","render","screen","waitFor","act","Fragment","FeatureFlagStore","getLDClient","LDProvider","LD_SERVICE_TOKEN","formatProps","props","Object","entries","map","key","value","toString","mockComponent","name","children","div","jest","mock","fn","describe","log","error","ldClient","on","off","waitUntilReady","mockResolvedValue","undefined","waitForInitialization","singletons","providedFeatureFlagStore","renderCount","Component","beforeEach","clearAllMocks","mocked","mockReturnValue","clientSideID","projectName","context","kind","tree","subject","expectLDProvider","expect","getByText","toBeInTheDocument","test","toHaveBeenCalled","flags","EnableThing","toBeDefined","initializedCallback","mockImplementation","event","callback","not","toBe","unmount","toHaveBeenCalledWith","mockRejectedValue","push","provide","useValue","category","code","message","queryByText","rerender","objectContaining","ldServiceClient","ldService","getClient","options","hash","application","id","version"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servicetitan/launchdarkly-service",
3
- "version": "39.0.0",
3
+ "version": "39.1.0",
4
4
  "description": "Service for creating and reusing client connections for LaunchDarkly",
5
5
  "homepage": "https://docs.st.dev/docs/frontend/uikit/launchdarkly-service",
6
6
  "repository": {
@@ -27,10 +27,10 @@
27
27
  ],
28
28
  "devDependencies": {
29
29
  "@datadog/browser-rum": "^7.5.0",
30
- "@servicetitan/log-service": "39.0.0",
31
- "@servicetitan/react-ioc": "39.0.0",
30
+ "@servicetitan/log-service": "39.1.0",
31
+ "@servicetitan/react-ioc": "39.1.0",
32
32
  "@testing-library/dom": "^10.4.1",
33
- "@testing-library/jest-dom": "^7.0.0",
33
+ "@testing-library/jest-dom": "^7.0.1",
34
34
  "@testing-library/react": "^16.3.2",
35
35
  "launchdarkly-js-client-sdk": "3.5.0",
36
36
  "launchdarkly-react-client-sdk": "3.6.1",
@@ -40,8 +40,8 @@
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@datadog/browser-rum": ">=6.19.0",
43
- "@servicetitan/log-service": "39.0.0",
44
- "@servicetitan/react-ioc": "39.0.0",
43
+ "@servicetitan/log-service": "39.1.0",
44
+ "@servicetitan/react-ioc": "39.1.0",
45
45
  "launchdarkly-js-client-sdk": "3.5.0",
46
46
  "launchdarkly-react-client-sdk": "3.6.1",
47
47
  "mobx": ">= 6.13.5",
@@ -59,5 +59,5 @@
59
59
  "cli": {
60
60
  "webpack": false
61
61
  },
62
- "gitHead": "9138e3fcda061941b89620b9004426f5c13a89ae"
62
+ "gitHead": "a2471578a506e8c40e90956a5c70cc1638bbe9c1"
63
63
  }
@@ -1,12 +1,14 @@
1
- import { LDClient, initialize } from 'launchdarkly-js-client-sdk';
1
+ import { LDClient, LDOptions, initialize } from 'launchdarkly-js-client-sdk';
2
2
  import { ClientConfig } from './types';
3
3
 
4
4
  const ldClients: Map<string, LDClient> = new Map();
5
5
 
6
+ const defaultOptions: LDOptions = { sendEventsOnlyForVariation: true };
7
+
6
8
  export function getLDClient({ clientSideID, context, options }: ClientConfig): LDClient {
7
9
  let client = ldClients.get(clientSideID);
8
10
  if (!client) {
9
- client = initialize(clientSideID, context, options);
11
+ client = initialize(clientSideID, context, { ...defaultOptions, ...options });
10
12
  ldClients.set(clientSideID, client);
11
13
  }
12
14
  return client;
@@ -11,7 +11,7 @@ import { FeatureFlagStore } from './feature-flag-store';
11
11
  import { getLDClient } from './get-ld-client';
12
12
  import { ClientConfig, LDService, LD_SERVICE_TOKEN } from './types';
13
13
 
14
- export type ProviderConfig = Pick<BaseProviderConfig, 'clientSideID'> & {
14
+ export type ProviderConfig = Pick<BaseProviderConfig, 'clientSideID' | 'flags'> & {
15
15
  /** Pass `null` to defer client init until a real context is available. */
16
16
  context: LDContext | null;
17
17
  projectName: string;
@@ -32,7 +32,14 @@ function mergeOptions(ldService: LDService | undefined): BaseProviderConfig['opt
32
32
  export const LDProvider: FC<PropsWithChildren<ProviderConfig>> = provide({
33
33
  singletons: [FeatureFlagStore],
34
34
  })(props => {
35
- const { children, clientSideID, context, projectName, waitForInitialization = true } = props;
35
+ const {
36
+ children,
37
+ clientSideID,
38
+ context,
39
+ flags,
40
+ projectName,
41
+ waitForInitialization = true,
42
+ } = props;
36
43
  const [ldClient, setLDClient] = useState<LDClient>();
37
44
  const [ldService, log] = useOptionalDependencies(LD_SERVICE_TOKEN, Log);
38
45
  const [counter, forceUpdate] = useReducer((x: number) => x + 1, 0);
@@ -87,7 +94,12 @@ export const LDProvider: FC<PropsWithChildren<ProviderConfig>> = provide({
87
94
  }
88
95
 
89
96
  return (
90
- <BaseLDProvider key={counter.toString()} clientSideID={clientSideID} ldClient={ldClient}>
97
+ <BaseLDProvider
98
+ key={counter.toString()}
99
+ clientSideID={clientSideID}
100
+ ldClient={ldClient}
101
+ {...(flags && { flags })}
102
+ >
91
103
  <FeatureFlagProvider>{children}</FeatureFlagProvider>
92
104
  </BaseLDProvider>
93
105
  );
@@ -7,6 +7,7 @@ jest.mock('launchdarkly-js-client-sdk', () => ({
7
7
 
8
8
  describe(`[launchdarkly-service] ${getLDClient.name}`, () => {
9
9
  const mockClient: any = Symbol('mockClient');
10
+ const defaultOptions = { sendEventsOnlyForVariation: true };
10
11
  let config: Parameters<typeof getLDClient>[0];
11
12
 
12
13
  const subject = () => getLDClient(config);
@@ -17,26 +18,37 @@ describe(`[launchdarkly-service] ${getLDClient.name}`, () => {
17
18
  config = { clientSideID: makeId(), context: { kind: 'multi' } };
18
19
  });
19
20
 
20
- test('initializes client with the given context', () => {
21
+ test('initializes client with the given context and default options', () => {
21
22
  expect(subject()).toBe(mockClient);
22
23
  expect(initialize).toHaveBeenCalledWith(
23
24
  config.clientSideID,
24
25
  config.context,
25
- config.options
26
+ defaultOptions
26
27
  );
27
28
  });
28
29
 
29
30
  describe('when given options', () => {
30
31
  beforeEach(() => (config.options = { hash: 'foo' }));
31
32
 
32
- test('passes options through unchanged', () => {
33
+ test('passes options through alongside the defaults', () => {
33
34
  subject();
34
35
 
35
- expect(initialize).toHaveBeenCalledWith(
36
- config.clientSideID,
37
- config.context,
38
- config.options
39
- );
36
+ expect(initialize).toHaveBeenCalledWith(config.clientSideID, config.context, {
37
+ ...defaultOptions,
38
+ ...config.options,
39
+ });
40
+ });
41
+ });
42
+
43
+ describe('when given sendEventsOnlyForVariation', () => {
44
+ beforeEach(() => (config.options = { sendEventsOnlyForVariation: false }));
45
+
46
+ test('lets the caller override the default', () => {
47
+ subject();
48
+
49
+ expect(initialize).toHaveBeenCalledWith(config.clientSideID, config.context, {
50
+ sendEventsOnlyForVariation: false,
51
+ });
40
52
  });
41
53
  });
42
54
 
@@ -98,6 +98,18 @@ describe(`[launchdarkly-service] ${LDProvider.name}`, () => {
98
98
  expect(ldClient.waitForInitialization).toHaveBeenCalled();
99
99
  });
100
100
 
101
+ describe('when given flags', () => {
102
+ beforeEach(() => (props.flags = { EnableThing: false }));
103
+
104
+ test('forwards them to the base provider', async () => {
105
+ subject();
106
+
107
+ await waitFor(() =>
108
+ expectLDProvider({ clientSideID: props.clientSideID, ldClient, flags: props.flags })
109
+ );
110
+ });
111
+ });
112
+
101
113
  test('provides FeatureFlagStore', async () => {
102
114
  subject();
103
115